From 0df54699a6f36dcb14db087781aa236adab55abb Mon Sep 17 00:00:00 2001 From: soypat Date: Sat, 20 May 2023 22:10:02 -0300 Subject: [PATCH] add Random* methods --- examples/transmitter/transmitter.go | 16 ++ sx127x/sx127x.go | 247 ++++++++++++++++++++++++---- 2 files changed, 231 insertions(+), 32 deletions(-) diff --git a/examples/transmitter/transmitter.go b/examples/transmitter/transmitter.go index 500bdc9..5b55dff 100644 --- a/examples/transmitter/transmitter.go +++ b/examples/transmitter/transmitter.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "machine" "time" @@ -51,4 +52,19 @@ func main() { panic(err.Error()) } println("config success") + for { + rssiRead, err := dev.EstimateReadFromRSSIPeriod(time.Second) + if err != nil { + panic(err.Error()) + } + println("rssiRead:", rssiRead.String()) + } + + tstart := time.Now() + var rng [4]byte + err = dev.RandomRead(rng[:], 0) + if err != nil { + panic(err.Error()) + } + fmt.Printf("random bytes: %b\nelapsed:%s\n", rng, time.Since(tstart).String()) } diff --git a/sx127x/sx127x.go b/sx127x/sx127x.go index 47cd1b5..967cb01 100644 --- a/sx127x/sx127x.go +++ b/sx127x/sx127x.go @@ -46,6 +46,7 @@ import ( "encoding/binary" "errors" "io" + "runtime" "time" "github.com/soypat/lora" @@ -63,9 +64,10 @@ type SPI interface { } type DeviceLoRa struct { - rst PinOutput - cs PinOutput - bus SPI + rst PinOutput + cs PinOutput + bus SPI + headerType lora.HeaderType } func NewLoRa(bus SPI, cs, reset PinOutput) *DeviceLoRa { @@ -81,6 +83,7 @@ var ( 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") ) func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) { @@ -95,6 +98,10 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) { 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.TxPower > 20: + err = errors.New("bad tx power") } if err != nil { return err @@ -121,7 +128,7 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) { if err != nil { return err } - err = d.EnableAutoAGC(true) + err = d.EnableAutoGainControl(true) if err != nil { return err } @@ -145,7 +152,8 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) { if err != nil { return err } - err = d.enableImplicitHeaderMode(cfg.HeaderType == lora.HeaderImplicit) + isImplicit := cfg.HeaderType == lora.HeaderImplicit + err = d.enableImplicitHeaderMode(isImplicit) if err != nil { return err } @@ -157,17 +165,44 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) { if err != nil { return err } + err = d.setTimeoutInSymbols(1023) // Set timeout to max value. + if err != nil { + return err + } + const rxStart = 128 + d.write8(regFIFO_TX_BASE_ADDR, 0) + d.write8(regFIFO_RX_BASE_ADDR, rxStart) d.setHopPeriod(0) - return nil + d.headerType = cfg.HeaderType + return d.SetOpMode(OpStandby) } +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 { + if mode == OpSleep || true { time.Sleep(15 * time.Millisecond) // TODO: do we need this sleep? } got, err := d.GetOpMode() @@ -180,6 +215,8 @@ func (d *DeviceLoRa) SetOpMode(mode OpMode) error { 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) @@ -206,6 +243,174 @@ func (d *DeviceLoRa) SetLNAGain(gain uint8) error { 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) +} + +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 + } + counts := 0 + var reg uint8 + for { + counts++ + reg, err = d.read8(regIRQ_FLAGS) + if reg&irqTXDONE_MASK != 0 || err != nil { + if err != nil { + return err + } + break + } + runtime.Gosched() // Yield to scheduler. + } + err = d.clearIRQ(irqTXDONE_MASK) + 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 + } + 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. +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 @@ -311,7 +516,9 @@ func (d *DeviceLoRa) setTxPower(txPow int8) error { if err != nil { return err } - return d.write8(regOCP, 0) // TODO: Disable OCP? + // Set to minimal current. + return d.setOCP(45) + // return d.write8(regOCP, 0) // TODO: Disable OCP? } // setFrequency sets the center radio frequency. @@ -365,13 +572,6 @@ 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 { @@ -453,23 +653,6 @@ func (d *DeviceLoRa) csEnable(b bool) { d.cs(!b) } -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) -} - -func (d *DeviceLoRa) IsConnected() bool { - version, err := d.read8(regVERSION) - if version == expectedVersion && err == nil { - return true - } - return false -} - func b2u8(b bool) uint8 { if b { return 1