Files
lora/sx127x/sx127x.go
T
2023-05-22 08:12:32 -03:00

973 lines
28 KiB
Go

/*
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 (
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"runtime"
"time"
"github.com/soypat/lora"
)
// 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
}
const debugBufSize = 255
type DeviceLoRa struct {
rst PinOutput
cs PinOutput
bus SPI
headerType lora.HeaderType
// onNoPacket is called while waiting for a packet to be received in a tight loop.
onNoPacket func()
// Debugging buffers.
debugRead, debugWrite, debugMask [debugBufSize]byte
}
func DefaultConfig(freq lora.Frequency) lora.Config {
return lora.Config{
Frequency: freq,
SpreadFactor: lora.SF7,
Bandwidth: 125 * lora.KiloHertz,
CodingRate: lora.CR4_5,
PreambleLength: 12,
HeaderType: lora.HeaderExplicit,
MaxImplicitPayloadLength: 0, // No need to be set when working with explicit headers.
CRC: true,
SyncWord: publicSyncword,
TxPower: 0,
LDRO: false,
IQInversion: false,
}
}
// NewLoRa returns a new SX127x device. It performs no I/O operations.
// The caller should call Configure before using the device.
func NewLoRa(bus SPI, cs, reset PinOutput) *DeviceLoRa {
d := DeviceLoRa{bus: bus, cs: cs, rst: reset, onNoPacket: runtime.Gosched}
return &d
}
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")
errIRQNotCleared = errors.New("IRQs not cleared")
ErrDeviceBusy = errors.New("device busy")
ErrCRC = errors.New("crc error")
ErrRxTimeout = errors.New("rx timeout")
errImplicitPacketTooLarge = errors.New("packet length exceeds MaxImplicitPayloadLength")
)
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:
err = errSF6Implicit
case cfg.PreambleLength < 6:
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
case cfg.HeaderType != lora.HeaderImplicit && cfg.HeaderType != lora.HeaderExplicit:
err = errors.New("bad header type")
case cfg.HeaderType == lora.HeaderImplicit && cfg.MaxImplicitPayloadLength == 0:
err = errors.New("MaxImplicitPayloadLength parameter must be set when working with implicit headers")
case cfg.TxPower > 20:
err = errors.New("bad tx power")
}
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 ErrNotDetected
}
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.EnableAutoGainControl(true)
if err != nil {
return err
}
err = d.setPreambleLength(cfg.PreambleLength)
if err != nil {
return err
}
err = d.setTxPower(cfg.TxPower)
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
}
isImplicit := cfg.HeaderType == lora.HeaderImplicit
err = d.enableImplicitHeaderMode(isImplicit)
if err != nil {
return err
}
if isImplicit {
d.setMaxPayloadLength(cfg.MaxImplicitPayloadLength)
}
err = d.enableIQInversion(cfg.IQInversion)
if err != nil {
return err
}
err = d.enableTxContinuousMode(false) // TODO: enable or disable?
if err != nil {
return err
}
err = d.enableLowDataRateOptimization(cfg.LDRO)
if err != nil {
return err
}
err = d.SetSymbolTimeout(1023) // Set timeout to max value.
if err != nil {
return err
}
d.setHopPeriod(0)
// Go insto standby mode to write to FIFO related registers.
err = d.SetOpMode(OpStandby)
if err != nil {
return err
}
d.write8(regFIFO_TX_BASE_ADDR, 0)
d.write8(regFIFO_RX_BASE_ADDR, 0)
d.write8(regFIFO_ADDR_PTR, 0)
d.headerType = cfg.HeaderType
return d.SetOpMode(OpSleep) // Return to sleep mode to conserve power.
}
func (d *DeviceLoRa) Reset() {
d.rst(true)
time.Sleep(200 * time.Millisecond)
d.rst(false)
time.Sleep(200 * time.Millisecond)
d.rst(true)
time.Sleep(200 * time.Millisecond)
}
// IsConnected reads the version register and checks if it matches the expected value.
func (d *DeviceLoRa) IsConnected() bool {
version, err := d.read8(regVERSION)
if version == expectedVersion && err == nil {
return true
}
return false
}
// SetOpMode sets the operating mode of the SX127x to a LoRa mode.
func (d *DeviceLoRa) SetOpMode(mode OpMode) error {
// We always write the LoRa mode bit
err := d.write8(regOP_MODE, byte(mode|opLoRaBit))
if err != nil {
return err
}
if mode == OpSleep || true {
time.Sleep(15 * time.Millisecond) // TODO: do we need this sleep?
}
got, err := d.GetOpMode()
if err != nil {
return d.wrapErr(err)
}
if got != mode {
return errors.New("tried to set opmode " + mode.String() + ", got " + got.String())
}
return nil
}
// GetOpMode returns the current operating mode of the SX127x. It returns an error
// if the device is not in LoRa mode.
func (d *DeviceLoRa) GetOpMode() (OpMode, error) {
const invalidOpMode = 0xff
got, err := d.read8(regOP_MODE)
if err != nil {
return invalidOpMode, err
}
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, 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)
}
// EnableAutoGainControl 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) EnableAutoGainControl(b bool) error {
const agcMask = 1 << 2
return d.writeMasked8(regMODEM_CONFIG_3, agcMask, b2u8(b)<<2)
}
// Tx sends packet over LoRa network and blocks until packet is done sending.
func (d *DeviceLoRa) Tx(packet []byte) (err error) {
if len(packet) > 255 {
return errors.New("packet too long")
}
opmode, err := d.GetOpMode()
if err != nil {
return err
}
if opmode != OpStandby && opmode != OpSleep {
println("unexpected opmode before Tx:", opmode.String())
err = d.SetOpMode(OpSleep)
if err != nil {
return err
}
}
err = d.write8(regPAYLOAD_LENGTH, uint8(len(packet)))
if err != nil {
return err
}
plen, _ := d.read8(regPAYLOAD_LENGTH)
if plen != uint8(len(packet)) {
return errors.New("payload length unable to be set correctly")
}
// FIFO registers only accesible in Standby mode.
err = d.SetOpMode(OpStandby)
if err != nil {
return err
}
d.write8(regFIFO_TX_BASE_ADDR, 0)
d.write8(regFIFO_ADDR_PTR, 0)
for i := 0; i < len(packet); i++ {
err := d.write8(regFIFO, packet[i])
if err != nil {
return err
}
}
// Begin transmitting immediately.
err = d.SetOpMode(OpTx)
if err != nil {
return err
}
var irq uint8
for irq&irqTXDONE_MASK == 0 {
runtime.Gosched() // Yield to scheduler.
irq, err = d.read8(regIRQ_FLAGS)
if err != nil {
d.SetOpMode(OpSleep) // Back to sleep in case of error.
return err
}
}
// Go back to sleep to be able to write to static registers.
err = d.SetOpMode(OpSleep)
if err != nil {
return err
}
err = d.clearIRQ(irqTXDONE_MASK)
if err != nil {
return err
}
return nil
}
// RxSingle receives a single packet over LoRa network and blocks until packet is
// received or timeout occurs. Timeout is controlled by value set in [SetSymbolTimeout].
func (d *DeviceLoRa) RxSingle(dst []byte) (uint8, error) {
op, err := d.GetOpMode()
switch {
case err != nil:
// err already set
case len(dst) < 255:
err = io.ErrShortBuffer
case op == OpTx || op == OpRx || op == OpRxSingle:
err = ErrDeviceBusy // Device is currently transmitting or receiving.
}
if err != nil {
return 0, err
}
// Reset packet pointers.
const fifoAddr = 0
err = d.prepareForRx(fifoAddr)
if err != nil {
return 0, err
}
// Begin looking for packets immediately until first one found (RxSingle mode).
err = d.SetOpMode(OpRxSingle)
if err != nil {
return 0, err
}
defer d.SetOpMode(OpSleep) // Ensure Sleep Mode on exit.
// Loop until Rx received or timeout IRQ.
var irq uint8
for {
irq, err = d.read8(regIRQ_FLAGS)
if err != nil {
return 0, err
}
if irq&(irqRXDONE_MASK|irqRXTOUT_MASK) != 0 {
break
}
d.onNoPacket()
}
// Check for payload integrity and timeout interrupt.
if irq&irqCRCERR_MASK != 0 {
if d.headerType == lora.HeaderImplicit {
return 0, errImplicitPacketTooLarge
}
return 0, ErrCRC
} else if irq&irqRXTOUT_MASK != 0 {
return 0, ErrRxTimeout
}
// TODO(soypat): This should already be in standby mode according to datasheet.
// remove once confirmed it does not affect behaviour. Needed to read FIFO addr.
err = d.SetOpMode(OpStandby)
if err != nil {
return 0, err
}
// Ready to read packet! Rewrite FifoAddrPtr just in case...
fr, err := d.readerToLastPacket()
if err != nil {
return 0, err
}
return fr.readInternal(dst)
}
type rxCallback = func(r io.Reader) (_ error)
// RxContinuous starts listening for packets until fn returns an error or ctx is cancelled.
//
// When a preamble is detected the device tracks it until the packet is received
// at which point fn is called with a Reader to the packet.
func (d *DeviceLoRa) RxContinuous(ctx context.Context, fn rxCallback) error {
op, err := d.GetOpMode()
switch {
case err != nil:
// err already set
case op == OpTx || op == OpRx || op == OpRxSingle:
err = ErrDeviceBusy // Device is currently transmitting or receiving.
}
if err != nil {
return err
}
// Reset packet pointers.
const fifoAddr = 0
err = d.prepareForRx(fifoAddr)
if err != nil {
return err
}
// Begin looking for packets immediately until first one found (RxSingle mode).
err = d.SetOpMode(OpRx)
if err != nil {
return err
}
defer d.SetOpMode(OpSleep) // Ensure Sleep Mode on exit.
// Loop until Rx received or timeout IRQ.
var irq uint8
count := 0
defer func() {
println("RxContinuous looped", count, "times")
}()
for ctx.Err() == nil {
irq, err = d.read8(regIRQ_FLAGS)
if err != nil {
return err
}
if irq&irqRXDONE_MASK != 0 {
nbBytes, _ := d.read8(regRX_NB_BYTES)
if nbBytes == 0 {
return fmt.Errorf("received packet with 0 bytes;irq=%08b; stat=%s", irq, d.statusString())
}
err = d.gotRxContinous(fn)
if err != nil {
return err
}
} else {
d.onNoPacket()
}
count++
op, err = d.GetOpMode()
if err != nil {
return err
} else if op != OpRx {
return d.wrapErr(errors.New("unexpected op mode change during RxContinuous to " +
op.String() + " with irqs:" + irqFlagsString(irq)))
}
}
return ctx.Err()
}
func (d *DeviceLoRa) prepareForRx(fifoAddr uint8) (err error) {
err = d.SetOpMode(OpStandby) // Standby mode needed so that FIFO can be written/read.
if err != nil {
return err
}
err = d.clearIRQ(irqRXDONE_MASK | irqHEADER_MASK | irqCRCERR_MASK | irqRXTOUT_MASK)
if err != nil {
return err
}
err = d.write8(regFIFO_RX_BASE_ADDR, fifoAddr)
if err != nil {
return err
}
return d.write8(regFIFO_ADDR_PTR, fifoAddr)
}
func (d *DeviceLoRa) gotRxContinous(fn rxCallback) error {
fr, err := d.readerToLastPacket()
if err != nil {
return err
}
err = fn(fr)
if err != nil {
return err
}
return nil
}
// clearIRQ clears IRQ bits indicated by toClear:
// - bit 0: CAD detected interrupt
// - bit 1: FHSS change channel interrupt
// - bit 2: CAD done interrupt
// - bit 3: Tx done interrupt
// - bit 4: Valid header received in Rx
// - bit 5: Payload CRC error
// - bit 6: Rx done interrupt
// - bit 7: Rx timeout interrupt
func (d *DeviceLoRa) clearIRQ(toClear uint8) error {
err := d.write8(regIRQ_FLAGS, toClear)
if err != nil {
return err
}
time.Sleep(500 * time.Millisecond)
reg, _ := d.read8(regIRQ_FLAGS)
if reg&toClear != 0 {
return errIRQNotCleared
}
return nil
}
// RandomU32 returns a random uint32 generated by reading the RSSI during
// Rx OpMode. This method should not be used while the device is operating.
func (d *DeviceLoRa) RandomU32() (rnd uint32, err error) {
var buf [4]byte
err = d.RandomRead(buf[:], 10*time.Millisecond)
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint32(buf[:]), d.SetOpMode(OpSleep)
}
// RandomRead reads random byte data to dst by reading the RSSI during Rx OpMode.
// The period between RSSI reads is readFromRSSIPeriod. A higher readFromRSSIPeriod
// will typically result in higher entropy in the random data. 10ms is a reasonable period.
// This method will take approximately readFromRSSIPeriod*len(dst)*8 + 50ms to complete.
//
// See EstimateReadFromRSSIPeriod method.
func (d *DeviceLoRa) RandomRead(dst []byte, readFromRSSIPeriod time.Duration) error {
// Disable ALL irqs
err := d.clearIRQ(0xff)
if err != nil {
return err
}
err = d.SetOpMode(OpRx)
if err != nil {
return err
}
for i := 0; i < len(dst)*8; i++ {
time.Sleep(readFromRSSIPeriod)
val, err := d.read8(regRSSI_WIDEBAND)
if err != nil {
return err
}
// Unfiltered RSSI value reading. Only takes the LSB value
dst[i/8] |= (val & 1) << (i % 8)
}
return d.SetOpMode(OpSleep)
}
// EstimateReadFromRSSIPeriod estimates the readFromRSSIPeriod required to read
// relatively random bits of data for testDuration time.
//
// It's recommended that one call this function several times with small
// durations and use a median value as a compromise between call duration
// and entropy. It is common to get values separated by orders of magnitude
// depending on whether there was a signal present during the test.
// See the [RandomRead] method.
func (d *DeviceLoRa) EstimateReadFromRSSIPeriod(testDuration time.Duration) (time.Duration, error) {
err := d.clearIRQ(0xff)
if err != nil {
return 0, err
}
err = d.SetOpMode(OpRx)
if err != nil {
return 0, err
}
start := time.Now()
val, _ := d.read8(regRSSI_WIDEBAND)
lastBit := val&1 != 0
maxBitHoldTime := time.Duration(0)
lastBitChangeTime := start
var readTime = time.Now()
for readTime.Sub(start) < testDuration {
val, err := d.read8(regRSSI_WIDEBAND)
if err != nil {
return 0, err
}
readTime = time.Now()
bit0 := val&1 != 0
if bit0 != lastBit {
lastBit = bit0
elapsedSinceBitChange := readTime.Sub(lastBitChangeTime)
lastBitChangeTime = readTime
if elapsedSinceBitChange > maxBitHoldTime {
maxBitHoldTime = elapsedSinceBitChange
}
}
}
return maxBitHoldTime, d.SetOpMode(OpSleep)
}
// 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])
if err != nil {
return cfg, err
}
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
binary.BigEndian.PutUint16(buf[:], pLen)
d.write8(regPREAMBLE_MSB, buf[0])
return d.write8(regPREAMBLE_LSB, buf[1])
}
func (d *DeviceLoRa) setMaxPayloadLength(plen uint8) error {
if plen == 0 {
return io.ErrShortBuffer
}
return d.write8(regPAYLOAD_LENGTH, plen)
}
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
// the max current (Imax) in milliamperes.
func (d *DeviceLoRa) setOCP(mA uint8) error {
const ocpEnabledMask = 1 << 5
if mA < 45 {
mA = 45 // Absolute minimum is 45mA.
}
var ocpTrim uint8
switch {
case mA <= 120: // Imax [mA] = 45 +5*OcpTrim
ocpTrim = (mA - 45) / 5
case mA <= 240: // Imax [mA] = -30 + 10*OcpTrim
ocpTrim = (mA + 30) / 10
default: // Imax = 240mA
ocpTrim = 27
}
return d.write8(regOCP, ocpEnabledMask|(0x1F&ocpTrim))
}
// setTxPower sets the transmit power without using te PA_BOOST.
func (d *DeviceLoRa) setTxPower(txPow int8) error {
if txPow >= 16 {
return errors.New("requested tx power exceeds capabilities without PA_BOOST")
}
// Pout=Pmax-(15-OutputPower)
// Pmax=10.8+0.6*MaxPower [dBm]
const Pmax = 0b111 // Use Pmax ceiling.
const PoutMask = 0b1111
Pout := Pmax - (15 - txPow)
if Pout < 0 {
Pout = 0
}
// This unsets PaSelect bit which switches mode of operation to RFO pin (limited to 14dBm power).
err := d.write8(regPA_CONFIG, (Pmax<<4)|(PoutMask&uint8(Pout)))
if err != nil {
return err
}
// Set to minimal current.
return d.setOCP(45)
// return d.write8(regOCP, 0) // TODO: Disable OCP?
}
// 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)
freqReg[1] = byte(frf >> 8)
freqReg[2] = byte(frf >> 0)
d.write8(regFRF_MSB, freqReg[0])
d.write8(regFRF_MID, freqReg[1])
// Note pg82: A change in the center frequency will only be taken into account when the
// least significant byte FrfLsb in RegFrfLsb is written.
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<<b2u8(isSF6)))
if err != nil {
return err
}
// Set DetectionThreshold to 0x0C for SF6 and to 0x0A otherwise (SF7 to SF12).
return d.write8(regDETECTION_THRESHOLD, 0b1000|(0b10<<b2u8(isSF6)))
}
// setSpreadingFactor sets the spreading factor. The value must be between 6 and 12.
// 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 {
if sf < 6 || sf > 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)
}
// 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 = 0b11 << 2
return d.writeMasked8(regMODEM_CONFIG_2, crcMask, b2u8(b)<<2)
}
// SetSymbolTimeout 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) SetSymbolTimeout(symbTimeout uint16) (err error) {
if symbTimeout > 0x3FF || symbTimeout < 4 {
return errors.New("symbol timeout must be in range 4..1023")
}
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)
if err != nil {
return err
}
existing &^= mask // remove mask bits from register value.
existing |= mask & value // add value's bits as masked.
err = d.write8(addr, existing)
if debugBufSize > 0 {
d.debugMask[addr] = mask
}
return err
}
func (d *DeviceLoRa) read8(addr byte) (byte, error) {
readBuf := make([]byte, 2)
writeBuf := []byte{addr &^ (1 << 7), 0} // unset write bit. Should be pretty much useless though we play it safe.
d.csEnable(true)
err := d.bus.Tx(writeBuf, readBuf)
d.csEnable(false)
if debugBufSize > 0 {
d.debugRead[addr] = readBuf[1]
}
return readBuf[1], err
}
func (d *DeviceLoRa) write8(addr, value byte) error {
readBuf := make([]byte, 2)
writeBuf := []byte{addr | (1 << 7), value} // set write bit.
d.csEnable(true)
err := d.bus.Tx(writeBuf, readBuf)
d.csEnable(false)
if debugBufSize > 0 {
d.debugWrite[addr] = value
d.debugMask[addr] = 0xFF
d.read8(addr) // refresh address value.
}
return err
}
func (d *DeviceLoRa) csEnable(b bool) {
d.cs(!b)
}
func b2u8(b bool) uint8 {
if b {
return 1
}
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
}
func (d *DeviceLoRa) readerToLastPacket() (_ *fifoReader, err error) {
// IRQ check according to page 41 of the datasheet.
// Flags must not be asserted in order to ensure packet reception has terminated succesfully.
const mustBeUnset = irqRXDONE_MASK | irqHEADER_MASK | irqCRCERR_MASK | irqTXDONE_MASK
d.write8(regIRQ_FLAGS, mustBeUnset)
var irqFlags uint8
for count := 0; count < 100; count++ {
irqFlags, err = d.read8(regIRQ_FLAGS)
if err != nil {
return nil, err
}
if irqFlags&mustBeUnset == 0 {
break
}
runtime.Gosched()
}
if irqFlags&mustBeUnset != 0 {
return nil, errors.New("timeout waiting for IRQ before reading packet:" + irqFlagsString(irqFlags))
}
// We now know that the packet has been received succesfully. Proceed to read.
curraddr, err := d.read8(regFIFO_RX_CURRENT_ADDR)
if err != nil {
return nil, err
}
numBytes, err := d.read8(regRX_NB_BYTES)
if err != nil {
return nil, err
}
if numBytes == 0 {
return nil, errors.New("readerToNextPacket called with RX_NB_BYTES=0")
}
err = d.write8(regFIFO_ADDR_PTR, curraddr)
if err != nil {
return nil, err
}
return &fifoReader{d: d, leftToRead: numBytes}, nil
}
type fifoReader struct {
d *DeviceLoRa
leftToRead uint8
}
func (f *fifoReader) Read(buf []byte) (int, error) {
n, err := f.readInternal(buf)
return int(n), err
}
func (f *fifoReader) readInternal(buf []byte) (_ uint8, err error) {
if f.leftToRead == 0 {
return 0, io.EOF
}
toRead := f.leftToRead
if int(toRead) > len(buf) {
toRead = uint8(len(buf))
}
for i := uint8(0); i < toRead; i-- {
buf[i], err = f.d.read8(regFIFO)
if err != nil {
return i, err
}
}
f.leftToRead -= toRead
return toRead, nil
}