feat: add LoRaInterface implementation for TinyGo with SPI communication and packet handling
This commit is contained in:
293
pkg/interfaces/lora_tinygo.go
Normal file
293
pkg/interfaces/lora_tinygo.go
Normal file
@@ -0,0 +1,293 @@
|
||||
// SPDX-License-Identifier: 0BSD
|
||||
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
|
||||
//go:build tinygo
|
||||
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
|
||||
"git.quad4.io/Networks/Reticulum-Go/pkg/debug"
|
||||
)
|
||||
|
||||
const (
|
||||
REG_FIFO = 0x00
|
||||
REG_OP_MODE = 0x01
|
||||
REG_FRF_MSB = 0x06
|
||||
REG_FRF_MID = 0x07
|
||||
REG_FRF_LSB = 0x08
|
||||
REG_PA_CONFIG = 0x09
|
||||
REG_FIFO_ADDR_PTR = 0x0D
|
||||
REG_FIFO_TX_BASE_ADDR = 0x0E
|
||||
REG_FIFO_RX_BASE_ADDR = 0x0F
|
||||
REG_FIFO_RX_CURRENT_ADDR = 0x10
|
||||
REG_IRQ_FLAGS = 0x12
|
||||
REG_RX_NB_BYTES = 0x13
|
||||
REG_MODEM_CONFIG_1 = 0x1D
|
||||
REG_MODEM_CONFIG_2 = 0x1E
|
||||
REG_PREAMBLE_MSB = 0x20
|
||||
REG_PREAMBLE_LSB = 0x21
|
||||
REG_PAYLOAD_LENGTH = 0x22
|
||||
REG_MODEM_CONFIG_3 = 0x26
|
||||
REG_RSSI_WIDEBAND = 0x2C
|
||||
REG_DETECTION_OPTIMIZE = 0x31
|
||||
REG_DETECTION_THRESHOLD = 0x37
|
||||
REG_SYNC_WORD = 0x39
|
||||
REG_DIO_MAPPING_1 = 0x40
|
||||
REG_VERSION = 0x42
|
||||
|
||||
MODE_LONG_RANGE_MODE = 0x80
|
||||
MODE_SLEEP = 0x00
|
||||
MODE_STDBY = 0x01
|
||||
MODE_TX = 0x03
|
||||
MODE_RX_CONTINUOUS = 0x05
|
||||
|
||||
IRQ_RX_DONE_MASK = 0x40
|
||||
IRQ_PAYLOAD_CRC_ERROR_MASK = 0x20
|
||||
IRQ_TX_DONE_MASK = 0x08
|
||||
|
||||
MAX_PKT_LENGTH = 255
|
||||
)
|
||||
|
||||
// LoRaInterface provides a TinyGo SPI-based LoRa interface for SX127x.
|
||||
type LoRaInterface struct {
|
||||
BaseInterface
|
||||
spi machine.SPI
|
||||
cs machine.Pin
|
||||
reset machine.Pin
|
||||
dio0 machine.Pin
|
||||
freq uint32
|
||||
bw uint32
|
||||
sf uint8
|
||||
cr uint8
|
||||
txPower uint8
|
||||
done chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
// NewLoRaInterface initializes a new LoRaInterface.
|
||||
func NewLoRaInterface(name string, spi machine.SPI, cs, reset, dio0 machine.Pin, freq uint32, bw uint32, sf uint8, cr uint8, enabled bool) (*LoRaInterface, error) {
|
||||
li := &LoRaInterface{
|
||||
BaseInterface: NewBaseInterface(name, common.IF_TYPE_SERIAL, enabled),
|
||||
spi: spi,
|
||||
cs: cs,
|
||||
reset: reset,
|
||||
dio0: dio0,
|
||||
freq: freq,
|
||||
bw: bw,
|
||||
sf: sf,
|
||||
cr: cr,
|
||||
txPower: 17,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
li.MTU = MAX_PKT_LENGTH
|
||||
li.Bitrate = int64(bw * uint32(sf) / (1 << (sf - 1)))
|
||||
|
||||
if enabled {
|
||||
err := li.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return li, nil
|
||||
}
|
||||
|
||||
// Start configures and brings the LoRaInterface online.
|
||||
func (li *LoRaInterface) Start() error {
|
||||
li.Mutex.Lock()
|
||||
defer li.Mutex.Unlock()
|
||||
|
||||
if li.Online {
|
||||
return nil
|
||||
}
|
||||
|
||||
li.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
li.cs.High()
|
||||
li.reset.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
li.dio0.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
|
||||
li.reset.Low()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
li.reset.High()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
version := li.readReg(REG_VERSION)
|
||||
if version != 0x12 {
|
||||
return fmt.Errorf("LoRa chip not found, version: 0x%02x", version)
|
||||
}
|
||||
|
||||
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_SLEEP)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
frf := uint64(li.freq) << 19 / 32000000
|
||||
li.writeReg(REG_FRF_MSB, uint8(frf>>16))
|
||||
li.writeReg(REG_FRF_MID, uint8(frf>>8))
|
||||
li.writeReg(REG_FRF_LSB, uint8(frf))
|
||||
|
||||
li.writeReg(REG_FIFO_TX_BASE_ADDR, 0)
|
||||
li.writeReg(REG_FIFO_RX_BASE_ADDR, 0)
|
||||
li.writeReg(0x0C, 0x23)
|
||||
li.writeReg(REG_MODEM_CONFIG_3, 0x04)
|
||||
li.writeReg(REG_PA_CONFIG, 0x80|(li.txPower-2))
|
||||
|
||||
var bwVal uint8
|
||||
switch li.bw {
|
||||
case 125000:
|
||||
bwVal = 7
|
||||
case 250000:
|
||||
bwVal = 8
|
||||
case 500000:
|
||||
bwVal = 9
|
||||
default:
|
||||
bwVal = 7
|
||||
}
|
||||
li.writeReg(REG_MODEM_CONFIG_1, (bwVal<<4)|(li.cr-4)<<1|0x00)
|
||||
li.writeReg(REG_MODEM_CONFIG_2, (li.sf<<4)|0x04)
|
||||
|
||||
li.writeReg(REG_SYNC_WORD, 0x12)
|
||||
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_STDBY)
|
||||
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_RX_CONTINUOUS)
|
||||
|
||||
li.Online = true
|
||||
li.Enabled = true
|
||||
|
||||
go li.readLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readReg reads a byte from the given register.
|
||||
func (li *LoRaInterface) readReg(reg uint8) uint8 {
|
||||
li.cs.Low()
|
||||
li.spi.Transfer(reg & 0x7F)
|
||||
val, _ := li.spi.Transfer(0)
|
||||
li.cs.High()
|
||||
return val
|
||||
}
|
||||
|
||||
// writeReg writes a byte to the given register.
|
||||
func (li *LoRaInterface) writeReg(reg uint8, val uint8) {
|
||||
li.cs.Low()
|
||||
li.spi.Transfer(reg | 0x80)
|
||||
li.spi.Transfer(val)
|
||||
li.cs.High()
|
||||
}
|
||||
|
||||
// readLoop polls the radio for received packets and dispatches them.
|
||||
func (li *LoRaInterface) readLoop() {
|
||||
for {
|
||||
li.Mutex.RLock()
|
||||
online := li.Online
|
||||
done := li.done
|
||||
li.Mutex.RUnlock()
|
||||
|
||||
if !online {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
irq := li.readReg(REG_IRQ_FLAGS)
|
||||
if irq&IRQ_RX_DONE_MASK != 0 {
|
||||
li.writeReg(REG_IRQ_FLAGS, IRQ_RX_DONE_MASK)
|
||||
|
||||
if irq&IRQ_PAYLOAD_CRC_ERROR_MASK == 0 {
|
||||
currentAddr := li.readReg(REG_FIFO_RX_CURRENT_ADDR)
|
||||
li.writeReg(REG_FIFO_ADDR_PTR, currentAddr)
|
||||
count := li.readReg(REG_RX_NB_BYTES)
|
||||
|
||||
packet := make([]byte, count)
|
||||
li.cs.Low()
|
||||
li.spi.Transfer(REG_FIFO)
|
||||
for i := uint8(0); i < count; i++ {
|
||||
packet[i], _ = li.spi.Transfer(0)
|
||||
}
|
||||
li.cs.High()
|
||||
|
||||
li.ProcessIncoming(packet)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Send transmits a packet over LoRa.
|
||||
func (li *LoRaInterface) Send(data []byte, address string) error {
|
||||
return li.ProcessOutgoing(data)
|
||||
}
|
||||
|
||||
// ProcessOutgoing encodes and sends a packet.
|
||||
func (li *LoRaInterface) ProcessOutgoing(data []byte) error {
|
||||
li.Mutex.Lock()
|
||||
defer li.Mutex.Unlock()
|
||||
|
||||
if !li.Online {
|
||||
return fmt.Errorf("interface offline")
|
||||
}
|
||||
|
||||
if len(data) > MAX_PKT_LENGTH {
|
||||
return fmt.Errorf("packet too long for LoRa: %d", len(data))
|
||||
}
|
||||
|
||||
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_STDBY)
|
||||
li.writeReg(REG_FIFO_ADDR_PTR, 0)
|
||||
|
||||
li.cs.Low()
|
||||
li.spi.Transfer(REG_FIFO | 0x80)
|
||||
for _, b := range data {
|
||||
li.spi.Transfer(b)
|
||||
}
|
||||
li.cs.High()
|
||||
|
||||
li.writeReg(REG_PAYLOAD_LENGTH, uint8(len(data)))
|
||||
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_TX)
|
||||
|
||||
start := time.Now()
|
||||
for {
|
||||
if li.readReg(REG_IRQ_FLAGS)&IRQ_TX_DONE_MASK != 0 {
|
||||
li.writeReg(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK)
|
||||
break
|
||||
}
|
||||
if time.Since(start) > 2*time.Second {
|
||||
debug.Log(debug.DEBUG_ERROR, "LoRa TX timeout")
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
|
||||
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_RX_CONTINUOUS)
|
||||
|
||||
li.TxBytes += uint64(len(data))
|
||||
li.lastTx = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop disables the LoRaInterface.
|
||||
func (li *LoRaInterface) Stop() error {
|
||||
li.Mutex.Lock()
|
||||
li.Online = false
|
||||
li.Enabled = false
|
||||
li.writeReg(REG_OP_MODE, MODE_LONG_RANGE_MODE|MODE_SLEEP)
|
||||
li.Mutex.Unlock()
|
||||
|
||||
li.stopOnce.Do(func() {
|
||||
if li.done != nil {
|
||||
close(li.done)
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user