From 373412180fcb38c4be0faa00cf68c41c9c036225 Mon Sep 17 00:00:00 2001 From: soypat Date: Sat, 20 May 2023 20:24:21 -0300 Subject: [PATCH] add lots of functionality and fix bugs --- lora.go | 19 ++- sx127x/registers.go | 53 ++++---- sx127x/sx127x.go | 306 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 336 insertions(+), 42 deletions(-) diff --git a/lora.go b/lora.go index 7f54dcf..0d0afa1 100644 --- a/lora.go +++ b/lora.go @@ -21,8 +21,9 @@ type Config struct { CRC bool // Low data rate optimisation flag. The use of this flag is mandated when // the symbol duration exceeds 16ms. Increases reliability at high spreading factors. - LDRO bool - IQMode uint8 + LDRO bool + // IQInversion configures I and Q signal inversion. + IQInversion bool } func (cfg *Config) TimeOnAir(payloadLength int) time.Duration { @@ -48,10 +49,14 @@ func (cfg *Config) TimeOnAir(payloadLength int) time.Duration { Npayload++ Npayload *= (cr + 4) } - Npayload += 8 + int64(cfg.PreambleLength) + 5 // Says 4.25 in manual but we round up. + // Says 4.25 in manual but we round up to 5. This means we'll overestimate the + // time calculated. + Npayload += 8 + int64(cfg.PreambleLength) + 5 + // base units for time calculation. A higher number means more resolution. + const baseUnitOfTime = time.Microsecond // Calculate LoRa Transmission Parameter Relationship page 28. - Ts_us := 1000_000 * cfg.SpreadFactor.ChipsPerSymbol() / cfg.Bandwidth.Hertz() - return time.Microsecond * (time.Duration(Npayload * Ts_us)) + Ts_us := int64(baseUnitOfTime) * cfg.SpreadFactor.ChipsPerSymbol() / cfg.Bandwidth.Hertz() + return baseUnitOfTime * time.Duration(Npayload*Ts_us) } type HeaderType uint8 @@ -64,9 +69,13 @@ const ( type CodingRate uint8 const ( + // 4/5 coding rate. CR4_5 CodingRate = 1 + // 4/6 coding rate. CR4_6 CodingRate = 2 + // 4/7 coding rate. CR4_7 CodingRate = 3 + // 4/8 coding rate. CR4_8 CodingRate = 4 ) diff --git a/sx127x/registers.go b/sx127x/registers.go index b72495c..d123c41 100644 --- a/sx127x/registers.go +++ b/sx127x/registers.go @@ -77,9 +77,9 @@ const ( // SX127X_PAYLOAD_LENGTH uint8 = 0x40 // Low Noise Amp - lnaMAX_GAIN uint8 = 0x23 - lnaOFF_GAIN uint8 = 0x00 - lnaLOW_GAIN uint8 = 0x20 + lnaMAX_GAIN uint8 = 0b001 + lnaOFF_GAIN uint8 = 0b111 + lnaLOW_GAIN uint8 = 0b110 // Bandwidth bw7_8 uint8 = 0x00 @@ -100,32 +100,37 @@ const ( privateSyncword = 0x14 ) +var bandwidths = [bw500_0 + 1]lora.Frequency{ + bw7_8: 7.8e3 * lora.Hertz, + bw10_4: 10.4e3 * lora.Hertz, + bw15_6: 15.6e3 * lora.Hertz, + bw20_8: 20.8e3 * lora.Hertz, + bw31_25: 31.25e3 * lora.Hertz, + bw41_7: 41.7e3 * lora.Hertz, + bw62_5: 62.5e3 * lora.Hertz, + bw125_0: 125e3 * lora.Hertz, + bw250_0: 250e3 * lora.Hertz, + bw500_0: 500e3 * lora.Hertz, +} + func bwReg(bandwidth lora.Frequency) (value byte) { - switch { - case bandwidth <= 7.8e3*lora.Hertz: - value = bw7_8 - case bandwidth <= 10.4e3*lora.Hertz: - value = bw10_4 - case bandwidth <= 15.6e3*lora.Hertz: - value = bw15_6 - case bandwidth <= 20.8e3*lora.Hertz: - value = bw20_8 - case bandwidth <= 31.25e3*lora.Hertz: - value = bw31_25 - case bandwidth <= 41.7e3*lora.Hertz: - value = bw41_7 - case bandwidth <= 62.5e3*lora.Hertz: - value = bw62_5 - case bandwidth <= 125*lora.KiloHertz: - value = bw125_0 - case bandwidth <= 250e3*lora.KiloHertz: - value = bw250_0 - default: - value = bw500_0 + value = bw7_8 + for i := byte(len(bandwidths) - 1); i >= 0; i-- { + if bandwidth >= bandwidths[i] { + value = i + break + } } return value } +func reg2Bw(bwByte byte) (value lora.Frequency) { + if bwByte > byte(len(bandwidths)-1) { + return 0 + } + return bandwidths[bwByte] +} + // Operation modes. const ( opmMASK uint8 = 0x07 diff --git a/sx127x/sx127x.go b/sx127x/sx127x.go index 6d5b259..47cd1b5 100644 --- a/sx127x/sx127x.go +++ b/sx127x/sx127x.go @@ -1,8 +1,51 @@ +/* +package sx127x implements a driver for the SX127x LoRa tranceiver family. +It is a low-level driver that exposes the device's registers and their + +The SX127x family includes the SX1276, SX1277, SX1278 and SX1279. The +differences between them are the supported frequency bands and the +maximum output power. + +# FIFO and packet handling + +The SX127x has a 256-byte FIFO buffer that is accesible via the SPI interface. +The FIFO is shared by the transmitter and the receiver but can be split via +the FifoTxBaseAddr and FifoRxBaseAddr registers. + +What follows is a diagram of the FIFO buffer and the data pointers: + + +-------------------+ + | | + | Unused | + | | + +-------------------+ <-- RxByteAddr (address of last byte received) + | | ^ + | Packet N | | FifoRxBytesUp (Only for ) + | | | + +-------------------+ <-- RegFifoRxCurrentAddr (address of start of last packet received) + | Packet N-1 | + +-------------------+ + | ... | <-- FifoAddrPtr (SPI read/write pointer) + +-------------------+ + | Packet 0 | + +-------------------+ <-- FifoRxBaseAddr + | Unused | + +-------------------+ + | | ^ + | Packet to | | PayloadLength + | Transmit | | + +-------------------+ <-- RegFifoTxCurrentAddr + | Unused | + +-------------------+ + +The FIFO is accessible through the SPI +*/ package sx127x import ( "encoding/binary" "errors" + "io" "time" "github.com/soypat/lora" @@ -10,9 +53,12 @@ import ( // TODO: page 82: To minimize the current consumption of the SX1276/77/78/79, please ensure that the CLKOUT signal is disabled when not required. +// PinOutput is a function that sets the logic-level of a pin to high (true) +// or low (false). It is used to abstract a GPIO pin interface. type PinOutput func(level bool) type SPI interface { + Transfer(w byte) (byte, error) Tx(writeBuffer, readBuffer []byte) error } @@ -27,22 +73,59 @@ func NewLoRa(bus SPI, cs, reset PinOutput) *DeviceLoRa { return &d } -func (d *DeviceLoRa) Configure(cfg lora.Config) error { +var ( + errBadSpread = errors.New("bad spread factor") + errSF6Implicit = errors.New("SF6 can only be used with implicit header type") // Page 30: Implicit Header Mode. + errPreambleTooShort = errors.New("preamble length too short") + ErrNotDetected = errors.New("sx127x not detected") + errBadMode = errors.New("bad mode: sx127x in FSK/OOK mode, not LoRa or viceversa") + errBadCodingRate = errors.New("bad coding rate") + errUnsupportedBandwidth = errors.New("bandwidth too high for frequency around 169MHz") +) + +func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) { switch { case cfg.SpreadFactor < lora.SF6 || cfg.SpreadFactor > lora.SF12: - return errors.New("bad spread factor") + err = errBadSpread case cfg.SpreadFactor == lora.SF6 && cfg.HeaderType != lora.HeaderImplicit: - return errors.New("SF6 can only be used with implicit header type") // Page 30: Implicit Header Mode. + err = errSF6Implicit case cfg.PreambleLength < 6: - return errors.New("preamble length too short") + err = errPreambleTooShort + case cfg.CodingRate < lora.CR4_5 || cfg.CodingRate > lora.CR4_8: + err = errBadCodingRate + case cfg.Frequency < 175*lora.MegaHertz && cfg.Bandwidth > 125*lora.KiloHertz: + err = errUnsupportedBandwidth + } + if err != nil { + return err } d.Reset() // We need to be in sleep mode to set LoRa mode if in FSK/OOK. d.write8(regOP_MODE, opmSLEEP) // No need to check error, do it in SetOpmode. if !d.IsConnected() { - return errors.New("sx127x not detected") + return ErrNotDetected } - err := d.SetOpMode(OpSleep) + err = d.SetOpMode(OpSleep) + if err != nil { + return err + } + err = d.setFrequency(cfg.Frequency) + if err != nil { + return err + } + err = d.setBandwidth(cfg.Bandwidth) + if err != nil { + return err + } + err = d.enableCRC(cfg.CRC) + if err != nil { + return err + } + err = d.EnableAutoAGC(true) + if err != nil { + return err + } + err = d.setPreambleLength(cfg.PreambleLength) if err != nil { return err } @@ -50,6 +133,31 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) error { if err != nil { return err } + err = d.setSyncWord(cfg.SyncWord) + if err != nil { + return err + } + err = d.setCodingRate(cfg.CodingRate) + if err != nil { + return err + } + err = d.setSpreadFactorConsistent(cfg.SpreadFactor) + if err != nil { + return err + } + err = d.enableImplicitHeaderMode(cfg.HeaderType == lora.HeaderImplicit) + if err != nil { + return err + } + err = d.enableIQInversion(cfg.IQInversion) + if err != nil { + return err + } + err = d.enableTxContinuousMode(false) // TODO: enable or disable? + if err != nil { + return err + } + d.setHopPeriod(0) return nil } @@ -78,14 +186,82 @@ func (d *DeviceLoRa) GetOpMode() (OpMode, error) { if err != nil { return invalidOpMode, err } - if got&byte(opLoRaBit) == 0 { + if got&byte(opLoRaBit) == 0 || // LongRangeMode bit influences operation. + got&(1<<6) != 0 { // AccessSharedReg bit allows access to FSK registers in LoRa mode, should not be set. // if the LoRa mode bit is not set, which would mean the device is in // FSK/OOK mode or disconnected. - return invalidOpMode, errors.New("device in FSK/OOK mode") + return invalidOpMode, errBadMode } return OpMode(got & opmMASK), nil } +// SetLNAGain sets the Low Noise amplifier gain with a value between 0 and 6 +// where 0 is Off and 6 is the maximum gain. +func (d *DeviceLoRa) SetLNAGain(gain uint8) error { + if gain > 6 { + return errors.New("gain must be between 0 and 6") + } + const lnaMask = 0b111 << 5 + gain = 0b111 - gain // invert gain value to reflect the fact that 0 is max gain. + return d.writeMasked8(regLNA, lnaMask, gain<<5) +} + +// setBandwidth sets the bandwidth of the LoRa modulation. +func (d *DeviceLoRa) setBandwidth(bw lora.Frequency) error { + const bwMask = 0b1111 << 4 + bwByte := bwReg(bw) + return d.writeMasked8(regMODEM_CONFIG_1, bwMask, bwByte<<4) +} + +// enableImplicitHeaderMode enables implicit header mode (instead of explicit). +func (d *DeviceLoRa) enableImplicitHeaderMode(enable bool) error { + return d.writeMasked8(regMODEM_CONFIG_1, 1, b2u8(enable)) +} + +// enableIQInversion inverts LoRa I and Q signals when set to true. +func (d *DeviceLoRa) enableIQInversion(enable bool) error { + const iqMask = 1 << 6 + return d.writeMasked8(regINVERTIQ, iqMask, b2u8(enable)<<6) +} + +// ReadConfig reads the configuration parameters from the device and returns +// the corresponding lora.Config for the current device configuration. +// Some lora.Config parameters are not set such as IQ, LDR, and Tx power. +func (d *DeviceLoRa) ReadConfig() (cfg lora.Config, err error) { + var buf [5]byte + err = d.read(regMODEM_CONFIG_1, buf[:5]) + if err != nil { + return cfg, err + } + cfg1 := buf[0] + cfg.Bandwidth = reg2Bw(cfg1 >> 4) + cfg.CodingRate = lora.CodingRate(cfg1>>1) & 0b111 + cfg.HeaderType = lora.HeaderType(cfg1 & 1) + cfg2 := buf[1] + cfg.SpreadFactor = lora.SpreadFactor(cfg1 >> 4) + cfg.CRC = cfg2&0x4 != 0 + // continuousMode = cfg2&0x8 != 0 + cfg.PreambleLength = binary.BigEndian.Uint16(buf[3:]) + // Read sync word. + sync, err := d.read8(regSYNC_WORD) + if err != nil { + return cfg, err + } + cfg.SyncWord = sync + // Read Frequency. + err = d.read(regFRF_MSB, buf[:3]) + freq := uint64(buf[0])<<16 | uint64(buf[1])<<8 | uint64(buf[2]) + cfg.Frequency = lora.Frequency((freq * 15625) >> 8) + // Read IQ inversion. + iq, err := d.read8(regINVERTIQ) + if err != nil { + return cfg, err + } + cfg.IQInversion = iq&(1<<6) != 0 + + return cfg, nil +} + // setPreambleLength defines number of preamble func (d *DeviceLoRa) setPreambleLength(pLen uint16) error { var buf [2]byte @@ -94,9 +270,8 @@ func (d *DeviceLoRa) setPreambleLength(pLen uint16) error { return d.write8(regPREAMBLE_LSB, buf[1]) } -// SetCrc Enable CRC generation and check on payload. -func (d *DeviceLoRa) enableCRC(enable bool) error { - return d.writeMasked8(regMODEM_CONFIG_2, 1<<2, b2u8(enable)<<2) +func (d *DeviceLoRa) enableTxContinuousMode(enable bool) error { + return d.writeMasked8(regMODEM_CONFIG_2, 1<<3, b2u8(enable)<<3) } // setOCP defines Overload Current Protection configuration. It receives @@ -139,8 +314,8 @@ func (d *DeviceLoRa) setTxPower(txPow int8) error { return d.write8(regOCP, 0) // TODO: Disable OCP? } -// setFrequency sets the radio frequency. -func (d *DeviceLoRa) setFrequency(freq uint32) error { +// setFrequency sets the center radio frequency. +func (d *DeviceLoRa) setFrequency(freq lora.Frequency) error { var freqReg [3]byte frf := freq / fSTEP // Page 82, 5.3.3 PLL. freqReg[0] = byte(frf >> 16) @@ -153,8 +328,98 @@ func (d *DeviceLoRa) setFrequency(freq uint32) error { return d.write8(regFRF_LSB, freqReg[2]) // Write LSB last! } +// setSpreadFactorConsistent sets the spreading factor and closely related parameters +// including Low Data Rate Optimization, DetectionOptimize, and DetectionThreshold. +func (d *DeviceLoRa) setSpreadFactorConsistent(sf lora.SpreadFactor) (err error) { + err = d.setSpreadingFactor(sf) + if err != nil { + return err + } + isSF6 := sf == lora.SF6 + err = d.enableLowDataRateOptimization(isSF6) + if err != nil { + return err + } + // Set DetectionOptimize to 0x05 for SF6 and to 0x03 otherwise (SF7 to SF12). + err = d.writeMasked8(regDETECTION_OPTIMIZE, 0b111, 1|(0b10< 12 { + return errBadSpread + } + const sfMask = 0b111 << 4 + return d.writeMasked8(regMODEM_CONFIG_2, sfMask, uint8(sf)<<4) +} + +func (d *DeviceLoRa) setSyncWord(sync byte) error { + return d.write8(regSYNC_WORD, sync) +} + +// EnableAutoAGC enables/disables Automatic Gain Control. This means the value set +// by SetLNAGain will be ignored. Set to false to use the value set by SetLNAGain. +func (d *DeviceLoRa) EnableAutoAGC(b bool) error { + const agcMask = 1 << 2 + return d.writeMasked8(regMODEM_CONFIG_3, agcMask, b2u8(b)<<2) +} + +// enableLowDataRateOptimization enables/disables Low Data Rate Optimization, a +// feature which is mandated when symbol length exceeds 16ms. +func (d *DeviceLoRa) enableLowDataRateOptimization(b bool) error { + const ldoMask = 1 << 3 + return d.writeMasked8(regMODEM_CONFIG_3, ldoMask, b2u8(b)<<3) +} + +// enableLowFrequencyMode enables/disables access to LowFrequencyMode registers. +func (d *DeviceLoRa) enableLowFrequencyMode(b bool) error { + const lowFreqMask = 1 << 3 + return d.writeMasked8(regOP_MODE, lowFreqMask, b2u8(b)<<3) +} + +// enableCRC enables/disables CRC generation and checking. +func (d *DeviceLoRa) enableCRC(b bool) error { + const crcMask = 1 << 2 + return d.writeMasked8(regMODEM_CONFIG_2, crcMask, b2u8(b)<<2) +} + +// setTimeoutInSymbols sets the timeout in symbols. The value must be between 0 and 1023. +// The timeout is used to stop reception automatically. The equation is: +// +// Timeout = timeoutSymbols * Ts (where Ts is the symbol period) +func (d *DeviceLoRa) setTimeoutInSymbols(symbTimeout uint16) (err error) { + if symbTimeout > 0x3FF { + return errors.New("timeout value too large") + } + err = d.writeMasked8(regMODEM_CONFIG_2, 0b11, byte(symbTimeout>>8)) + if err != nil { + return err + } + return d.write8(regSYMB_TIMEOUT_LSB, byte(symbTimeout)) +} + +// setCodingRate sets the error coding rate. The value must be between 4/5 and 4/8. +func (d *DeviceLoRa) setCodingRate(cr lora.CodingRate) error { + if cr < lora.CR4_5 || cr > lora.CR4_8 { + return errBadCodingRate + } + return d.writeMasked8(regMODEM_CONFIG_1, 0b111<<1, uint8(cr)<<1) +} + +// setHopPeriod sets number of symbol periods between frequency hops. (0 = disabled). +func (d *DeviceLoRa) setHopPeriod(val uint8) error { return d.write8(regHOP_PERIOD, val) } + func (d *DeviceLoRa) writeMasked8(addr uint8, mask, value byte) error { if value != 0 && value&^mask != 0 { + println("value", value, "mask", mask) panic("misuse of writeMasked8") // Bug in this package if hit. } existing, err := d.read8(addr) @@ -211,3 +476,18 @@ func b2u8(b bool) uint8 { } return 0 } + +func (d *DeviceLoRa) read(addr uint8, buf []byte) error { + if len(buf) <= 1 { + return io.ErrShortBuffer + } + d.csEnable(true) + _, err := d.bus.Transfer(addr) + if err != nil { + d.csEnable(false) + return err + } + err = d.bus.Tx(nil, buf) + d.csEnable(false) + return err +}