add SPI driver for semtech sx128x chips (#864)

* add SPI driver for semtech sx128x chips

Co-authored-by: Copilot <copilot@github.com>

* handle busy loop better

* switch to time based busy timeout

Co-authored-by: Copilot <copilot@github.com>

* comment functions

* start on using types for function inputs

* use types where applicable and align with datasheet more

Co-authored-by: Copilot <copilot@github.com>

* work on exporting less constants

* only export "actionable" errors

* combine identical constants

* add crude lora rx and tx examples

* change from type aliases to local types

---------

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Joel Wetzell
2026-05-04 07:42:03 -05:00
committed by GitHub
parent 8f372935ac
commit 62663c1832
8 changed files with 1473 additions and 0 deletions
+106
View File
@@ -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")
}
+96
View File
@@ -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
}
+7
View File
@@ -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)
+52
View File
@@ -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)
)
+356
View File
@@ -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)
)
+19
View File
@@ -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")
)
+69
View File
@@ -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)
)
+768
View File
@@ -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 0s and 1s 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
}