Files
tinygo-projects/lilygo-t3s3-sx1280-lora2udp/main.go
T
2026-05-09 09:00:41 -05:00

255 lines
6.5 KiB
Go

package main
import (
"machine"
"net"
"runtime"
"sync/atomic"
"time"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/netdev"
nl "tinygo.org/x/drivers/netlink"
"tinygo.org/x/drivers/sx128x"
link "tinygo.org/x/espradio/netlink"
)
var (
ssid string
password string
)
type LoRaConfig struct {
Frequency uint32
Power int8
RadioRamp sx128x.RadioRampTime
RegulatorMode sx128x.RegulatorMode
SpreadingFactor sx128x.LoRaSpreadingFactor
Bandwidth sx128x.LoRaBandwidth
CodingRate sx128x.LoRaCodingRate
PreambleLength uint32
HeaderType sx128x.LoRaHeaderType
CrcType sx128x.LoRaCrcType
IqType sx128x.LoRaIqType
SyncWord uint16
}
func main() {
time.Sleep(3 * time.Second)
link := link.Esplink{}
netdev.UseNetdev(&link)
println("Connecting to WiFi...")
err := link.NetConnect(&nl.ConnectParams{
Ssid: ssid,
Passphrase: password,
})
if err != nil {
panic("connect failed: " + err.Error())
}
conn, err := net.Dial("udp", "10.0.0.50:53000")
if err != nil {
panic("dial failed: " + err.Error())
}
defer conn.Close()
println("Connected to WiFi.")
led := machine.GPIO37
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
spi := machine.SPI0
spi.Configure(machine.SPIConfig{
Mode: 0,
Frequency: 8 * 1e6,
SDO: machine.GPIO6,
SDI: machine.GPIO3,
SCK: machine.GPIO5,
})
nssPin := machine.GPIO7
nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
nssPin.Set(true)
resetPin := machine.GPIO8
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Set(true)
busyPin := machine.GPIO36
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
println("initializing radio...")
radio := sx128x.New(
spi,
nssPin,
resetPin,
busyPin,
)
dio1Pin := machine.GPIO9
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInput})
txDone := atomic.Bool{}
txDone.Store(true)
rxDone := atomic.Bool{}
rxDone.Store(true)
timeout := atomic.Bool{}
timeout.Store(true)
dio1Interrupt := atomic.Bool{}
dio1Interrupt.Store(false)
dio1Pin.SetInterrupt(machine.PinRising, func(machine.Pin) {
dio1Interrupt.Store(true)
})
loraConfig := LoRaConfig{
Frequency: 2400000000, // 2.4Ghz
Power: 13, // dBm
RadioRamp: sx128x.RADIO_RAMP_02_US, // 2 microsecond ramp time
RegulatorMode: sx128x.REGULATOR_DC_DC,
SpreadingFactor: sx128x.LORA_SF_9,
Bandwidth: sx128x.LORA_BW_1600,
CodingRate: sx128x.LORA_CR_4_7,
PreambleLength: 12,
HeaderType: sx128x.LORA_HEADER_EXPLICIT,
CrcType: sx128x.LORA_CRC_DISABLE,
IqType: sx128x.LORA_IQ_STD,
SyncWord: 0x1424, // the default private sync word
}
radio.WaitWhileBusy(time.Second)
SetupLora(radio, loraConfig)
println("radio initialized, waiting for messages...")
for {
runtime.Gosched()
if dio1Interrupt.Load() {
// println("DIO1 interrupt triggered")
irqStatus, _ := radio.GetIrqStatus()
println("irq status:", irqStatus)
if irqStatus&sx128x.IRQ_TX_DONE_MASK != 0 {
txDone.Store(true)
timeout.Store(false)
led.Set(false)
}
if irqStatus&sx128x.IRQ_RX_DONE_MASK != 0 {
rxDone.Store(true)
timeout.Store(false)
led.Set(true)
println("rx done")
}
if irqStatus&sx128x.IRQ_RX_TX_TIMEOUT_MASK != 0 {
timeout.Store(true)
txDone.Store(true)
rxDone.Store(true)
println("rx/tx timeout")
}
dio1Interrupt.Store(false)
}
if rxDone.Load() {
rxDone.Store(false)
if !timeout.Load() {
rxLength, rxPointer, _ := radio.GetRxBufferStatus()
rxData, _ := radio.ReadBuffer(rxPointer, rxLength)
// num, err := conn.Write(rxData)
// if err != nil {
// println("failed to send OSC message:", err)
// } else {
// println("sent", num, "bytes to broker")
// }
println("received message:", string(rxData))
led.Set(false)
}
Rx(radio, loraConfig)
println("radio set to receive")
} else {
if dio1Pin.Get() {
irqStatus, _ := radio.GetIrqStatus()
if irqStatus&sx128x.IRQ_TX_DONE_MASK != 0 {
txDone.Store(true)
timeout.Store(false)
led.Set(false)
}
if irqStatus&sx128x.IRQ_RX_DONE_MASK != 0 {
rxDone.Store(true)
timeout.Store(false)
led.Set(true)
}
if irqStatus&sx128x.IRQ_RX_TX_TIMEOUT_MASK != 0 {
timeout.Store(true)
txDone.Store(true)
rxDone.Store(true)
}
}
}
}
}
func SetupLora(radio *sx128x.Device, config LoRaConfig) {
// Switch to standby prior to configuration changes
circuitMode, _, _ := radio.GetStatus()
if circuitMode != sx128x.CIRCUIT_MODE_STDBY_RC {
radio.SetStandby(sx128x.STANDBY_RC)
}
// Clear errors, disable radio interrupts for the moment
radio.SetPacketType(sx128x.PACKET_TYPE_LORA)
radio.SetCadParams(uint8(sx128x.LORA_CAD_08_SYMBOLS))
radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC)
radio.SetRfFrequency(config.Frequency)
radio.SetModulationParamsLoRa(config.SpreadingFactor, config.Bandwidth, config.CodingRate)
data := [1]uint8{}
if config.SpreadingFactor == lora.SpreadingFactor5 || config.SpreadingFactor == lora.SpreadingFactor6 {
data[0] = 0x1E
} else if config.SpreadingFactor == lora.SpreadingFactor7 || config.SpreadingFactor == lora.SpreadingFactor8 {
data[0] = 0x37
} else {
data[0] = 0x32
}
radio.WriteRegister(0x925, data[:])
existing, _ := radio.ReadRegister(0x93C)
data[0] = existing | 0x01
radio.WriteRegister(0x93C, data[:])
radio.SetTxParams(config.Power, sx128x.RADIO_RAMP_02_US)
radio.SetPacketParamsLoRa(config.PreambleLength, config.HeaderType, 0xFF, config.CrcType, config.IqType)
var syncWord [2]uint8
syncWord[0] = uint8(config.SyncWord >> 8)
syncWord[1] = uint8(config.SyncWord & 0x00FF)
radio.WriteRegister(sx128x.REG_LORA_SYNC_WORD_MSB, syncWord[:])
}
func checkStatus(radio *sx128x.Device, operation string) {
circuitMode, commandStatus, _ := radio.GetStatus()
if commandStatus != sx128x.COMMAND_STATUS_SUCCESS {
println(operation, "-> failed with status:", commandStatus)
}
if circuitMode != sx128x.CIRCUIT_MODE_STDBY_RC {
println(operation, "-> entered circuit mode:", circuitMode)
}
}
func Rx(radio *sx128x.Device, loraConfig LoRaConfig) {
radio.SetStandby(sx128x.STANDBY_XOSC)
radio.SetBufferBaseAddress(0, 0)
radio.SetDioIrqParams(sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00)
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
err := radio.SetRx(sx128x.PERIOD_BASE_4_MS, 250)
if err != nil {
println("failed to set RX mode:", err)
}
}