mirror of
https://github.com/soypat/lora.git
synced 2026-08-22 13:18:58 +00:00
@@ -22,9 +22,9 @@ type Config struct {
|
|||||||
// Must be set when working with implicit headers.
|
// Must be set when working with implicit headers.
|
||||||
MaxImplicitPayloadLength uint8
|
MaxImplicitPayloadLength uint8
|
||||||
CodingRate CodingRate
|
CodingRate CodingRate
|
||||||
SpreadFactor SpreadFactor
|
SpreadingFactor SpreadingFactor
|
||||||
SyncWord uint8
|
SyncWord uint16 // for new chips sync word is full 16 bits
|
||||||
TxPower int8 // Tx Power in dBm.
|
TxPower int8 // Tx Power in dBm.
|
||||||
CRC bool
|
CRC bool
|
||||||
// Low data rate optimisation flag. The use of this flag is mandated when
|
// Low data rate optimisation flag. The use of this flag is mandated when
|
||||||
// the symbol duration exceeds 16ms. Increases reliability at high spreading factors.
|
// the symbol duration exceeds 16ms. Increases reliability at high spreading factors.
|
||||||
@@ -34,9 +34,9 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SymbolPeriod returns the time it takes to transmit a single symbol given the
|
// SymbolPeriod returns the time it takes to transmit a single symbol given the
|
||||||
// current configuration parameters. It depends on Spread factor and Bandwidth.
|
// current configuration parameters. It depends on Spreading factor and Bandwidth.
|
||||||
func (cfg *Config) SymbolPeriod() time.Duration {
|
func (cfg *Config) SymbolPeriod() time.Duration {
|
||||||
T_s := time.Second * time.Duration(cfg.SpreadFactor.ChipsPerSymbol()) /
|
T_s := time.Second * time.Duration(cfg.SpreadingFactor.ChipsPerSymbol()) /
|
||||||
time.Duration(cfg.Bandwidth.Hertz())
|
time.Duration(cfg.Bandwidth.Hertz())
|
||||||
return T_s
|
return T_s
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ func (cfg *Config) SymbolPeriod() time.Duration {
|
|||||||
// - CRC presence (presence == longer)
|
// - CRC presence (presence == longer)
|
||||||
// - Header type (explicit == longer)
|
// - Header type (explicit == longer)
|
||||||
// - Coding rate (proportional)
|
// - Coding rate (proportional)
|
||||||
// - Spread factor (inversely proportional)
|
// - Spreading factor (inversely proportional)
|
||||||
// - Preamble length (proportional)
|
// - Preamble length (proportional)
|
||||||
// - Low data rate optimisation (presence == longer)
|
// - Low data rate optimisation (presence == longer)
|
||||||
func (cfg *Config) TimeOnAir(payloadLength int) time.Duration {
|
func (cfg *Config) TimeOnAir(payloadLength int) time.Duration {
|
||||||
@@ -58,10 +58,10 @@ func (cfg *Config) TimeOnAir(payloadLength int) time.Duration {
|
|||||||
ih := int64(cfg.HeaderType)
|
ih := int64(cfg.HeaderType)
|
||||||
ldr := int64(b2u8(cfg.LDRO))
|
ldr := int64(b2u8(cfg.LDRO))
|
||||||
cr := int64(cfg.CodingRate)
|
cr := int64(cfg.CodingRate)
|
||||||
spread := int64(cfg.SpreadFactor)
|
sf := int64(cfg.SpreadingFactor)
|
||||||
// Page 31 SX1276IMLTRT SEMTECH | Alldatasheet.
|
// Page 31 SX1276IMLTRT SEMTECH | Alldatasheet.
|
||||||
Npayload := 8*int64(payloadLength) - 4*spread + 28 + 16*crc - 20*ih
|
Npayload := 8*int64(payloadLength) - 4*sf + 28 + 16*crc - 20*ih
|
||||||
div := 4 * (spread - 2*ldr)
|
div := 4 * (sf - 2*ldr)
|
||||||
// Apply Ceil and max with minimal branching.
|
// Apply Ceil and max with minimal branching.
|
||||||
if Npayload < 0 || div <= 0 {
|
if Npayload < 0 || div <= 0 {
|
||||||
Npayload = 0
|
Npayload = 0
|
||||||
@@ -77,7 +77,7 @@ func (cfg *Config) TimeOnAir(payloadLength int) time.Duration {
|
|||||||
// time calculated.
|
// time calculated.
|
||||||
Npayload += 8 + int64(cfg.PreambleLength) + 5
|
Npayload += 8 + int64(cfg.PreambleLength) + 5
|
||||||
// Calculate LoRa Transmission Parameter Relationship page 28.
|
// Calculate LoRa Transmission Parameter Relationship page 28.
|
||||||
chipsPerSymbol := cfg.SpreadFactor.ChipsPerSymbol() // chips per symbol.
|
chipsPerSymbol := cfg.SpreadingFactor.ChipsPerSymbol() // chips per symbol.
|
||||||
return time.Second * time.Duration(Npayload*int64(chipsPerSymbol)) /
|
return time.Second * time.Duration(Npayload*int64(chipsPerSymbol)) /
|
||||||
time.Duration(cfg.Bandwidth.Hertz())
|
time.Duration(cfg.Bandwidth.Hertz())
|
||||||
}
|
}
|
||||||
@@ -110,15 +110,16 @@ const (
|
|||||||
CR4_8 CodingRate = 4
|
CR4_8 CodingRate = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// SpreadFactor defines the number of chips per symbol. Higher spread factors
|
// SpreadingFactor defines the number of chips per symbol. Higher spreading factors
|
||||||
// imply longer transmission times but more robust communications.
|
// imply longer transmission times but more robust communications.
|
||||||
// The number of chips per symbol is 2^SF, so a spread factor of 8 takes twice
|
// The number of chips per symbol is 2^SF, so a spreading factor of 8 takes twice
|
||||||
// as long to transmit a symbol as a spread factor of 7.
|
// as long to transmit a symbol as a spreading factor of 7.
|
||||||
type SpreadFactor uint8
|
type SpreadingFactor uint8
|
||||||
|
|
||||||
// Common spread factors. Decide the number of chips per symbol.
|
// Common spreading factors. Decide the number of chips per symbol.
|
||||||
const (
|
const (
|
||||||
SF6 SpreadFactor = iota + 6
|
SF5 SpreadingFactor = iota + 5
|
||||||
|
SF6
|
||||||
SF7
|
SF7
|
||||||
SF8
|
SF8
|
||||||
SF9
|
SF9
|
||||||
@@ -129,9 +130,9 @@ const (
|
|||||||
|
|
||||||
// ChipsPerSymbol returns the number of chips in a symbol. A chip is a subdivision
|
// ChipsPerSymbol returns the number of chips in a symbol. A chip is a subdivision
|
||||||
// of a symbol in the frequency domain rather than the time domain, which is why
|
// of a symbol in the frequency domain rather than the time domain, which is why
|
||||||
// the units of this value is Hz, not Duration. A chip tells tells where to start
|
// the units of this value is Hz, not Duration. A chip tells where to start
|
||||||
// the frequency sweep for a symbol.
|
// the frequency sweep for a symbol.
|
||||||
func (sf SpreadFactor) ChipsPerSymbol() int64 {
|
func (sf SpreadingFactor) ChipsPerSymbol() int64 {
|
||||||
return 1 << sf
|
return 1 << sf
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,9 +159,10 @@ const (
|
|||||||
|
|
||||||
// Common LoRa bandwidths
|
// Common LoRa bandwidths
|
||||||
const (
|
const (
|
||||||
BW125k = 125 * Kilohertz
|
BW125k = 125 * Kilohertz
|
||||||
BW250k = 250 * Kilohertz
|
BW250k = 250 * Kilohertz
|
||||||
BW500k = 500 * Kilohertz
|
BW500k = 500 * Kilohertz
|
||||||
|
BW1625k = 1625 * Kilohertz
|
||||||
)
|
)
|
||||||
|
|
||||||
// Common LoRa frequencies
|
// Common LoRa frequencies
|
||||||
@@ -168,11 +170,12 @@ const (
|
|||||||
// 433.05MHz Low limit medical, scientific and industrial band.
|
// 433.05MHz Low limit medical, scientific and industrial band.
|
||||||
Freq433_0M = 433050000 * Hertz
|
Freq433_0M = 433050000 * Hertz
|
||||||
// 434.8MHz High limit medical, scientific and industrial band.
|
// 434.8MHz High limit medical, scientific and industrial band.
|
||||||
Freq434_8M = 434790000 * Hertz
|
Freq434_8M = 434790000 * Hertz
|
||||||
Freq868_1M = 868100000 * Hertz
|
Freq868_1M = 868100000 * Hertz
|
||||||
Freq868_5M = 868500000 * Hertz
|
Freq868_5M = 868500000 * Hertz
|
||||||
Freq916_8M = 916800000 * Hertz
|
Freq916_8M = 916800000 * Hertz
|
||||||
Freq923_3M = 923300000 * Hertz
|
Freq923_3M = 923300000 * Hertz
|
||||||
|
Freq2400_0M = 2400 * Megahertz
|
||||||
)
|
)
|
||||||
|
|
||||||
// 169.4MHz radio band ([Wize]), formerly known as ERMES band. Historically used by pagers.
|
// 169.4MHz radio band ([Wize]), formerly known as ERMES band. Historically used by pagers.
|
||||||
|
|||||||
+14
-14
@@ -22,13 +22,13 @@ func TestTimeOnAir(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "LoRaWAN 240bytes", // https://www.thethingsnetwork.org/airtime-calculator
|
desc: "LoRaWAN 240bytes", // https://www.thethingsnetwork.org/airtime-calculator
|
||||||
cfg: lora.Config{
|
cfg: lora.Config{
|
||||||
Bandwidth: lora.BW125k,
|
Bandwidth: lora.BW125k,
|
||||||
Frequency: 915e6,
|
Frequency: 915e6,
|
||||||
SpreadFactor: lora.SF7,
|
SpreadingFactor: lora.SF7,
|
||||||
HeaderType: lora.HeaderExplicit,
|
HeaderType: lora.HeaderExplicit,
|
||||||
CodingRate: loraWANCodeRate,
|
CodingRate: loraWANCodeRate,
|
||||||
CRC: true,
|
CRC: true,
|
||||||
PreambleLength: loraWANPreambleLength,
|
PreambleLength: loraWANPreambleLength,
|
||||||
},
|
},
|
||||||
plen: 240,
|
plen: 240,
|
||||||
expected: 394.5e3 * time.Microsecond,
|
expected: 394.5e3 * time.Microsecond,
|
||||||
@@ -36,13 +36,13 @@ func TestTimeOnAir(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "LoRaWAN 5byte BW=500kHz", // https://www.thethingsnetwork.org/airtime-calculator
|
desc: "LoRaWAN 5byte BW=500kHz", // https://www.thethingsnetwork.org/airtime-calculator
|
||||||
cfg: lora.Config{
|
cfg: lora.Config{
|
||||||
Bandwidth: lora.BW500k,
|
Bandwidth: lora.BW500k,
|
||||||
Frequency: 915e6,
|
Frequency: 915e6,
|
||||||
SpreadFactor: lora.SF7,
|
SpreadingFactor: lora.SF7,
|
||||||
HeaderType: lora.HeaderExplicit,
|
HeaderType: lora.HeaderExplicit,
|
||||||
CodingRate: loraWANCodeRate,
|
CodingRate: loraWANCodeRate,
|
||||||
CRC: true,
|
CRC: true,
|
||||||
PreambleLength: loraWANPreambleLength,
|
PreambleLength: loraWANPreambleLength,
|
||||||
},
|
},
|
||||||
plen: 10,
|
plen: 10,
|
||||||
expected: 15.4e3 * time.Microsecond,
|
expected: 15.4e3 * time.Microsecond,
|
||||||
|
|||||||
+16
-13
@@ -81,7 +81,7 @@ type DeviceLoRa struct {
|
|||||||
func DefaultConfig(freq lora.Frequency) lora.Config {
|
func DefaultConfig(freq lora.Frequency) lora.Config {
|
||||||
return lora.Config{
|
return lora.Config{
|
||||||
Frequency: freq,
|
Frequency: freq,
|
||||||
SpreadFactor: lora.SF7,
|
SpreadingFactor: lora.SF7,
|
||||||
Bandwidth: lora.BW125k,
|
Bandwidth: lora.BW125k,
|
||||||
CodingRate: lora.CR4_5,
|
CodingRate: lora.CR4_5,
|
||||||
PreambleLength: 8,
|
PreambleLength: 8,
|
||||||
@@ -103,7 +103,7 @@ func NewLoRa(bus SPI, cs, reset PinOutput) *DeviceLoRa {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errBadSpread = errors.New("bad spread factor")
|
errBadSpreadingFactor = errors.New("bad spreading factor")
|
||||||
errSF6Implicit = errors.New("SF6 can only be used with implicit header type") // Page 30: Implicit Header Mode.
|
errSF6Implicit = errors.New("SF6 can only be used with implicit header type") // Page 30: Implicit Header Mode.
|
||||||
errPreambleTooShort = errors.New("preamble length too short")
|
errPreambleTooShort = errors.New("preamble length too short")
|
||||||
ErrNotDetected = errors.New("sx127x not detected")
|
ErrNotDetected = errors.New("sx127x not detected")
|
||||||
@@ -116,6 +116,7 @@ var (
|
|||||||
ErrCRC = errors.New("crc error")
|
ErrCRC = errors.New("crc error")
|
||||||
ErrRxTimeout = errors.New("rx timeout")
|
ErrRxTimeout = errors.New("rx timeout")
|
||||||
errImplicitPacketTooLarge = errors.New("packet length exceeds MaxImplicitPayloadLength")
|
errImplicitPacketTooLarge = errors.New("packet length exceeds MaxImplicitPayloadLength")
|
||||||
|
errSyncWordTooLarge = errors.New("sync word too long")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d *DeviceLoRa) InitLoRaMode() (err error) {
|
func (d *DeviceLoRa) InitLoRaMode() (err error) {
|
||||||
@@ -139,9 +140,9 @@ func (d *DeviceLoRa) InitLoRaMode() (err error) {
|
|||||||
|
|
||||||
func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) {
|
func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) {
|
||||||
switch {
|
switch {
|
||||||
case cfg.SpreadFactor < lora.SF6 || cfg.SpreadFactor > lora.SF12:
|
case cfg.SpreadingFactor < lora.SF6 || cfg.SpreadingFactor > lora.SF12:
|
||||||
err = errBadSpread
|
err = errBadSpreadingFactor
|
||||||
case cfg.SpreadFactor == lora.SF6 && cfg.HeaderType != lora.HeaderImplicit:
|
case cfg.SpreadingFactor == lora.SF6 && cfg.HeaderType != lora.HeaderImplicit:
|
||||||
err = errSF6Implicit
|
err = errSF6Implicit
|
||||||
case cfg.PreambleLength < 6:
|
case cfg.PreambleLength < 6:
|
||||||
err = errPreambleTooShort
|
err = errPreambleTooShort
|
||||||
@@ -156,6 +157,8 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) {
|
|||||||
err = errors.New("MaxImplicitPayloadLength parameter must be set when working with implicit headers")
|
err = errors.New("MaxImplicitPayloadLength parameter must be set when working with implicit headers")
|
||||||
case cfg.TxPower > 20 || cfg.TxPower < -4:
|
case cfg.TxPower > 20 || cfg.TxPower < -4:
|
||||||
err = errors.New("tx power not in operating range -4..20")
|
err = errors.New("tx power not in operating range -4..20")
|
||||||
|
case cfg.SyncWord > 0xff:
|
||||||
|
err = errSyncWordTooLarge
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -189,7 +192,7 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// TODO(soypat): Use default OCP?
|
// TODO(soypat): Use default OCP?
|
||||||
err = d.setSyncWord(cfg.SyncWord)
|
err = d.setSyncWord(uint8(cfg.SyncWord))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -197,7 +200,7 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = d.setSpreadFactorConsistent(cfg.SpreadFactor)
|
err = d.setSpreadingFactorConsistent(cfg.SpreadingFactor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -706,7 +709,7 @@ func (d *DeviceLoRa) ReadConfig() (cfg lora.Config, err error) {
|
|||||||
cfg.CodingRate = lora.CodingRate(cfg1>>1) & 0b111
|
cfg.CodingRate = lora.CodingRate(cfg1>>1) & 0b111
|
||||||
cfg.HeaderType = lora.HeaderType(cfg1 & 1)
|
cfg.HeaderType = lora.HeaderType(cfg1 & 1)
|
||||||
cfg2 := buf[1]
|
cfg2 := buf[1]
|
||||||
cfg.SpreadFactor = lora.SpreadFactor(cfg1 >> 4)
|
cfg.SpreadingFactor = lora.SpreadingFactor(cfg1 >> 4)
|
||||||
cfg.CRC = cfg2&0x4 != 0
|
cfg.CRC = cfg2&0x4 != 0
|
||||||
// continuousMode = cfg2&0x8 != 0
|
// continuousMode = cfg2&0x8 != 0
|
||||||
cfg.PreambleLength = binary.BigEndian.Uint16(buf[3:])
|
cfg.PreambleLength = binary.BigEndian.Uint16(buf[3:])
|
||||||
@@ -715,7 +718,7 @@ func (d *DeviceLoRa) ReadConfig() (cfg lora.Config, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
cfg.SyncWord = sync
|
cfg.SyncWord = uint16(sync)
|
||||||
// Read Frequency.
|
// Read Frequency.
|
||||||
err = d.read(regFRF_MSB, buf[:3])
|
err = d.read(regFRF_MSB, buf[:3])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -864,9 +867,9 @@ func (d *DeviceLoRa) setFrequency(freq lora.Frequency) error {
|
|||||||
return d.Write8(regFRF_LSB, freqReg[2]) // Write LSB last!
|
return d.Write8(regFRF_LSB, freqReg[2]) // Write LSB last!
|
||||||
}
|
}
|
||||||
|
|
||||||
// setSpreadFactorConsistent sets the spreading factor and closely related parameters
|
// setSpreadingFactorConsistent sets the spreading factor and closely related parameters
|
||||||
// including Low Data Rate Optimization, DetectionOptimize, and DetectionThreshold.
|
// including Low Data Rate Optimization, DetectionOptimize, and DetectionThreshold.
|
||||||
func (d *DeviceLoRa) setSpreadFactorConsistent(sf lora.SpreadFactor) (err error) {
|
func (d *DeviceLoRa) setSpreadingFactorConsistent(sf lora.SpreadingFactor) (err error) {
|
||||||
err = d.setSpreadingFactor(sf)
|
err = d.setSpreadingFactor(sf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -889,9 +892,9 @@ func (d *DeviceLoRa) setSpreadFactorConsistent(sf lora.SpreadFactor) (err error)
|
|||||||
// It does not set parameters closely associated with the spreading factor such as
|
// It does not set parameters closely associated with the spreading factor such as
|
||||||
// the Low Data Optimization, the Detection threshold, Detection Optimize and the
|
// the Low Data Optimization, the Detection threshold, Detection Optimize and the
|
||||||
// Symbol Timeout.
|
// Symbol Timeout.
|
||||||
func (d *DeviceLoRa) setSpreadingFactor(sf lora.SpreadFactor) error {
|
func (d *DeviceLoRa) setSpreadingFactor(sf lora.SpreadingFactor) error {
|
||||||
if sf < 6 || sf > 12 {
|
if sf < 6 || sf > 12 {
|
||||||
return errBadSpread
|
return errBadSpreadingFactor
|
||||||
}
|
}
|
||||||
const sfMask = 0b111 << 4
|
const sfMask = 0b111 << 4
|
||||||
return d.writeMasked8(regMODEM_CONFIG_2, sfMask, uint8(sf)<<4)
|
return d.writeMasked8(regMODEM_CONFIG_2, sfMask, uint8(sf)<<4)
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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)
|
||||||
|
cmdSetTx = uint8(0x83)
|
||||||
|
cmdSetRx = uint8(0x82)
|
||||||
|
|
||||||
|
// Radio Configuration
|
||||||
|
cmdSetPacketType = uint8(0x8A)
|
||||||
|
cmdGetPacketType = uint8(0x03)
|
||||||
|
cmdSetRFFrequency = uint8(0x86)
|
||||||
|
cmdSetTxParams = uint8(0x8E)
|
||||||
|
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)
|
||||||
|
)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package sx128x
|
||||||
|
|
||||||
|
type standbyConfig uint8
|
||||||
|
type periodBase uint8
|
||||||
|
type packetType uint8
|
||||||
|
type radioRampTime uint8
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
type irqMask uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
|
||||||
|
// StandbyConfig
|
||||||
|
standbyRC = standbyConfig(0)
|
||||||
|
standbyXOSC = standbyConfig(1)
|
||||||
|
|
||||||
|
// PeriodBase
|
||||||
|
periodBase4Ms = periodBase(3)
|
||||||
|
|
||||||
|
// PacketType
|
||||||
|
packetTypeLoRa = packetType(0x01)
|
||||||
|
packetTypeBLE = packetType(0x04)
|
||||||
|
|
||||||
|
// RampTime
|
||||||
|
radioRamp02us = radioRampTime(0x00)
|
||||||
|
radioRamp04us = radioRampTime(0x20)
|
||||||
|
radioRamp06us = radioRampTime(0x40)
|
||||||
|
radioRamp08us = radioRampTime(0x60)
|
||||||
|
radioRamp10us = radioRampTime(0x80)
|
||||||
|
radioRamp12us = radioRampTime(0xA0)
|
||||||
|
radioRamp16us = radioRampTime(0xC0)
|
||||||
|
radioRamp20us = radioRampTime(0xE0)
|
||||||
|
|
||||||
|
// LoRa Modulation Params
|
||||||
|
loraBW1600 = byte(0x0A)
|
||||||
|
loraBW800 = byte(0x18)
|
||||||
|
loraBW400 = byte(0x26)
|
||||||
|
loraBW200 = byte(0x34)
|
||||||
|
|
||||||
|
// CodingRate
|
||||||
|
loraCR4_5 = byte(0x01)
|
||||||
|
loraCR4_6 = byte(0x02)
|
||||||
|
loraCR4_7 = byte(0x03)
|
||||||
|
loraCR4_8 = byte(0x04)
|
||||||
|
loraCRLI_4_5 = byte(0x05)
|
||||||
|
loraCRLI_4_6 = byte(0x06)
|
||||||
|
loraCRLI_4_8 = byte(0x07)
|
||||||
|
|
||||||
|
// LoraPacketParams
|
||||||
|
// HeaderType
|
||||||
|
loraHeaderExplicit = byte(0x00)
|
||||||
|
loraHeaderImplicit = byte(0x80)
|
||||||
|
|
||||||
|
// CRC Type
|
||||||
|
loraCRCEnable = byte(0x20)
|
||||||
|
loraCRCDisable = byte(0x00)
|
||||||
|
|
||||||
|
// IQ Type
|
||||||
|
loraIQInverted = byte(0x00)
|
||||||
|
loraIQStandard = byte(0x40)
|
||||||
|
|
||||||
|
// IRQ masks
|
||||||
|
irqAll = irqMask(0xFFFF)
|
||||||
|
irqTxDone = irqMask(0b0000000000000001)
|
||||||
|
irqRxDone = irqMask(0b0000000000000010)
|
||||||
|
irqTimeout = irqMask(0b0100000000000000)
|
||||||
|
)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package sx128x
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBusyPinTimeout = errors.New("busy pin timeout")
|
||||||
|
errDataTooLong = errors.New("data over 256 bytes")
|
||||||
|
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")
|
||||||
|
errInvalidHeaderType = errors.New("invalid header type")
|
||||||
|
errInvalidBandwidth = errors.New("invalid bandwidth")
|
||||||
|
errInvalidCodingRate = errors.New("invalid coding rate")
|
||||||
|
errPayloadLengthTooShort = errors.New("payload length too short")
|
||||||
|
errRxTimeout = errors.New("rx timeout")
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package sx128x
|
||||||
|
|
||||||
|
const (
|
||||||
|
regSpreadingFactorAdditionalConfiguration = uint16(0x925)
|
||||||
|
regFrequencyErrorCorrection = uint16(0x93C)
|
||||||
|
regLoRaSyncWordMSB = uint16(0x944)
|
||||||
|
regLoRaSyncWordLSB = uint16(0x945)
|
||||||
|
)
|
||||||
@@ -0,0 +1,580 @@
|
|||||||
|
package sx128x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/soypat/lora"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Output func(bool)
|
||||||
|
type Input func() bool
|
||||||
|
|
||||||
|
type SPI interface {
|
||||||
|
Transfer(w byte) (byte, error)
|
||||||
|
Tx(writeBuffer, readBuffer []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceLoRa struct {
|
||||||
|
spi SPI
|
||||||
|
cs Output
|
||||||
|
rst Output
|
||||||
|
busy Input
|
||||||
|
dio1 Input
|
||||||
|
spiTxBuf []byte
|
||||||
|
spiRxBuf []byte
|
||||||
|
config lora.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultConfig(freq lora.Frequency) lora.Config {
|
||||||
|
return lora.Config{
|
||||||
|
Frequency: freq,
|
||||||
|
SpreadingFactor: lora.SF9,
|
||||||
|
Bandwidth: lora.BW1625k,
|
||||||
|
CodingRate: lora.CR4_5,
|
||||||
|
PreambleLength: 12,
|
||||||
|
HeaderType: lora.HeaderExplicit,
|
||||||
|
MaxImplicitPayloadLength: 0, // No need to be set when working with explicit headers.
|
||||||
|
CRC: true,
|
||||||
|
SyncWord: 0x1424,
|
||||||
|
TxPower: 2, // Low power by default.
|
||||||
|
LDRO: false, // not available on sx128x
|
||||||
|
IQInversion: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoRa(spi SPI, cs Output, rst Output, busy Input, dio1 Input) *DeviceLoRa {
|
||||||
|
return &DeviceLoRa{
|
||||||
|
spi: spi,
|
||||||
|
cs: cs,
|
||||||
|
rst: rst,
|
||||||
|
busy: busy,
|
||||||
|
dio1: dio1,
|
||||||
|
spiTxBuf: make([]byte, 256), // TODO: optimize buffer size
|
||||||
|
spiRxBuf: make([]byte, 256),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceLoRa) Configure(config lora.Config) error {
|
||||||
|
switch {
|
||||||
|
case config.Frequency < 2400*lora.Megahertz:
|
||||||
|
return errFrequencyTooLow
|
||||||
|
case config.Frequency > 2500*lora.Megahertz:
|
||||||
|
return errFrequencyTooHigh
|
||||||
|
case config.TxPower < -18:
|
||||||
|
return errPowerTooLow
|
||||||
|
case config.TxPower > 13:
|
||||||
|
return errPowerTooHigh
|
||||||
|
case config.HeaderType != lora.HeaderExplicit && config.HeaderType != lora.HeaderImplicit:
|
||||||
|
return errInvalidHeaderType
|
||||||
|
case config.Bandwidth != lora.BW1625k:
|
||||||
|
return errInvalidBandwidth
|
||||||
|
case config.CodingRate != lora.CR4_5 && config.CodingRate != lora.CR4_6 && config.CodingRate != lora.CR4_7 && config.CodingRate != lora.CR4_8:
|
||||||
|
return errInvalidCodingRate
|
||||||
|
}
|
||||||
|
d.Reset()
|
||||||
|
d.config = config
|
||||||
|
// Switch to standby prior to configuration changes
|
||||||
|
err := d.setStandby(standbyRC)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Clear errors, disable radio interrupts for the moment
|
||||||
|
err = d.setPacketType(packetTypeLoRa)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = d.setRfFrequency(config.Frequency)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = d.setModulationParamsLoRa(config.SpreadingFactor, config.Bandwidth, config.CodingRate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// special register setting depending on spreading factor chosen
|
||||||
|
switch config.SpreadingFactor {
|
||||||
|
case lora.SF5, lora.SF6:
|
||||||
|
d.writeRegister(regSpreadingFactorAdditionalConfiguration, []byte{0x1E})
|
||||||
|
case lora.SF7, lora.SF8:
|
||||||
|
d.writeRegister(regSpreadingFactorAdditionalConfiguration, []byte{0x37})
|
||||||
|
default:
|
||||||
|
d.writeRegister(regSpreadingFactorAdditionalConfiguration, []byte{0x32})
|
||||||
|
}
|
||||||
|
d.writeRegister(regFrequencyErrorCorrection, []byte{0x01})
|
||||||
|
|
||||||
|
// TODO(jwetzell): hardcoded radio ramp
|
||||||
|
err = d.setTxParams(config.TxPower, radioRamp02us)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = d.setPacketParamsLoRa(uint32(config.PreambleLength), config.HeaderType, 0xFF, config.CRC, config.IQInversion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var syncWord [2]uint8
|
||||||
|
syncWord[0] = uint8(config.SyncWord >> 8)
|
||||||
|
syncWord[1] = uint8(config.SyncWord & 0x00FF)
|
||||||
|
|
||||||
|
err = d.writeRegister(regLoRaSyncWordMSB, syncWord[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceLoRa) Tx(data []byte) error {
|
||||||
|
if len(data) > 255 {
|
||||||
|
return errors.New("data length exceeds maximum of 255 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jwetzell): check chip status prior to setting Rx mode and return error if not ready
|
||||||
|
d.setStandby(standbyRC)
|
||||||
|
d.setPacketParamsLoRa(uint32(d.config.PreambleLength), d.config.HeaderType, uint8(len(data)&0xFF), d.config.CRC, d.config.IQInversion)
|
||||||
|
d.setBufferBaseAddress(0, 0)
|
||||||
|
d.writeBuffer(0, data)
|
||||||
|
d.setDioIrqParams(irqTxDone|irqTimeout, irqTxDone|irqTimeout, 0x00, 0x00)
|
||||||
|
d.clearIrqStatus(irqAll)
|
||||||
|
d.setTx(periodBase4Ms, 250) // fixed timeout for now
|
||||||
|
for {
|
||||||
|
if d.dio1() {
|
||||||
|
irqStatus, err := d.getIrqStatus()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if irqStatus&irqTimeout != 0 {
|
||||||
|
return errRxTimeout
|
||||||
|
}
|
||||||
|
if irqStatus&irqTxDone != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceLoRa) RxSingle(dst []byte) (uint8, error) {
|
||||||
|
if len(dst) < 255 {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
// TODO(jwetzell): check chip status prior to setting Rx mode and return error if not ready
|
||||||
|
d.setStandby(standbyRC)
|
||||||
|
d.setDioIrqParams(irqRxDone|irqTimeout, irqRxDone|irqTimeout, 0x00, 0x00)
|
||||||
|
d.setBufferBaseAddress(0, 0)
|
||||||
|
d.clearIrqStatus(irqAll)
|
||||||
|
d.setRx(periodBase4Ms, 250) // fixed timeout for now
|
||||||
|
for {
|
||||||
|
if d.dio1() {
|
||||||
|
irqStatus, err := d.getIrqStatus()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if irqStatus&irqTimeout != 0 {
|
||||||
|
return 0, errRxTimeout
|
||||||
|
}
|
||||||
|
if irqStatus&irqRxDone != 0 {
|
||||||
|
payloadLength, offset, err := d.getRxBufferStatus()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
data, err := d.readBuffer(offset, payloadLength)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
copy(dst, data)
|
||||||
|
return payloadLength, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceLoRa) Reset() {
|
||||||
|
d.rst(true)
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
d.rst(false)
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
d.rst(true)
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
d.cs(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceLoRa) 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.busy() {
|
||||||
|
if time.Since(now) > timeout {
|
||||||
|
return ErrBusyPinTimeout
|
||||||
|
}
|
||||||
|
runtime.Gosched()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceLoRa) writeRegister(addr uint16, data []byte) error {
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(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.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceLoRa) readRegister(addr uint16) (uint8, error) {
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
d.cs(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.cs(true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return d.spiRxBuf[4], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceLoRa) writeBuffer(offset uint8, data []byte) error {
|
||||||
|
if len(data) > 256 {
|
||||||
|
return errDataTooLong
|
||||||
|
}
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(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.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data from the payload buffer starting at the given offset with the given length
|
||||||
|
func (d *DeviceLoRa) readBuffer(offset uint8, length uint8) ([]byte, error) {
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d.cs(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.cs(true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.spiRxBuf[3 : 3+length], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put device into standby mode, 0 (RC) or 1 (XOSC)
|
||||||
|
func (d *DeviceLoRa) setStandby(standbyConfig standbyConfig) error {
|
||||||
|
if standbyConfig > standbyXOSC { // XOSC is the highest standby config anything higher is invalid
|
||||||
|
return errInvalidStandbyConfig
|
||||||
|
}
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(false)
|
||||||
|
d.spiTxBuf = d.spiTxBuf[:0]
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, cmdSetStandby, uint8(standbyConfig))
|
||||||
|
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||||
|
d.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPeriodBase(periodBase periodBase) error {
|
||||||
|
if periodBase > periodBase4Ms { // 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 *DeviceLoRa) 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.cs(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.cs(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 *DeviceLoRa) 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.cs(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.cs(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 *DeviceLoRa) setPacketType(packetType packetType) error {
|
||||||
|
if packetType > packetTypeBLE { // BLE is the highest packet type anything higher is invalid.
|
||||||
|
return errInvalidPacketType
|
||||||
|
}
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(false)
|
||||||
|
d.spiTxBuf = d.spiTxBuf[:0]
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, cmdSetPacketType, uint8(packetType))
|
||||||
|
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||||
|
d.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the RF frequency in Hz, must be between 2.4 GHz and 2.5 GHz
|
||||||
|
func (d *DeviceLoRa) setRfFrequency(frequencyHz lora.Frequency) error {
|
||||||
|
if frequencyHz < 2400000000 {
|
||||||
|
return errFrequencyTooLow
|
||||||
|
}
|
||||||
|
if frequencyHz > 2500000000 {
|
||||||
|
return errFrequencyTooHigh
|
||||||
|
}
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(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.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the output power in dBm, must be between -18 and 13 dBm, and the ramp time
|
||||||
|
func (d *DeviceLoRa) 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.cs(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.cs(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 *DeviceLoRa) setBufferBaseAddress(txBase uint8, rxBase uint8) error {
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(false)
|
||||||
|
d.spiTxBuf = d.spiTxBuf[:0]
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, cmdSetBufferBaseAddress, txBase, rxBase)
|
||||||
|
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||||
|
d.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeviceLoRa) setModulationParamsLoRa(spreadingFactor lora.SpreadingFactor, bandwidth lora.Frequency, codingRate lora.CodingRate) error {
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(false)
|
||||||
|
d.spiTxBuf = d.spiTxBuf[:0]
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, cmdSetModulationParams) //, uint8(spreadingFactor), uint8(bandwidth), uint8(codingRate))
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, uint8(spreadingFactor<<4))
|
||||||
|
switch bandwidth {
|
||||||
|
case lora.BW1625k:
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraBW1600)
|
||||||
|
// TODO(jwetzell): support for other bandwidths
|
||||||
|
default:
|
||||||
|
return errInvalidBandwidth
|
||||||
|
}
|
||||||
|
switch codingRate {
|
||||||
|
case lora.CR4_5:
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraCR4_5)
|
||||||
|
case lora.CR4_6:
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraCR4_6)
|
||||||
|
case lora.CR4_7:
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraCR4_7)
|
||||||
|
case lora.CR4_8:
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraCR4_8)
|
||||||
|
// TODO(jwetzell): support for LI coding rates
|
||||||
|
default:
|
||||||
|
return errInvalidCodingRate
|
||||||
|
}
|
||||||
|
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||||
|
d.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set LoRa related packet parameters, this assumes the packet type is already set to LoRa.
|
||||||
|
// - payloadLength: range of 1-255
|
||||||
|
func (d *DeviceLoRa) setPacketParamsLoRa(preambleLength uint32, headerType lora.HeaderType, payloadLength uint8, crcEnabled bool, iqInversion bool) error {
|
||||||
|
if payloadLength == 0 {
|
||||||
|
return errPayloadLengthTooShort
|
||||||
|
}
|
||||||
|
exponent, mantissa := getExponentAndMantissa(preambleLength)
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(false)
|
||||||
|
d.spiTxBuf = d.spiTxBuf[:0]
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, cmdSetPacketParams, uint8(exponent<<4)|mantissa)
|
||||||
|
switch headerType {
|
||||||
|
case lora.HeaderExplicit:
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraHeaderExplicit)
|
||||||
|
case lora.HeaderImplicit:
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraHeaderImplicit)
|
||||||
|
default:
|
||||||
|
return errInvalidHeaderType
|
||||||
|
}
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, payloadLength)
|
||||||
|
if crcEnabled {
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraCRCEnable)
|
||||||
|
} else {
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraCRCDisable)
|
||||||
|
}
|
||||||
|
if iqInversion {
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraIQInverted)
|
||||||
|
} else {
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, loraIQStandard)
|
||||||
|
}
|
||||||
|
d.spiTxBuf = append(d.spiTxBuf, 0, 0) // unused parameters
|
||||||
|
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||||
|
d.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExponentAndMantissa(value uint32) (uint8, uint8) {
|
||||||
|
// pulled from RadioLib https://github.com/jgromes/RadioLib/blob/master/src/modules/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 *DeviceLoRa) getRxBufferStatus() (uint8, uint8, error) {
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
d.cs(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.cs(true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return d.spiRxBuf[2], d.spiRxBuf[3], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the overall IRQ mask and the mapping of individual IRQs to the DIO1, DIO2 and DIO3 pins
|
||||||
|
func (d *DeviceLoRa) setDioIrqParams(irqMask irqMask, dio1Mask irqMask, dio2Mask irqMask, dio3Mask irqMask) error {
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(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.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current IRQ status.
|
||||||
|
func (d *DeviceLoRa) getIrqStatus() (irqMask, error) {
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
d.cs(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.cs(true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return irqMask(uint16(d.spiRxBuf[2])<<8 | uint16(d.spiRxBuf[3])), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the IRQ bits specified in the irqMask.
|
||||||
|
func (d *DeviceLoRa) clearIrqStatus(irqMask irqMask) error {
|
||||||
|
err := d.waitWhileBusy(time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.cs(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.cs(true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user