diff --git a/lora.go b/lora.go index da2f4e1..a361346 100644 --- a/lora.go +++ b/lora.go @@ -22,9 +22,9 @@ type Config struct { // Must be set when working with implicit headers. MaxImplicitPayloadLength uint8 CodingRate CodingRate - SpreadFactor SpreadFactor - SyncWord uint8 - TxPower int8 // Tx Power in dBm. + SpreadingFactor SpreadingFactor + SyncWord uint16 // for new chips sync word is full 16 bits + TxPower int8 // Tx Power in dBm. 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. @@ -34,9 +34,9 @@ type Config struct { } // 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 { - T_s := time.Second * time.Duration(cfg.SpreadFactor.ChipsPerSymbol()) / + T_s := time.Second * time.Duration(cfg.SpreadingFactor.ChipsPerSymbol()) / time.Duration(cfg.Bandwidth.Hertz()) return T_s } @@ -47,7 +47,7 @@ func (cfg *Config) SymbolPeriod() time.Duration { // - CRC presence (presence == longer) // - Header type (explicit == longer) // - Coding rate (proportional) -// - Spread factor (inversely proportional) +// - Spreading factor (inversely proportional) // - Preamble length (proportional) // - Low data rate optimisation (presence == longer) func (cfg *Config) TimeOnAir(payloadLength int) time.Duration { @@ -58,10 +58,10 @@ func (cfg *Config) TimeOnAir(payloadLength int) time.Duration { ih := int64(cfg.HeaderType) ldr := int64(b2u8(cfg.LDRO)) cr := int64(cfg.CodingRate) - spread := int64(cfg.SpreadFactor) + sf := int64(cfg.SpreadingFactor) // Page 31 SX1276IMLTRT SEMTECH | Alldatasheet. - Npayload := 8*int64(payloadLength) - 4*spread + 28 + 16*crc - 20*ih - div := 4 * (spread - 2*ldr) + Npayload := 8*int64(payloadLength) - 4*sf + 28 + 16*crc - 20*ih + div := 4 * (sf - 2*ldr) // Apply Ceil and max with minimal branching. if Npayload < 0 || div <= 0 { Npayload = 0 @@ -77,7 +77,7 @@ func (cfg *Config) TimeOnAir(payloadLength int) time.Duration { // time calculated. Npayload += 8 + int64(cfg.PreambleLength) + 5 // 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)) / time.Duration(cfg.Bandwidth.Hertz()) } @@ -110,15 +110,16 @@ const ( 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. -// The number of chips per symbol is 2^SF, so a spread factor of 8 takes twice -// as long to transmit a symbol as a spread factor of 7. -type SpreadFactor uint8 +// 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 spreading factor of 7. +type SpreadingFactor uint8 -// Common spread factors. Decide the number of chips per symbol. +// Common spreading factors. Decide the number of chips per symbol. const ( - SF6 SpreadFactor = iota + 6 + SF5 SpreadingFactor = iota + 5 + SF6 SF7 SF8 SF9 @@ -129,9 +130,9 @@ const ( // 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 -// 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. -func (sf SpreadFactor) ChipsPerSymbol() int64 { +func (sf SpreadingFactor) ChipsPerSymbol() int64 { return 1 << sf } @@ -158,9 +159,10 @@ const ( // Common LoRa bandwidths const ( - BW125k = 125 * Kilohertz - BW250k = 250 * Kilohertz - BW500k = 500 * Kilohertz + BW125k = 125 * Kilohertz + BW250k = 250 * Kilohertz + BW500k = 500 * Kilohertz + BW1625k = 1625 * Kilohertz ) // Common LoRa frequencies @@ -168,11 +170,12 @@ const ( // 433.05MHz Low limit medical, scientific and industrial band. Freq433_0M = 433050000 * Hertz // 434.8MHz High limit medical, scientific and industrial band. - Freq434_8M = 434790000 * Hertz - Freq868_1M = 868100000 * Hertz - Freq868_5M = 868500000 * Hertz - Freq916_8M = 916800000 * Hertz - Freq923_3M = 923300000 * Hertz + Freq434_8M = 434790000 * Hertz + Freq868_1M = 868100000 * Hertz + Freq868_5M = 868500000 * Hertz + Freq916_8M = 916800000 * Hertz + Freq923_3M = 923300000 * Hertz + Freq2400_0M = 2400 * Megahertz ) // 169.4MHz radio band ([Wize]), formerly known as ERMES band. Historically used by pagers. diff --git a/lora_test.go b/lora_test.go index 2d3161a..e4cf46e 100644 --- a/lora_test.go +++ b/lora_test.go @@ -22,13 +22,13 @@ func TestTimeOnAir(t *testing.T) { { desc: "LoRaWAN 240bytes", // https://www.thethingsnetwork.org/airtime-calculator cfg: lora.Config{ - Bandwidth: lora.BW125k, - Frequency: 915e6, - SpreadFactor: lora.SF7, - HeaderType: lora.HeaderExplicit, - CodingRate: loraWANCodeRate, - CRC: true, - PreambleLength: loraWANPreambleLength, + Bandwidth: lora.BW125k, + Frequency: 915e6, + SpreadingFactor: lora.SF7, + HeaderType: lora.HeaderExplicit, + CodingRate: loraWANCodeRate, + CRC: true, + PreambleLength: loraWANPreambleLength, }, plen: 240, 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 cfg: lora.Config{ - Bandwidth: lora.BW500k, - Frequency: 915e6, - SpreadFactor: lora.SF7, - HeaderType: lora.HeaderExplicit, - CodingRate: loraWANCodeRate, - CRC: true, - PreambleLength: loraWANPreambleLength, + Bandwidth: lora.BW500k, + Frequency: 915e6, + SpreadingFactor: lora.SF7, + HeaderType: lora.HeaderExplicit, + CodingRate: loraWANCodeRate, + CRC: true, + PreambleLength: loraWANPreambleLength, }, plen: 10, expected: 15.4e3 * time.Microsecond, diff --git a/sx127x/sx127x.go b/sx127x/sx127x.go index 7fb934b..fc12716 100644 --- a/sx127x/sx127x.go +++ b/sx127x/sx127x.go @@ -81,7 +81,7 @@ type DeviceLoRa struct { func DefaultConfig(freq lora.Frequency) lora.Config { return lora.Config{ Frequency: freq, - SpreadFactor: lora.SF7, + SpreadingFactor: lora.SF7, Bandwidth: lora.BW125k, CodingRate: lora.CR4_5, PreambleLength: 8, @@ -103,7 +103,7 @@ func NewLoRa(bus SPI, cs, reset PinOutput) *DeviceLoRa { } 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. errPreambleTooShort = errors.New("preamble length too short") ErrNotDetected = errors.New("sx127x not detected") @@ -116,6 +116,7 @@ var ( ErrCRC = errors.New("crc error") ErrRxTimeout = errors.New("rx timeout") errImplicitPacketTooLarge = errors.New("packet length exceeds MaxImplicitPayloadLength") + errSyncWordTooLarge = errors.New("sync word too long") ) 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) { switch { - case cfg.SpreadFactor < lora.SF6 || cfg.SpreadFactor > lora.SF12: - err = errBadSpread - case cfg.SpreadFactor == lora.SF6 && cfg.HeaderType != lora.HeaderImplicit: + case cfg.SpreadingFactor < lora.SF6 || cfg.SpreadingFactor > lora.SF12: + err = errBadSpreadingFactor + case cfg.SpreadingFactor == lora.SF6 && cfg.HeaderType != lora.HeaderImplicit: err = errSF6Implicit case cfg.PreambleLength < 6: 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") case cfg.TxPower > 20 || cfg.TxPower < -4: err = errors.New("tx power not in operating range -4..20") + case cfg.SyncWord > 0xff: + err = errSyncWordTooLarge } if err != nil { return err @@ -189,7 +192,7 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) { return err } // TODO(soypat): Use default OCP? - err = d.setSyncWord(cfg.SyncWord) + err = d.setSyncWord(uint8(cfg.SyncWord)) if err != nil { return err } @@ -197,7 +200,7 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) { if err != nil { return err } - err = d.setSpreadFactorConsistent(cfg.SpreadFactor) + err = d.setSpreadingFactorConsistent(cfg.SpreadingFactor) if err != nil { return err } @@ -706,7 +709,7 @@ func (d *DeviceLoRa) ReadConfig() (cfg lora.Config, err error) { cfg.CodingRate = lora.CodingRate(cfg1>>1) & 0b111 cfg.HeaderType = lora.HeaderType(cfg1 & 1) cfg2 := buf[1] - cfg.SpreadFactor = lora.SpreadFactor(cfg1 >> 4) + cfg.SpreadingFactor = lora.SpreadingFactor(cfg1 >> 4) cfg.CRC = cfg2&0x4 != 0 // continuousMode = cfg2&0x8 != 0 cfg.PreambleLength = binary.BigEndian.Uint16(buf[3:]) @@ -715,7 +718,7 @@ func (d *DeviceLoRa) ReadConfig() (cfg lora.Config, err error) { if err != nil { return cfg, err } - cfg.SyncWord = sync + cfg.SyncWord = uint16(sync) // Read Frequency. err = d.read(regFRF_MSB, buf[:3]) 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! } -// 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. -func (d *DeviceLoRa) setSpreadFactorConsistent(sf lora.SpreadFactor) (err error) { +func (d *DeviceLoRa) setSpreadingFactorConsistent(sf lora.SpreadingFactor) (err error) { err = d.setSpreadingFactor(sf) if err != nil { 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 // the Low Data Optimization, the Detection threshold, Detection Optimize and the // Symbol Timeout. -func (d *DeviceLoRa) setSpreadingFactor(sf lora.SpreadFactor) error { +func (d *DeviceLoRa) setSpreadingFactor(sf lora.SpreadingFactor) error { if sf < 6 || sf > 12 { - return errBadSpread + return errBadSpreadingFactor } const sfMask = 0b111 << 4 return d.writeMasked8(regMODEM_CONFIG_2, sfMask, uint8(sf)<<4) diff --git a/sx128x/commands.go b/sx128x/commands.go new file mode 100644 index 0000000..18f4c02 --- /dev/null +++ b/sx128x/commands.go @@ -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) +) diff --git a/sx128x/constants.go b/sx128x/constants.go new file mode 100644 index 0000000..46c73dc --- /dev/null +++ b/sx128x/constants.go @@ -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) +) diff --git a/sx128x/errors.go b/sx128x/errors.go new file mode 100644 index 0000000..ef5acfb --- /dev/null +++ b/sx128x/errors.go @@ -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") +) diff --git a/sx128x/registers.go b/sx128x/registers.go new file mode 100644 index 0000000..e99f82c --- /dev/null +++ b/sx128x/registers.go @@ -0,0 +1,8 @@ +package sx128x + +const ( + regSpreadingFactorAdditionalConfiguration = uint16(0x925) + regFrequencyErrorCorrection = uint16(0x93C) + regLoRaSyncWordMSB = uint16(0x944) + regLoRaSyncWordLSB = uint16(0x945) +) diff --git a/sx128x/sx128x.go b/sx128x/sx128x.go new file mode 100644 index 0000000..c5fb913 --- /dev/null +++ b/sx128x/sx128x.go @@ -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 +}