294 lines
6.5 KiB
Go
294 lines
6.5 KiB
Go
// 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
|
|
}
|
|
|