diff --git a/lora.go b/lora.go index da2f4e1..000f7dd 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. + SpreadFactor SpreadFactor // this should probably be called "SpreadingFactor" to be consistent with LoRa terminology + 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. @@ -118,7 +118,8 @@ type SpreadFactor uint8 // Common spread factors. Decide the number of chips per symbol. const ( - SF6 SpreadFactor = iota + 6 + SF5 SpreadFactor = iota + 5 + SF6 SF7 SF8 SF9 @@ -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/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..b692574 --- /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, + SpreadFactor: 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.SpreadFactor, config.Bandwidth, config.CodingRate) + if err != nil { + return err + } + + // special register setting depending on spreading factor chosen + switch config.SpreadFactor { + 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.SpreadFactor, 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 +}