diff --git a/examples/sx128x/lora_rx/main.go b/examples/sx128x/lora_rx/main.go new file mode 100644 index 000000000..33ea60ddf --- /dev/null +++ b/examples/sx128x/lora_rx/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "errors" + "machine" + "runtime" + "time" + + "tinygo.org/x/drivers/sx128x" +) + +var ( + // pin mapping specific to the lilygo t3s3, change as needed for your board + sdoPin = machine.GPIO6 + sdiPin = machine.GPIO3 + sckPin = machine.GPIO5 + nssPin = machine.GPIO7 + busyPin = machine.GPIO36 + resetPin = machine.GPIO8 + dio1Pin = machine.GPIO9 +) + +func setupPins() { + nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + nssPin.Set(true) + + resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + resetPin.Set(true) + + busyPin.Configure(machine.PinConfig{Mode: machine.PinInput}) + dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInput}) +} + +func main() { + setupPins() + + spi := machine.SPI0 + spi.Configure(machine.SPIConfig{ + Mode: 0, + Frequency: 8 * 1e6, + SDO: sdoPin, + SDI: sdiPin, + SCK: sckPin, + }) + + radio := sx128x.New( + spi, + nssPin, + resetPin, + busyPin, + ) + + radio.WaitWhileBusy(time.Second) + SetupLora(radio) + + for { + data, err := Rx(radio) + if err != nil { + println("failed to receive:", err) + } else { + println("received:", string(data)) + } + } +} + +func SetupLora(radio *sx128x.Device) { + radio.SetStandby(sx128x.STANDBY_RC) + radio.SetPacketType(sx128x.PACKET_TYPE_LORA) + radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC) + + radio.SetRfFrequency(2400000000) // 2.4Ghz + radio.SetModulationParamsLoRa(sx128x.LORA_SF_9, sx128x.LORA_BW_1600, sx128x.LORA_CR_4_7) + + // section 14.4.1 shows required register setting for setting up LoRa operations. These depend on the chosen spreading factor. + radio.WriteRegister(0x925, []byte{0x32}) + radio.WriteRegister(0x93C, []byte{0x01}) + + radio.SetTxParams(13, sx128x.RADIO_RAMP_02_US) + radio.SetPacketParamsLoRa(12, sx128x.LORA_HEADER_EXPLICIT, 0xFF, sx128x.LORA_CRC_DISABLE, sx128x.LORA_IQ_STD) + radio.WriteRegister(sx128x.REG_LORA_SYNC_WORD_MSB, []byte{0x14, 0x24}) // full sync word is 0x1424 + +} + +func Rx(radio *sx128x.Device) ([]byte, error) { + radio.SetStandby(sx128x.STANDBY_RC) + radio.SetDioIrqParams(sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00) + radio.SetBufferBaseAddress(0, 0) + radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK) + radio.SetRx(sx128x.PERIOD_BASE_4_MS, 250) // 4ms * 250 = 1s + // busy wait for IRQ indication + for dio1Pin.Get() == false { + runtime.Gosched() + } + irqStatus, _ := radio.GetIrqStatus() + if irqStatus&sx128x.IRQ_RX_DONE_MASK != 0 { + payloadLength, bufferOffset, err := radio.GetRxBufferStatus() + if err != nil { + return nil, err + } + data, err := radio.ReadBuffer(bufferOffset, payloadLength) + return data, nil + } else if irqStatus&sx128x.IRQ_RX_TX_TIMEOUT_MASK != 0 { + return nil, errors.New("rx timeout") + } + return nil, errors.New("unexpected IRQ status") +} diff --git a/examples/sx128x/lora_tx/main.go b/examples/sx128x/lora_tx/main.go new file mode 100644 index 000000000..248d3a7cb --- /dev/null +++ b/examples/sx128x/lora_tx/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "errors" + "machine" + "runtime" + "time" + + "tinygo.org/x/drivers/sx128x" +) + +var ( + // pin mapping specific to the lilygo t3s3, change as needed for your board + sdoPin = machine.GPIO6 + sdiPin = machine.GPIO3 + sckPin = machine.GPIO5 + nssPin = machine.GPIO7 + busyPin = machine.GPIO36 + resetPin = machine.GPIO8 + dio1Pin = machine.GPIO9 +) + +func setupPins() { + nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + nssPin.Set(true) + + resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + resetPin.Set(true) + + busyPin.Configure(machine.PinConfig{Mode: machine.PinInput}) + dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInput}) +} + +func main() { + setupPins() + + spi := machine.SPI0 + spi.Configure(machine.SPIConfig{ + Mode: 0, + Frequency: 8 * 1e6, + SDO: sdoPin, + SDI: sdiPin, + SCK: sckPin, + }) + + radio := sx128x.New( + spi, + nssPin, + resetPin, + busyPin, + ) + + radio.WaitWhileBusy(time.Second) + SetupLora(radio) + + for { + Tx(radio, []byte("Hello, world!")) + time.Sleep(1 * time.Second) + } +} + +func SetupLora(radio *sx128x.Device) { + radio.SetStandby(sx128x.STANDBY_RC) + radio.SetPacketType(sx128x.PACKET_TYPE_LORA) + radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC) + + radio.SetRfFrequency(2400000000) // 2.4Ghz + radio.SetModulationParamsLoRa(sx128x.LORA_SF_9, sx128x.LORA_BW_1600, sx128x.LORA_CR_4_7) + + // section 14.4.1 shows required register setting for setting up LoRa operations. These depend on the chosen spreading factor. + radio.WriteRegister(0x925, []byte{0x32}) + radio.WriteRegister(0x93C, []byte{0x01}) + + radio.SetTxParams(13, sx128x.RADIO_RAMP_02_US) + radio.SetPacketParamsLoRa(12, sx128x.LORA_HEADER_EXPLICIT, 0xFF, sx128x.LORA_CRC_DISABLE, sx128x.LORA_IQ_STD) + radio.WriteRegister(sx128x.REG_LORA_SYNC_WORD_MSB, []byte{0x14, 0x24}) // full sync word is 0x1424 + +} + +func Tx(radio *sx128x.Device, data []byte) error { + if len(data) > 255 { + return errors.New("data length exceeds maximum of 255 bytes") + } + radio.SetStandby(sx128x.STANDBY_RC) + radio.SetPacketParamsLoRa(12, sx128x.LORA_HEADER_EXPLICIT, uint8(len(data)&0xFF), sx128x.LORA_CRC_DISABLE, sx128x.LORA_IQ_STD) + radio.SetBufferBaseAddress(0, 0) + radio.WriteBuffer(0, data) + radio.SetDioIrqParams(sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00) + radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK) + radio.SetTx(sx128x.PERIOD_BASE_4_MS, 250) // 4ms * 250 = 1s + // busy wait for IRQ indication + for dio1Pin.Get() == false { + runtime.Gosched() + } + return nil +} diff --git a/sx128x/README.md b/sx128x/README.md new file mode 100644 index 000000000..1dd0198f2 --- /dev/null +++ b/sx128x/README.md @@ -0,0 +1,7 @@ +# SX128x Radio +Radio from Semtech in the 2.4 GHz band. This driver uses SPI to communicate with the radio instead of the alternative UART interface. + +## Supported Chips +- [SX1280](https://www.semtech.com/products/wireless-rf/lora-connect/sx1280) +- [SX1281](https://www.semtech.com/products/wireless-rf/lora-connect/sx1281) + diff --git a/sx128x/commands.go b/sx128x/commands.go new file mode 100644 index 000000000..fc294cdb6 --- /dev/null +++ b/sx128x/commands.go @@ -0,0 +1,52 @@ +package sx128x + +const ( + // SX128X SPI commands + cmdGetStatus = uint8(0xC0) + + // Register Access Operations + cmdWriteRegister = uint8(0x18) + cmdReadRegister = uint8(0x19) + + // Data Buffer Operations + cmdWriteBuffer = uint8(0x1A) + cmdReadBuffer = uint8(0x1B) + + // Radio Operation Modes + cmdSetSleep = uint8(0x84) + cmdSetStandby = uint8(0x80) + cmdSetFS = uint8(0xC1) + cmdSetTx = uint8(0x83) + cmdSetRx = uint8(0x82) + cmdSetRxDutyCycle = uint8(0x94) + cmdSetLongPreamble = uint8(0x9B) + cmdSetCAD = uint8(0xC5) + cmdSetTxContinuousWave = uint8(0xD1) + cmdSetContinuousPreamble = uint8(0xD2) + cmdSetAutoTx = uint8(0x98) + cmdSetAutoFS = uint8(0x9E) + + // Radio Configuration + cmdSetPacketType = uint8(0x8A) + cmdGetPacketType = uint8(0x03) + cmdSetRFFrequency = uint8(0x86) + cmdSetTxParams = uint8(0x8E) + cmdSetCADParams = uint8(0x88) + cmdSetBufferBaseAddress = uint8(0x8F) + cmdSetModulationParams = uint8(0x8B) + cmdSetPacketParams = uint8(0x8C) + + // Communication Status Information + cmdGetRxBufferStatus = uint8(0x17) + cmdGetPacketStatus = uint8(0x1D) + cmdGetRSSIInst = uint8(0x1F) + + // IRQ Handling + cmdSetDIOIRQParams = uint8(0x8D) + cmdGetIRQStatus = uint8(0x15) + cmdClearIRQStatus = uint8(0x97) + + // Miscellaneous + cmdSetRegulatorMode = uint8(0x96) + cmdSetSaveContext = uint8(0xD5) +) diff --git a/sx128x/constants.go b/sx128x/constants.go new file mode 100644 index 000000000..aa0e17d09 --- /dev/null +++ b/sx128x/constants.go @@ -0,0 +1,356 @@ +package sx128x + +type SleepConfig uint8 +type StandbyConfig uint8 +type PeriodBase uint8 +type PacketType uint8 +type RadioRampTime uint8 +type CadSymbolNum uint8 + +// GFSK Modulation Params +type GFSKBLEBitrateBandwidth uint8 +type ModulationIndex uint8 +type ModulationShaping uint8 + +// GFSK Packet Params +type GFSKPreambleLength uint8 +type GFSKSyncWordLength uint8 +type GFSKSyncWordMatch uint8 +type GFSKHeaderType uint8 +type GFSKCrcType uint8 + +// BLE Packet Params +type BLEConnectionState uint8 +type BLECrcType uint8 +type BLETestPayload uint8 + +// FLRC Modulation Params +type FLRCBitrateBandwidth uint8 +type FLRCCodingRate uint8 + +// FLRC Packet Params +type FLRCPreambleLength uint8 +type FLRCSyncWordLength uint8 +type FLRCSyncWordMatch uint8 +type FLRCHeaderType uint8 +type FLRCCrcType uint8 + +// LoRa Modulation Params +type LoRaSpreadingFactor uint8 +type LoRaBandwidth uint8 +type LoRaCodingRate uint8 + +// LoRa Packet Params +type LoRaHeaderType uint8 +type LoRaCrcType uint8 +type LoRaIqType uint8 + +// Misc +type RegulatorMode uint8 +type IRQMask = uint16 +type CircuitMode uint8 +type CommandStatus uint8 + +// Packet Status +type GFSKPacketInfo uint8 +type BLEPacketInfo uint8 +type FLRCPacketInfo uint8 + +const ( + whiteningDisable = 0x00 + whiteningEnable = 0x08 + + // Circuit Mode + circuitModeMask = uint8(0b11100000) + CIRCUIT_MODE_STDBY_RC = CircuitMode(0x2) + CIRCUIT_MODE_STDBY_XOSC = CircuitMode(0x3) + CIRCUIT_MODE_FS = CircuitMode(0x4) + CIRCUIT_MODE_RX = CircuitMode(0x5) + CIRCUIT_MODE_TX = CircuitMode(0x6) + + // Command Status + commandStatusMask = uint8(0b00011100) + COMMAND_STATUS_SUCCESS = CommandStatus(0x1) + COMMAND_STATUS_DATA_AVAILABLE = CommandStatus(0x2) + COMMAND_STATUS_TIMEOUT = CommandStatus(0x3) + COMMAND_STATUS_PROCESSING_ERROR = CommandStatus(0x4) + COMMAND_STATUS_EXECUTION_ERROR = CommandStatus(0x5) + COMMAND_STATUS_TX_DONE = CommandStatus(0x6) + + // SleepConfig + SLEEP_DATA_BUFFER_RETAIN = SleepConfig(2) + SLEEP_DATA_RAM_RETAIN = SleepConfig(1) + + // StandbyConfig + STANDBY_RC = StandbyConfig(0) + STANDBY_XOSC = StandbyConfig(1) + + // PeriodBase + PERIOD_BASE_15_625_US = PeriodBase(0) + PERIOD_BASE_62_5_US = PeriodBase(1) + PERIOD_BASE_1_MS = PeriodBase(2) + PERIOD_BASE_4_MS = PeriodBase(3) + + // PacketType + PACKET_TYPE_GFSK = PacketType(0x00) // default + PACKET_TYPE_LORA = PacketType(0x01) + PACKET_TYPE_RANGING = PacketType(0x02) + PACKET_TYPE_FLRC = PacketType(0x03) + PACKET_TYPE_BLE = PacketType(0x04) + + // RampTime + RADIO_RAMP_02_US = RadioRampTime(0x00) + RADIO_RAMP_04_US = RadioRampTime(0x20) + RADIO_RAMP_06_US = RadioRampTime(0x40) + RADIO_RAMP_08_US = RadioRampTime(0x60) + RADIO_RAMP_10_US = RadioRampTime(0x80) + RADIO_RAMP_12_US = RadioRampTime(0xA0) + RADIO_RAMP_16_US = RadioRampTime(0xC0) + RADIO_RAMP_20_US = RadioRampTime(0xE0) + + // CadSymbolNum + LORA_CAD_01_SYMBOL = CadSymbolNum(0x00) + LORA_CAD_02_SYMBOLS = CadSymbolNum(0x20) + LORA_CAD_04_SYMBOLS = CadSymbolNum(0x40) + LORA_CAD_08_SYMBOLS = CadSymbolNum(0x60) + LORA_CAD_16_SYMBOLS = CadSymbolNum(0x80) + + // GFSK Modulation Params + // Bitrate + Bandwidth - same for BLE + GFSK_BLE_BR_2_000_BW_2_4 = GFSKBLEBitrateBandwidth(0x04) + GFSK_BLE_BR_1_600_BW_2_4 = GFSKBLEBitrateBandwidth(0x28) + GFSK_BLE_BR_1_000_BW_2_4 = GFSKBLEBitrateBandwidth(0x4C) + GFSK_BLE_BR_1_000_BW_1_2 = GFSKBLEBitrateBandwidth(0x45) + GFSK_BLE_BR_0_800_BW_2_4 = GFSKBLEBitrateBandwidth(0x70) + GFSK_BLE_BR_0_800_BW_1_2 = GFSKBLEBitrateBandwidth(0x69) + GFSK_BLE_BR_0_500_BW_1_2 = GFSKBLEBitrateBandwidth(0x8D) + GFSK_BLE_BR_0_500_BW_0_6 = GFSKBLEBitrateBandwidth(0x86) + GFSK_BLE_BR_0_400_BW_1_2 = GFSKBLEBitrateBandwidth(0xB1) + GFSK_BLE_BR_0_400_BW_0_6 = GFSKBLEBitrateBandwidth(0xAA) + GFSK_BLE_BR_0_250_BW_0_6 = GFSKBLEBitrateBandwidth(0xCE) + GFSK_BLE_BR_0_250_BW_0_3 = GFSKBLEBitrateBandwidth(0xC7) + GFSK_BLE_BR_0_125_BW_0_3 = GFSKBLEBitrateBandwidth(0xEF) + + // Modulation Index - same for BLE + MOD_IND_0_35 = ModulationIndex(0x00) + MOD_IND_0_5 = ModulationIndex(0x01) + MOD_IND_0_75 = ModulationIndex(0x02) + MOD_IND_1_00 = ModulationIndex(0x03) + MOD_IND_1_25 = ModulationIndex(0x04) + MOD_IND_1_50 = ModulationIndex(0x05) + MOD_IND_1_75 = ModulationIndex(0x06) + MOD_IND_2_00 = ModulationIndex(0x07) + MOD_IND_2_25 = ModulationIndex(0x08) + MOD_IND_2_50 = ModulationIndex(0x09) + MOD_IND_2_75 = ModulationIndex(0x0A) + MOD_IND_3_00 = ModulationIndex(0x0B) + MOD_IND_3_25 = ModulationIndex(0x0C) + MOD_IND_3_50 = ModulationIndex(0x0D) + MOD_IND_3_75 = ModulationIndex(0x0E) + MOD_IND_4_00 = ModulationIndex(0x0F) + + // Modulation Shaping - same for BLE and FLRC + MOD_SHAPING_OFF = ModulationShaping(0x00) + MOD_SHAPING_1_0 = ModulationShaping(0x10) + MOD_SHAPING_0_5 = ModulationShaping(0x20) + + // GFSK Packet Params + // Preamble Length + GFSK_PREAMBLE_LENGTH_04_BITS = GFSKPreambleLength(0x00) + GFSK_PREAMBLE_LENGTH_08_BITS = GFSKPreambleLength(0x10) + GFSK_PREAMBLE_LENGTH_12_BITS = GFSKPreambleLength(0x20) + GFSK_PREAMBLE_LENGTH_16_BITS = GFSKPreambleLength(0x30) + GFSK_PREAMBLE_LENGTH_20_BITS = GFSKPreambleLength(0x40) + GFSK_PREAMBLE_LENGTH_24_BITS = GFSKPreambleLength(0x50) + GFSK_PREAMBLE_LENGTH_28_BITS = GFSKPreambleLength(0x60) + GFSK_PREAMBLE_LENGTH_32_BITS = GFSKPreambleLength(0x70) + + // Sync Word Length + GFSK_SYNC_WORD_LEN_1_B = GFSKSyncWordLength(0x00) + GFSK_SYNC_WORD_LEN_2_B = GFSKSyncWordLength(0x02) + GFSK_SYNC_WORD_LEN_3_B = GFSKSyncWordLength(0x04) + GFSK_SYNC_WORD_LEN_4_B = GFSKSyncWordLength(0x06) + GFSK_SYNC_WORD_LEN_5_B = GFSKSyncWordLength(0x08) + + // Sync Word Match + GFSK_SYNCWORD_MATCH_OFF = GFSKSyncWordMatch(0x00) + GFSK_SYNCWORD_MATCH_1 = GFSKSyncWordMatch(0x10) + GFSK_SYNCWORD_MATCH_2 = GFSKSyncWordMatch(0x20) + GFSK_SYNCWORD_MATCH_1_2 = GFSKSyncWordMatch(0x30) + GFSK_SYNCWORD_MATCH_3 = GFSKSyncWordMatch(0x40) + GFSK_SYNCWORD_MATCH_1_3 = GFSKSyncWordMatch(0x50) + GFSK_SYNCWORD_MATCH_2_3 = GFSKSyncWordMatch(0x60) + GFSK_SYNCWORD_MATCH_1_2_3 = GFSKSyncWordMatch(0x70) + + // GFSK Header Type + GFSK_HEADER_FIXED_LENGTH = GFSKHeaderType(0x00) + GFSK_HEADER_VARIABLE_LENGTH = GFSKHeaderType(0x20) + + // GFSK CRC Type + GFSK_CRC_OFF = GFSKCrcType(0x00) + GFSK_CRC_1_BYTE = GFSKCrcType(0x10) + GFSK_CRC_2_BYTES = GFSKCrcType(0x20) + + // BLE Packet Params + // Connection State + BLE_MASTER_SLAVE = BLEConnectionState(0x00) + BLE_ADVERTISER = BLEConnectionState(0x02) + BLE_TX_TEST_MODE = BLEConnectionState(0x04) + BLE_RX_TEST_MODE = BLEConnectionState(0x06) + BLE_RXTX_TEST_MODE = BLEConnectionState(0x08) + + // CRC Type + BLE_CRC_OFF = BLECrcType(0x00) + BLE_CRC_3_BYTES = BLECrcType(0x10) + + // BLE Test Payload + BLE_PAYLOAD_PRBS_9 = BLETestPayload(0x00) + BLE_PAYLOAD_EYELONG_1_0 = BLETestPayload(0x04) + BLE_PAYLOAD_EYESHORT_1_0 = BLETestPayload(0x08) + BLE_PAYLOAD_PRBS_15 = BLETestPayload(0x0C) + BLE_PAYLOAD_ALL_1 = BLETestPayload(0x10) + BLE_PAYLOAD_ALL_0 = BLETestPayload(0x14) + BLE_PAYLOAD_EYELONG_0_1 = BLETestPayload(0x18) + BLE_PAYLOAD_EYESHORT_0_1 = BLETestPayload(0x1C) + + // FLRC Modulation Params + // Bitrate + Bandwidth + FLRC_BR_1_300_BW_1_2 = FLRCBitrateBandwidth(0x45) + FLRC_BR_1_000_BW_1_2 = FLRCBitrateBandwidth(0x69) + FLRC_BR_0_650_BW_0_6 = FLRCBitrateBandwidth(0x86) + FLRC_BR_0_520_BW_0_6 = FLRCBitrateBandwidth(0xAA) + FLRC_BR_0_325_BW_0_3 = FLRCBitrateBandwidth(0xC7) + FLRC_BR_0_260_BW_0_3 = FLRCBitrateBandwidth(0xEB) + + // Coding Rate + FLRC_CR_1_2 = FLRCCodingRate(0x00) // 1/2 + FLRC_CR_3_4 = FLRCCodingRate(0x02) // 3/4 + FLRC_CR_1_0 = FLRCCodingRate(0x04) // 1 + + // FLRC Packet Params + // Preamble Length + FLRC_PREAMBLE_LENGTH_4_BITS = FLRCPreambleLength(0x00) + FLRC_PREAMBLE_LENGTH_8_BITS = FLRCPreambleLength(0x10) + FLRC_PREAMBLE_LENGTH_12_BITS = FLRCPreambleLength(0x20) + FLRC_PREAMBLE_LENGTH_16_BITS = FLRCPreambleLength(0x30) + FLRC_PREAMBLE_LENGTH_20_BITS = FLRCPreambleLength(0x40) + FLRC_PREAMBLE_LENGTH_24_BITS = FLRCPreambleLength(0x50) + FLRC_PREAMBLE_LENGTH_28_BITS = FLRCPreambleLength(0x60) + FLRC_PREAMBLE_LENGTH_32_BITS = FLRCPreambleLength(0x70) + + // Sync Word Length + FLRC_SYNC_WORD_LEN_0 = FLRCSyncWordLength(0x00) + FLRC_SYNC_WORD_LEN_32_BITS = FLRCSyncWordLength(0x04) + + // Sync Word Match + FLRC_SYNC_WORD_MATCH_DISABLE = FLRCSyncWordMatch(0x00) // Disable Sync Word + FLRC_SYNC_WORD_MATCH_1 = FLRCSyncWordMatch(0x10) // Sync Word 1 + FLRC_SYNC_WORD_MATCH_2 = FLRCSyncWordMatch(0x20) // Sync Word 2 + FLRC_SYNC_WORD_MATCH_1_2 = FLRCSyncWordMatch(0x30) // Sync Word 1 or Sync Word 2 + FLRC_SYNC_WORD_MATCH_3 = FLRCSyncWordMatch(0x40) // Sync Word 3 + FLRC_SYNC_WORD_MATCH_1_3 = FLRCSyncWordMatch(0x50) // Sync Word 1 or Sync Word 3 + FLRC_SYNC_WORD_MATCH_2_3 = FLRCSyncWordMatch(0x60) // Sync Word 2 or Sync Word 3 + FLRC_SYNC_WORD_MATCH_1_2_3 = FLRCSyncWordMatch(0x70) // Sync Word 1 or Sync Word 2 or Sync Word 3 + + // Header Type + FLRC_HEADER_FIXED_LENGTH = FLRCHeaderType(0x00) + FLRC_HEADER_VARIABLE_LENGTH = FLRCHeaderType(0x20) + + // CRC Type + FLRC_CRC_OFF = FLRCCrcType(0x00) + FLRC_CRC_1_BYTE = FLRCCrcType(0x10) + FLRC_CRC_2_BYTES = FLRCCrcType(0x20) + FLRC_CRC_3_BYTES = FLRCCrcType(0x30) + + // LoRa Modulation Params + + // SpreadingFactor + LORA_SF_5 = LoRaSpreadingFactor(0x50) + LORA_SF_6 = LoRaSpreadingFactor(0x60) + LORA_SF_7 = LoRaSpreadingFactor(0x70) + LORA_SF_8 = LoRaSpreadingFactor(0x80) + LORA_SF_9 = LoRaSpreadingFactor(0x90) + LORA_SF_10 = LoRaSpreadingFactor(0xA0) + LORA_SF_11 = LoRaSpreadingFactor(0xB0) + LORA_SF_12 = LoRaSpreadingFactor(0xC0) + + // Bandwidth + LORA_BW_1600 = LoRaBandwidth(0x0A) + LORA_BW_800 = LoRaBandwidth(0x18) + LORA_BW_400 = LoRaBandwidth(0x26) + LORA_BW_200 = LoRaBandwidth(0x34) + + // CodingRate + LORA_CR_4_5 = LoRaCodingRate(0x01) + LORA_CR_4_6 = LoRaCodingRate(0x02) + LORA_CR_4_7 = LoRaCodingRate(0x03) + LORA_CR_4_8 = LoRaCodingRate(0x04) + LORA_CR_LI_4_5 = LoRaCodingRate(0x05) + LORA_CR_LI_4_6 = LoRaCodingRate(0x06) + LORA_CR_LI_4_8 = LoRaCodingRate(0x07) + + // LoraPacketParams + // HeaderType + LORA_HEADER_EXPLICIT = LoRaHeaderType(0x00) + LORA_HEADER_IMPLICIT = LoRaHeaderType(0x80) + + // CRC Type + LORA_CRC_ENABLE = LoRaCrcType(0x20) + LORA_CRC_DISABLE = LoRaCrcType(0x00) + + // IQ Type + LORA_IQ_INVERTED = LoRaIqType(0x00) + LORA_IQ_STD = LoRaIqType(0x40) + + // RegulatorMode + REGULATOR_LDO = RegulatorMode(0) + REGULATOR_DC_DC = RegulatorMode(1) + + // IRQ masks + IRQ_ALL_MASK = IRQMask(0xFFFF) + IRQ_NONE_MASK = IRQMask(0x0000) + IRQ_TX_DONE_MASK = IRQMask(0b0000000000000001) + IRQ_RX_DONE_MASK = IRQMask(0b0000000000000010) + IRQ_SYNC_WORD_VALID_MASK = IRQMask(0b0000000000000100) + IRQ_SYNC_WORD_ERROR_MASK = IRQMask(0b0000000000001000) + IRQ_HEADER_VALID_MASK = IRQMask(0b0000000000010000) + IRQ_HEADER_ERROR_MASK = IRQMask(0b0000000000100000) + IRQ_CRC_ERROR_MASK = IRQMask(0b0000000001000000) + IRQ_RANGING_SLAVE_RESPONSE_DONE_MASK = IRQMask(0b0000000010000000) + IRQ_RANGING_SLAVE_RESPONSE_DISCARD_MASK = IRQMask(0b0000000100000000) + IRQ_RANGING_MASTER_RESULT_VALID_MASK = IRQMask(0b0000001000000000) + IRQ_RANGING_MASTER_TIMEOUT_MASK = IRQMask(0b0000010000000000) + IRQ_RANGING_SLAVE_REQUEST_VALID_MASK = IRQMask(0b0000100000000000) + IRQ_CAD_DONE_MASK = IRQMask(0b0001000000000000) + IRQ_CAD_DETECTED_MASK = IRQMask(0b0010000000000000) + IRQ_RX_TX_TIMEOUT_MASK = IRQMask(0b0100000000000000) + IRQ_PREAMBLE_DETECTED_MASK = IRQMask(0b1000000000000000) + IRQ_ADVANCED_RANGING_DONE_MASK = IRQMask(0b1000000000000000) + + // GFSK Packet Info + GFSK_SYNC_ERROR = GFSKPacketInfo(0b1000000) + GFSK_LENGTH_ERROR = GFSKPacketInfo(0b0100000) + GFSK_CRC_ERROR = GFSKPacketInfo(0b0010000) + GFSK_ABORT_ERROR = GFSKPacketInfo(0b0001000) + GFSK_HEADER_RECEIVED = GFSKPacketInfo(0b0000100) + GFSK_PACKET_RECEIVED = GFSKPacketInfo(0b0000010) + GFSK_PACKET_CRTL_BUSY = GFSKPacketInfo(0b0000001) + + // BLE Packet Info + BLE_SYNC_ERROR = BLEPacketInfo(0b1000000) + BLE_LENGTH_ERROR = BLEPacketInfo(0b0100000) + BLE_CRC_ERROR = BLEPacketInfo(0b0010000) + BLE_ABORT_ERROR = BLEPacketInfo(0b0001000) + BLE_HEADER_RECEIVED = BLEPacketInfo(0b0000100) + BLE_PACKET_RECEIVED = BLEPacketInfo(0b0000010) + BLE_PACKET_CRTL_BUSY = BLEPacketInfo(0b0000001) + + // FLRC Packet Info + FLRC_SYNC_ERROR = FLRCPacketInfo(0b1000000) + FLRC_LENGTH_ERROR = FLRCPacketInfo(0b0100000) + FLRC_CRC_ERROR = FLRCPacketInfo(0b0010000) + FLRC_ABORT_ERROR = FLRCPacketInfo(0b0001000) + FLRC_HEADER_RECEIVED = FLRCPacketInfo(0b0000100) + FLRC_PACKET_RECEIVED = FLRCPacketInfo(0b0000010) + FLRC_PACKET_CRTL_BUSY = FLRCPacketInfo(0b0000001) +) diff --git a/sx128x/errors.go b/sx128x/errors.go new file mode 100644 index 000000000..e81b812a6 --- /dev/null +++ b/sx128x/errors.go @@ -0,0 +1,19 @@ +package sx128x + +import "errors" + +var ( + ErrBusyPinTimeout = errors.New("busy pin timeout") + errDataTooLong = errors.New("data over 256 bytes") + errInvalidSleepConfig = errors.New("invalid sleep config") + errInvalidStandbyConfig = errors.New("invalid standby config") + errFrequencyTooLow = errors.New("frequency below 2.4Ghz") + errFrequencyTooHigh = errors.New("frequency above 2.5Ghz") + errPowerTooLow = errors.New("power level below -18dBm") + errPowerTooHigh = errors.New("power level above 13dBm") + errInvalidPeriodBase = errors.New("invalid period base") + errInvalidPacketType = errors.New("invalid packet type") + errInvalidRegulatorMode = errors.New("invalid regulator mode") + errPayloadLengthTooShort = errors.New("payload length too short") + errPayloadLengthTooLong = errors.New("payload length too long") +) diff --git a/sx128x/registers.go b/sx128x/registers.go new file mode 100644 index 000000000..83cd9db52 --- /dev/null +++ b/sx128x/registers.go @@ -0,0 +1,69 @@ +package sx128x + +const ( + + // SX128X register map + REG_FIRMWARE_VERSIONS = uint16(0x153) + REG_RX_GAIN = uint16(0x891) + REG_MANUAL_GAIN_SETTING = uint16(0x895) + REG_LNA_GAIN_VALUE = uint16(0x89E) + REG_LNA_GAIN_CONTROL = uint16(0x89F) + REG_SYNCH_PEAK_ATTENUATION = uint16(0x8C2) + REG_PAYLOAD_LENGTH = uint16(0x901) + REG_LORA_HEADER_MODE = uint16(0x903) + REG_RANGING_REQUEST_ADDRESS_BYTE_3 = uint16(0x912) + REG_RANGING_REQUEST_ADDRESS_BYTE_2 = uint16(0x913) + REG_RANGING_REQUEST_ADDRESS_BYTE_1 = uint16(0x914) + REG_RANGING_REQUEST_ADDRESS_BYTE_0 = uint16(0x915) + REG_RANGING_DEVICE_ADDRESS_BYTE_3 = uint16(0x916) + REG_RANGING_DEVICE_ADDRESS_BYTE_2 = uint16(0x917) + REG_RANGING_DEVICE_ADDRESS_BYTE_1 = uint16(0x918) + REG_RANGING_DEVICE_ADDRESS_BYTE_0 = uint16(0x919) + REG_RANGING_FILTER_WINDOW_SIZE = uint16(0x91E) + REG_RESET_RANGING_FILTER = uint16(0x923) + REG_RANGING_RESULT_MUX = uint16(0x924) + REG_SF_ADDITIONAL_CONFIGURATION = uint16(0x925) + REG_RANGING_CALIBRATION_BYTE_2 = uint16(0x92B) + REG_RANGING_CALIBRATION_BYTE_1 = uint16(0x92C) + REG_RANGING_CALIBRATION_BYTE_0 = uint16(0x92D) + REG_RANGING_ID_CHECK_LENGTH = uint16(0x931) + REG_FREQUENCY_ERROR_CORRECTION = uint16(0x93C) + REG_CAD_DETECT_PEAK = uint16(0x942) + REG_LORA_SYNC_WORD_MSB = uint16(0x944) + REG_LORA_SYNC_WORD_LSB = uint16(0x945) + REG_HEADER_CRC = uint16(0x954) + REG_CODING_RATE = uint16(0x950) + REG_FEI_BYTE_2 = uint16(0x954) + REG_FEI_BYTE_1 = uint16(0x955) + REG_FEI_BYTE_0 = uint16(0x956) + REG_RANGING_RESULT_BYTE_2 = uint16(0x961) + REG_RANGING_RESULT_BYTE_1 = uint16(0x962) + REG_RANGING_RESULT_BYTE_0 = uint16(0x963) + REG_RANGING_RSSI = uint16(0x964) + REG_FREEZE_RANGING_RESULT = uint16(0x97F) + REG_PACKET_PREAMBLE_SETTINGS = uint16(0x9C1) + REG_WHITENING_INITIAL_VALUE = uint16(0x9C5) + REG_CRC_POLYNOMIAL_DEFINITION_MSB = uint16(0x9C6) + REG_CRC_POLYNOMIAL_DEFINITION_LSB = uint16(0x9C7) + REG_CRC_POLYNOMIAL_SEED_BYTE_2 = uint16(0x9C7) + REG_CRC_POLYNOMIAL_SEED_BYTE_1 = uint16(0x9C8) + REG_CRC_POLYNOMIAL_SEED_BYTE_0 = uint16(0x9C9) + REG_CRC_MSB_INITIAL_VALUE = uint16(0x9C8) + REG_CRC_LSB_INITIAL_VALUE = uint16(0x9C9) + REG_SYNC_ADDRESS_CONTROL = uint16(0x9CD) + REG_SYNC_ADDRESS_1_BYTE_4 = uint16(0x9CE) + REG_SYNC_ADDRESS_1_BYTE_3 = uint16(0x9CF) + REG_SYNC_ADDRESS_1_BYTE_2 = uint16(0x9D0) + REG_SYNC_ADDRESS_1_BYTE_1 = uint16(0x9D1) + REG_SYNC_ADDRESS_1_BYTE_0 = uint16(0x9D2) + REG_SYNC_ADDRESS_2_BYTE_4 = uint16(0x9D3) + REG_SYNC_ADDRESS_2_BYTE_3 = uint16(0x9D4) + REG_SYNC_ADDRESS_2_BYTE_2 = uint16(0x9D5) + REG_SYNC_ADDRESS_2_BYTE_1 = uint16(0x9D6) + REG_SYNC_ADDRESS_2_BYTE_0 = uint16(0x9D7) + REG_SYNC_ADDRESS_3_BYTE_4 = uint16(0x9D8) + REG_SYNC_ADDRESS_3_BYTE_3 = uint16(0x9D9) + REG_SYNC_ADDRESS_3_BYTE_2 = uint16(0x9DA) + REG_SYNC_ADDRESS_3_BYTE_1 = uint16(0x9DB) + REG_SYNC_ADDRESS_3_BYTE_0 = uint16(0x9DC) +) diff --git a/sx128x/sx128x.go b/sx128x/sx128x.go new file mode 100644 index 000000000..a477fc62c --- /dev/null +++ b/sx128x/sx128x.go @@ -0,0 +1,768 @@ +package sx128x + +import ( + "runtime" + "time" + + "tinygo.org/x/drivers" + "tinygo.org/x/drivers/internal/pin" +) + +type Device struct { + spi drivers.SPI + nssPin pin.Output + resetPin pin.Output + busyPin pin.Input + spiTxBuf []byte + spiRxBuf []byte +} + +func New(spi drivers.SPI, nssPin pin.Output, resetPin pin.Output, busyPin pin.Input) *Device { + return &Device{ + spi: spi, + nssPin: nssPin, + resetPin: resetPin, + busyPin: busyPin, + spiTxBuf: make([]byte, 256), // TODO: optimize buffer size + spiRxBuf: make([]byte, 256), + } +} + +func (d *Device) Reset() { + d.resetPin.Set(false) + time.Sleep(10 * time.Millisecond) + d.resetPin.Set(true) + time.Sleep(10 * time.Millisecond) +} + +func (d *Device) WaitWhileBusy(timeout time.Duration) error { + // largest busy period is on boot with around ~400ish this should be more than enough + now := time.Now() + for d.busyPin.Get() { + if time.Since(now) > timeout { + return ErrBusyPinTimeout + } + runtime.Gosched() + } + return nil +} + +// Get tranceiver status, returns circuit mode and command status +func (d *Device) GetStatus() (CircuitMode, CommandStatus, error) { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return 0, 0, err + } + d.nssPin.Set(false) + status, err := d.spi.Transfer(cmdGetStatus) + d.nssPin.Set(true) + + if err != nil { + return 0, 0, err + } + + circuitMode := (status & circuitModeMask) >> 5 + commandStatus := (status & commandStatusMask) >> 2 + return CircuitMode(circuitMode), CommandStatus(commandStatus), nil +} + +func (d *Device) WriteRegister(addr uint16, data []byte) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdWriteRegister, uint8((addr>>8)&0xFF), uint8(addr&0xFF)) + d.spiTxBuf = append(d.spiTxBuf, data...) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +func (d *Device) ReadRegister(addr uint16) (uint8, error) { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return 0, err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdReadRegister, uint8((addr&0xFF00)>>8), uint8(addr&0x00FF), 0x00, 0x00) + d.spiRxBuf = d.spiRxBuf[:5] + err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf) + d.nssPin.Set(true) + if err != nil { + return 0, err + } + return d.spiRxBuf[4], nil +} + +func (d *Device) WriteBuffer(offset uint8, data []byte) error { + if len(data) > 256 { + return errDataTooLong + } + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdWriteBuffer, offset) + d.spiTxBuf = append(d.spiTxBuf, data...) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Read data from the payload buffer starting at the given offset with the given length +func (d *Device) ReadBuffer(offset uint8, length uint8) ([]byte, error) { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return nil, err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdReadBuffer, offset, 0x00) + for i := uint8(0); i < length; i++ { + d.spiTxBuf = append(d.spiTxBuf, 0x00) + } + d.spiRxBuf = d.spiRxBuf[:len(d.spiTxBuf)] + err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf) + d.nssPin.Set(true) + if err != nil { + return nil, err + } + return d.spiRxBuf[3:], nil +} + +// Set the device into sleep mode with the given configuration: 0 (no retention), 1 (ram retentation), 2 (buffer retention) or 3 (ram and buffer retention) +func (d *Device) SetSleep(sleepConfig SleepConfig) error { + if sleepConfig > (SLEEP_DATA_BUFFER_RETAIN | SLEEP_DATA_RAM_RETAIN) { + return errInvalidSleepConfig + } + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetSleep, uint8(sleepConfig)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Put device into standby mode, 0 (RC) or 1 (XOSC) +func (d *Device) SetStandby(standbyConfig StandbyConfig) error { + if standbyConfig > STANDBY_XOSC { // XOSC is the highest standby config anything higher is invalid + return errInvalidStandbyConfig + } + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetStandby, uint8(standbyConfig)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Set the device into Frequency Synthesizer mode +func (d *Device) SetFs() error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetFS) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +func checkPeriodBase(periodBase PeriodBase) error { + if periodBase > PERIOD_BASE_4_MS { // 4ms is the highest period base anything higher is invalid + return errInvalidPeriodBase + } + return nil +} + +// Sets the device in transmit mode, the IRQ status should be cleared before using this command +// timout is determined by periodBase * periodBaseCount +func (d *Device) SetTx(periodBase PeriodBase, periodBaseCount uint16) error { + err := checkPeriodBase(periodBase) + if err != nil { + return err + } + err = d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetTx, uint8(periodBase), uint8((periodBaseCount>>8)&0xFF), uint8(periodBaseCount&0xFF)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Sets the device in receive mode, the IRQ status should be cleared before using this command +// timeout is determined by periodBase * periodBaseCount +func (d *Device) SetRx(periodBase PeriodBase, periodBaseCount uint16) error { + err := checkPeriodBase(periodBase) + if err != nil { + return err + } + err = d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetRx, uint8(periodBase), uint8((periodBaseCount>>8)&0xFF), uint8(periodBaseCount&0xFF)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Sets the device in a continuous receive mode, it enters receive mode with a timeout of periodBase * rxPeriodBaseCount. +// If no packet is received it will enter sleep mode for periodBase * sleepPeriodBaseCount before re-entering receive mode. +// The loop is exited when a packet is received or the device is put into standby mode. +func (d *Device) SetRxDutyCycle(periodBase PeriodBase, rxPeriodBaseCount uint16, sleepPeriodBaseCount uint16) error { + err := checkPeriodBase(periodBase) + if err != nil { + return err + } + err = d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetRxDutyCycle, uint8(periodBase), uint8((rxPeriodBaseCount&0xFF00)>>8), uint8(rxPeriodBaseCount&0x00FF)) + d.spiTxBuf = append(d.spiTxBuf, uint8((sleepPeriodBaseCount&0xFF00)>>8), uint8(sleepPeriodBaseCount&0x00FF)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Sets the transceiver into Long Preamble mode, and can only be used with either the LoRa mode and GFSK mode +func (d *Device) SetLongPreamble(enable bool) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetLongPreamble) + if enable { + d.spiTxBuf = append(d.spiTxBuf, 1) + } else { + d.spiTxBuf = append(d.spiTxBuf, 0) + } + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Channel activity detection (CAD) is a LoRa specific mode of operation where the device searches for a LoRa signal. +// After search has completed, the device returns to STDBY_RC mode. The length of the search is configured via the SetCadParams() command. +func (d *Device) SetCAD() error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetCAD) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Test command to generate a Continuous Wave (RF tone) at a selected frequency and output power +// The device remains in Tx Continuous Wave until the host sends a mode configuration command. +func (d *Device) SetTxContinuousWave() error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetTxContinuousWave) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Test command to generate an infinite sequence of alternating ‘0’s and ‘1’s in +// GFSK modulation and symbol 0 in LoRa. The device remains in transmit until the host sends a mode configuration command. +func (d *Device) SetTxContinuousPreamble() error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetContinuousPreamble) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// This command allows the transceiver to send a packet at a user programmable time after the end of a packet reception. +// This is useful for Bluetooth Low Energy (BLE) compatibility which requires the transceiver to be able to send back a response 150µs after a packet reception. +func (d *Device) SetAutoTx(timeUs uint16) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetAutoTx, uint8((timeUs&0xFF00)>>8), uint8(timeUs&0x00FF)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Modifies the chip behavior so that the state following a Rx or Tx operation is FS and not standby. +// This allows for faster transitions between Rx and/or Tx. +func (d *Device) SetAutoFs(enable bool) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetAutoFS) + if enable { + d.spiTxBuf = append(d.spiTxBuf, 1) + } else { + d.spiTxBuf = append(d.spiTxBuf, 0) + } + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Choose between GFSK, LoRa, Ranging, FLRC or BLE packet types, this will affect the available configuration parameters and the structure of the packet +func (d *Device) SetPacketType(packetType PacketType) error { + if packetType > PACKET_TYPE_BLE { // BLE is the highest packet type anything higher is invalid. + return errInvalidPacketType + } + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetPacketType, uint8(packetType)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Get the currently configured packet type, this will be 0 (GFSK), 1 (LoRa), 2 (Ranging), 3 (FLRC) or 4 (BLE) +func (d *Device) GetPacketType() (PacketType, error) { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return 0, err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdGetPacketType, 0x00, 0x00) + d.spiRxBuf = d.spiRxBuf[:3] + err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf) + d.nssPin.Set(true) + if err != nil { + return 0, err + } + return PacketType(d.spiRxBuf[2]), nil +} + +// Set the RF frequency in Hz, must be between 2.4 GHz and 2.5 GHz +func (d *Device) SetRfFrequency(frequencyHz uint32) error { + if frequencyHz < 2400000000 { + return errFrequencyTooLow + } + if frequencyHz > 2500000000 { + return errFrequencyTooHigh + } + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + rfFrequency := uint32((uint64(frequencyHz) << 18) / 52000000) + d.spiTxBuf = append(d.spiTxBuf, cmdSetRFFrequency, uint8((rfFrequency>>16)&0xFF), uint8((rfFrequency>>8)&0xFF), uint8(rfFrequency&0xFF)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Set the output power in dBm, must be between -18 and 13 dBm, and the ramp time +func (d *Device) SetTxParams(powerdBm int8, rampTime RadioRampTime) error { + if powerdBm < -18 { + return errPowerTooLow + } + if powerdBm > 13 { + return errPowerTooHigh + } + + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + adjustedPower := uint8(powerdBm + 18) + d.spiTxBuf = append(d.spiTxBuf, cmdSetTxParams, adjustedPower, uint8(rampTime)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Set the number of symbols used for channel activity detection which determines the sensitivity of the detection. +// This is only applicable in LoRa mode. +func (d *Device) SetCadParams(cadSymbolNum uint8) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetCADParams, cadSymbolNum) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Set the base address for the internal buffer for Tx and Rx operations. +// When transmitting or receiving data is read from or written to the buffer starting at the given offset. +func (d *Device) SetBufferBaseAddress(txBase uint8, rxBase uint8) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetBufferBaseAddress, txBase, rxBase) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// The arguments to this function depend on the packet type. It is recommended to use the mode specific functions for a better experience. +// BLE & GFSK: BitrateBandwidth, ModulationIndex, ModulationShaping +// FLRC: BitrateBandwidth, CodingRate, ModulationShaping +// LoRa & Ranging: SpreadingFactor, Bandwidth, CodingRate +func (d *Device) SetModulationParams(modParam1, modParam2, modParam3 uint8) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetModulationParams, modParam1, modParam2, modParam3) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +func (d *Device) SetModulationParamsBLE(bitrateBandwidth GFSKBLEBitrateBandwidth, modulationIndex ModulationIndex, modulationShaping ModulationShaping) error { + return d.SetModulationParams(uint8(bitrateBandwidth), uint8(modulationIndex), uint8(modulationShaping)) +} + +func (d *Device) SetModulationParamsGFSK(bitrateBandwidth GFSKBLEBitrateBandwidth, modulationIndex ModulationIndex, modulationShaping ModulationShaping) error { + return d.SetModulationParams(uint8(bitrateBandwidth), uint8(modulationIndex), uint8(modulationShaping)) +} + +func (d *Device) SetModulationParamsFLRC(bitrateBandwidth FLRCBitrateBandwidth, codingRate FLRCCodingRate, modulationShaping ModulationShaping) error { + return d.SetModulationParams(uint8(bitrateBandwidth), uint8(codingRate), uint8(modulationShaping)) +} + +func (d *Device) SetModulationParamsLoRa(spreadingFactor LoRaSpreadingFactor, bandwidth LoRaBandwidth, codingRate LoRaCodingRate) error { + return d.SetModulationParams(uint8(spreadingFactor), uint8(bandwidth), uint8(codingRate)) +} + +// The arguments to this function depend on the packet type. It is recommended to use the mode specific functions for a better experience. +// GFSK & FLRC: PreambleLength, SyncWordLength, SyncWordMatch, HeaderType, PayloadLength, CrcLength, Whitening +// BLE: ConnectionState, CrcLength, BleTestPayload, Whitening +// LoRa & Ranging: PreambleLength, HeaderType, PayloadLength, CRC, InvertIQ/chirp invert +func (d *Device) SetPacketParams(param1, param2, param3, param4, param5, param6, param7 uint8) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetPacketParams, param1, param2, param3, param4, param5, param6, param7) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Set GFSK related packet parameters, this assumes the packet type is already set to GFSK. +// - payloadLength: range of 0-255 +func (d *Device) SetPacketParamsGFSK(preambleLength GFSKPreambleLength, syncWordLength GFSKSyncWordLength, syncWordMatch GFSKSyncWordMatch, headerType GFSKHeaderType, payloadLength uint8, crcLength GFSKCrcType, whitening bool) error { + var whiteningVal uint8 + if whitening { + whiteningVal = whiteningEnable + } else { + whiteningVal = whiteningDisable + } + return d.SetPacketParams(uint8(preambleLength), uint8(syncWordLength), uint8(syncWordMatch), uint8(headerType), payloadLength, uint8(crcLength), whiteningVal) +} + +// Set FLRC related packet parameters, this assumes the packet type is already set to FLRC. +// - payloadLength: range of 6-127 +func (d *Device) SetPacketParamsFLRC(preambleLength FLRCPreambleLength, syncWordLength FLRCSyncWordLength, syncWordMatch FLRCSyncWordMatch, headerType FLRCHeaderType, payloadLength uint8, crcLength FLRCCrcType) error { + if payloadLength < 6 { + return errPayloadLengthTooShort + } + if payloadLength > 127 { + return errPayloadLengthTooLong + } + return d.SetPacketParams(uint8(preambleLength), uint8(syncWordLength), uint8(syncWordMatch), uint8(headerType), payloadLength, uint8(crcLength), whiteningDisable) +} + +// Set BLE related packet parameters, this assumes the packet type is already set to BLE. +func (d *Device) SetPacketParamsBLE(connectionState BLEConnectionState, crcLength BLECrcType, bleTestPayload BLETestPayload, whitening bool) error { + var whiteningVal uint8 + if whitening { + whiteningVal = whiteningEnable + } else { + whiteningVal = whiteningDisable + } + return d.SetPacketParams(uint8(connectionState), uint8(crcLength), uint8(bleTestPayload), whiteningVal, 0, 0, 0) +} + +// Set LoRa related packet parameters, this assumes the packet type is already set to LoRa. +// - payloadLength: range of 1-255 +func (d *Device) SetPacketParamsLoRa(preambleLength uint32, headerType LoRaHeaderType, payloadLength uint8, crcType LoRaCrcType, iqType LoRaIqType) error { + if payloadLength == 0 { + return errPayloadLengthTooShort + } + exponent, mantissa := getExponentAndMantissa(preambleLength) + return d.SetPacketParams(uint8(exponent<<4)|mantissa, uint8(headerType), payloadLength, uint8(crcType), uint8(iqType), 0, 0) +} + +func getExponentAndMantissa(value uint32) (uint8, uint8) { + // pulled from RadioLib https://github.com/jgromes/RadioLib/blob/master/src/modules/SX128x/SX128x.cpp + e := uint8(1) + m := uint8(1) + len := uint32(0) + for e = uint8(1); e <= 15; e++ { + for m = uint8(1); m <= 15; m++ { + len = uint32(m) * (uint32(1 << e)) + if len >= value { + break + } + } + if len >= value { + break + } + } + + return e, m +} + +// Get information about the most recent packet received. +// Return the payload length, the offset in the buffer where the payload starts. +func (d *Device) GetRxBufferStatus() (uint8, uint8, error) { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return 0, 0, err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdGetRxBufferStatus, 0x00, 0x00, 0x00) + d.spiRxBuf = d.spiRxBuf[:4] + err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf) + d.nssPin.Set(true) + if err != nil { + return 0, 0, err + } + return d.spiRxBuf[2], d.spiRxBuf[3], nil +} + +// The return type of this function depends on the packet type. Use mode specific function for typed returns. +// BLE, GFSK & FLRC: unused, rssiSync, errors, status, sync +// LoRa & Ranging: rssiSync, SNR +func (d *Device) GetPacketStatus() (uint8, uint8, uint8, uint8, uint8, error) { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return 0, 0, 0, 0, 0, err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdGetPacketStatus, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + d.spiRxBuf = d.spiRxBuf[:7] + err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf) + d.nssPin.Set(true) + if err != nil { + return 0, 0, 0, 0, 0, err + } + return d.spiRxBuf[2], d.spiRxBuf[3], d.spiRxBuf[4], d.spiRxBuf[5], d.spiRxBuf[6], nil +} + +// Get information about the most recent GFSK packet received or transmitted: +// - RSSI of last received packet +// - packet information (each bit represents a different error or status flag) +// - whether the last packet transmission has ended +// - the sync word that was used for the last packet reception (0-3) +func (d *Device) GetPacketStatusGFSK() (float32, GFSKPacketInfo, bool, uint8, error) { + _, rssiSync, packetInfo, status, sync, err := d.GetPacketStatus() + if err != nil { + return 0, 0, false, 0, err + } + return float32(int8(rssiSync)) / 2 * -1, GFSKPacketInfo(packetInfo), status != 0, sync, nil +} + +// Get information about the most recent BLE packet received or transmitted: +// - RSSI of last received packet +// - packet information (each bit represents a different error or status flag) +// - whether the last packet transmission has ended +// - the sync word that was used for the last packet reception (0-1) +func (d *Device) GetPacketStatusBLE() (float32, BLEPacketInfo, bool, uint8, error) { + _, rssiSync, packetInfo, status, sync, err := d.GetPacketStatus() + if err != nil { + return 0, 0, false, 0, err + } + return float32(int8(rssiSync)) / 2 * -1, BLEPacketInfo(packetInfo), status != 0, sync, nil +} + +// Get information about the most recent BLE packet received or transmitted: +// - RSSI of last received packet +// - packet information (each bit represents a different error or status flag) +// - PID field of the received packet +// - NO_ACK field of the received packet +// - PID check status of the current packet +// - whether the last packet transmission has ended +// - the sync word that was used for the last packet reception (0-1) +func (d *Device) GetPacketStatusFLRC() (float32, FLRCPacketInfo, uint8, bool, bool, bool, uint8, error) { + _, rawRSSI, packetInfo, rxTxInfo, sync, err := d.GetPacketStatus() + + rxPid := (rxTxInfo & 0b11000000) >> 6 + noAck := (rxTxInfo & 0b00100000) != 0 + pidCheck := (rxTxInfo & 0b00010000) != 0 + txDone := (rxTxInfo & 0b00000001) != 0 + + if err != nil { + return 0, 0, 0, false, false, false, 0, err + } + return float32(int8(rawRSSI)) / 2 * -1, FLRCPacketInfo(packetInfo), rxPid, noAck, pidCheck, txDone, sync, nil +} + +// Get information about the most recent LoRa packet received: +// - RSSI of last received packet +// - signal-to-noise ratio (SNR) of last received packet +func (d *Device) GetPacketStatusLoRa() (float32, float32, error) { + rawRSSI, rawSnr, _, _, _, err := d.GetPacketStatus() + if err != nil { + return 0, 0, err + } + return float32(int8(rawRSSI)) / 2 * -1, float32(int8(rawSnr)) / 4, nil +} + +// Get the instantaneous RSSI value during reception of the packet +func (d *Device) GetRssiInst() (float32, error) { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return 0, err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdGetRSSIInst, 0x00, 0x00) + d.spiRxBuf = d.spiRxBuf[:3] + err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf) + d.nssPin.Set(true) + if err != nil { + return 0, err + } + return float32(int8(d.spiRxBuf[2])) / 2 * -1, nil +} + +// Configure the overall IRQ mask and the mapping of individual IRQs to the DIO1, DIO2 and DIO3 pins +func (d *Device) SetDioIrqParams(irqMask IRQMask, dio1Mask IRQMask, dio2Mask IRQMask, dio3Mask IRQMask) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetDIOIRQParams, uint8((irqMask&0xFF00)>>8), uint8(irqMask&0x00FF)) + d.spiTxBuf = append(d.spiTxBuf, uint8((dio1Mask&0xFF00)>>8), uint8(dio1Mask&0x00FF)) + d.spiTxBuf = append(d.spiTxBuf, uint8((dio2Mask&0xFF00)>>8), uint8(dio2Mask&0x00FF)) + d.spiTxBuf = append(d.spiTxBuf, uint8((dio3Mask&0xFF00)>>8), uint8(dio3Mask&0x00FF)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Get the current IRQ status. +func (d *Device) GetIrqStatus() (IRQMask, error) { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return 0, err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdGetIRQStatus, 0x00, 0x00, 0x00) + d.spiRxBuf = d.spiRxBuf[:4] + err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf) + d.nssPin.Set(true) + if err != nil { + return 0, err + } + return uint16(d.spiRxBuf[2])<<8 | uint16(d.spiRxBuf[3]), err +} + +// Clear the IRQ bits specified in the irqMask. +func (d *Device) ClearIrqStatus(irqMask IRQMask) error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdClearIRQStatus, uint8((irqMask&0xFF00)>>8), uint8(irqMask&0x00FF)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Switch between the low-dropout regulator (LDO) and the DC-DC converter for internal power regulation. +func (d *Device) SetRegulatorMode(mode RegulatorMode) error { + if mode > REGULATOR_DC_DC { // DC-DC is the highest regulator mode anything higher is invalid + return errInvalidRegulatorMode + } + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetRegulatorMode, uint8(mode)) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +} + +// Stores the present context of the radio register values to the Data RAM which will be restored when the device wakes up from sleep mode. +func (d *Device) SetSaveContext() error { + err := d.WaitWhileBusy(time.Second) + if err != nil { + return err + } + d.nssPin.Set(false) + d.spiTxBuf = d.spiTxBuf[:0] + d.spiTxBuf = append(d.spiTxBuf, cmdSetSaveContext) + err = d.spi.Tx(d.spiTxBuf, nil) + d.nssPin.Set(true) + return err +}