mirror of
https://github.com/soypat/lora.git
synced 2026-08-21 12:58:59 +00:00
add fifoReader type; bugfixes to lora package TimeOnAir
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build tinygo
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -26,6 +26,23 @@ type Config struct {
|
|||||||
IQInversion bool
|
IQInversion bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SymbolPeriod returns the time it takes to transmit a single symbol given the
|
||||||
|
// current configuration parameters. It depends on Spread factor and Bandwidth.
|
||||||
|
func (cfg *Config) SymbolPeriod() time.Duration {
|
||||||
|
T_s := time.Second * time.Duration(cfg.SpreadFactor.ChipsPerSymbol()) /
|
||||||
|
time.Duration(cfg.Bandwidth.Hertz())
|
||||||
|
return T_s
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeOnAir returns the time it takes to transmit a packet of the given payload
|
||||||
|
// length. It depends on the following config parameters:
|
||||||
|
// - Bandwidth (proportional)
|
||||||
|
// - CRC presence (presence == longer)
|
||||||
|
// - Header type (explicit == longer)
|
||||||
|
// - Coding rate (proportional)
|
||||||
|
// - Spread factor (inversely proportional)
|
||||||
|
// - Preamble length (proportional)
|
||||||
|
// - Low data rate optimisation (presence == longer)
|
||||||
func (cfg *Config) TimeOnAir(payloadLength int) time.Duration {
|
func (cfg *Config) TimeOnAir(payloadLength int) time.Duration {
|
||||||
if cfg.Bandwidth == 0 {
|
if cfg.Bandwidth == 0 {
|
||||||
return 0
|
return 0
|
||||||
@@ -52,11 +69,10 @@ func (cfg *Config) TimeOnAir(payloadLength int) time.Duration {
|
|||||||
// Says 4.25 in manual but we round up to 5. This means we'll overestimate the
|
// Says 4.25 in manual but we round up to 5. This means we'll overestimate the
|
||||||
// time calculated.
|
// time calculated.
|
||||||
Npayload += 8 + int64(cfg.PreambleLength) + 5
|
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.
|
// Calculate LoRa Transmission Parameter Relationship page 28.
|
||||||
Ts_us := int64(baseUnitOfTime) * cfg.SpreadFactor.ChipsPerSymbol() / cfg.Bandwidth.Hertz()
|
chipsPerSymbol := cfg.SpreadFactor.ChipsPerSymbol() // chips per symbol.
|
||||||
return baseUnitOfTime * time.Duration(Npayload*Ts_us)
|
return time.Second * time.Duration(Npayload*chipsPerSymbol) /
|
||||||
|
time.Duration(cfg.Bandwidth.Hertz())
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeaderType uint8
|
type HeaderType uint8
|
||||||
@@ -124,12 +140,13 @@ const (
|
|||||||
Freq923_3M = 923300000 * Hertz
|
Freq923_3M = 923300000 * Hertz
|
||||||
)
|
)
|
||||||
|
|
||||||
func max[T ~int | ~int64 | ~uint8](a, b T) T {
|
// 169.4MHz radio band ([Wize]), formerly known as ERMES band. Historically used by pagers.
|
||||||
if a > b {
|
//
|
||||||
return a
|
// [Wize]: https://en.wikipedia.org/wiki/Wize_technology
|
||||||
}
|
const (
|
||||||
return b
|
Freq169_4M = 169400000 * Hertz
|
||||||
}
|
Freq169_8M = 169812500 * Hertz
|
||||||
|
)
|
||||||
|
|
||||||
func b2u8(b bool) uint8 {
|
func b2u8(b bool) uint8 {
|
||||||
if b {
|
if b {
|
||||||
|
|||||||
+129
-10
@@ -43,6 +43,7 @@ The FIFO is accessible through the SPI
|
|||||||
package sx127x
|
package sx127x
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
@@ -168,7 +169,7 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = d.setTimeoutInSymbols(1023) // Set timeout to max value.
|
err = d.SetSymbolTimeout(1023) // Set timeout to max value.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -316,6 +317,8 @@ func (d *DeviceLoRa) Tx(packet []byte) (err error) {
|
|||||||
return nil
|
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) {
|
func (d *DeviceLoRa) RxSingle(dst []byte) (uint8, error) {
|
||||||
op, err := d.GetOpMode()
|
op, err := d.GetOpMode()
|
||||||
switch {
|
switch {
|
||||||
@@ -364,18 +367,59 @@ func (d *DeviceLoRa) RxSingle(dst []byte) (uint8, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
// Ready to read packet! Rewrite FifoAddrPtr just in case...
|
// Ready to read packet! Rewrite FifoAddrPtr just in case...
|
||||||
d.write8(regFIFO_ADDR_PTR, fifoAddr)
|
fr, err := d.readerToLastPacket()
|
||||||
nbBytes, err := d.read8(regRX_NB_BYTES)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
for i := uint8(0); i < nbBytes; i++ {
|
return fr.readInternal(dst)
|
||||||
dst[i], err = d.read8(regFIFO)
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
for ctx.Err() == nil {
|
||||||
|
runtime.Gosched() // Yield to scheduler.
|
||||||
|
irq, err = d.read8(regIRQ_FLAGS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return i, err
|
return err
|
||||||
|
}
|
||||||
|
if irq&irqRXDONE_MASK != 0 {
|
||||||
|
err = d.gotRxContinous(fn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nbBytes, nil
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DeviceLoRa) prepareForRx(fifoAddr uint8) (err error) {
|
func (d *DeviceLoRa) prepareForRx(fifoAddr uint8) (err error) {
|
||||||
@@ -394,6 +438,18 @@ func (d *DeviceLoRa) prepareForRx(fifoAddr uint8) (err error) {
|
|||||||
return d.write8(regFIFO_ADDR_PTR, fifoAddr)
|
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:
|
// clearIRQ clears IRQ bits indicated by toClear:
|
||||||
// - bit 0: CAD detected interrupt
|
// - bit 0: CAD detected interrupt
|
||||||
// - bit 1: FHSS change channel interrupt
|
// - bit 1: FHSS change channel interrupt
|
||||||
@@ -411,7 +467,6 @@ func (d *DeviceLoRa) clearIRQ(toClear uint8) error {
|
|||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
reg, _ := d.read8(regIRQ_FLAGS)
|
reg, _ := d.read8(regIRQ_FLAGS)
|
||||||
if reg&toClear != 0 {
|
if reg&toClear != 0 {
|
||||||
println(reg, toClear)
|
|
||||||
return errIRQNotCleared
|
return errIRQNotCleared
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -678,11 +733,11 @@ func (d *DeviceLoRa) enableCRC(b bool) error {
|
|||||||
return d.writeMasked8(regMODEM_CONFIG_2, crcMask, b2u8(b)<<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.
|
// 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:
|
// The timeout is used to stop reception automatically. The equation is:
|
||||||
//
|
//
|
||||||
// Timeout = timeoutSymbols * Ts (where Ts is the symbol period)
|
// Timeout = timeoutSymbols * Ts (where Ts is the symbol period)
|
||||||
func (d *DeviceLoRa) setTimeoutInSymbols(symbTimeout uint16) (err error) {
|
func (d *DeviceLoRa) SetSymbolTimeout(symbTimeout uint16) (err error) {
|
||||||
if symbTimeout > 0x3FF || symbTimeout < 4 {
|
if symbTimeout > 0x3FF || symbTimeout < 4 {
|
||||||
return errors.New("symbol timeout must be in range 4..1023")
|
return errors.New("symbol timeout must be in range 4..1023")
|
||||||
}
|
}
|
||||||
@@ -761,3 +816,67 @@ func (d *DeviceLoRa) read(addr uint8, buf []byte) error {
|
|||||||
d.csEnable(false)
|
d.csEnable(false)
|
||||||
return err
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user