diff --git a/lilygo-t3s3-sx1280-lora2udp/go.mod b/lilygo-t3s3-sx1280-lora2udp/go.mod new file mode 100644 index 0000000..c900def --- /dev/null +++ b/lilygo-t3s3-sx1280-lora2udp/go.mod @@ -0,0 +1,18 @@ +module lilygot3s3 + +go 1.26.2 + +require ( + github.com/jwetzell/osc-go v0.3.0 + tinygo.org/x/drivers v0.35.0 + tinygo.org/x/espradio v0.1.0 +) + +require ( + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/soypat/lneto v0.1.1-0.20260425023453-aa77403a2b32 // indirect +) + +replace tinygo.org/x/drivers => /Users/jwetzell/Projects/drivers + +replace tinygo.org/x/espradio => /Users/jwetzell/Projects/espradio diff --git a/lilygo-t3s3-sx1280-lora2udp/go.sum b/lilygo-t3s3-sx1280-lora2udp/go.sum new file mode 100644 index 0000000..2e0f30e --- /dev/null +++ b/lilygo-t3s3-sx1280-lora2udp/go.sum @@ -0,0 +1,6 @@ +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/jwetzell/osc-go v0.3.0 h1:z75TxuQSEmdcmZ56OAepkDa3m88SdZh//3m4nBb/XZI= +github.com/jwetzell/osc-go v0.3.0/go.mod h1:kCs329JxY6Qjga08tRQ/Gl0PqhgQzLIMpOhm6uszvIc= +github.com/soypat/lneto v0.1.1-0.20260425023453-aa77403a2b32 h1:4fNa4mcNPVTMJ3Kzn+cS/LqmpXhKM9SiZ9Vgu/Ucq3c= +github.com/soypat/lneto v0.1.1-0.20260425023453-aa77403a2b32/go.mod h1:Be5PjwoYukvHFiUXxpYi8+ppH2F/gw/vjGBvFdv+Ti8= diff --git a/lilygo-t3s3-sx1280-lora2udp/main.go b/lilygo-t3s3-sx1280-lora2udp/main.go new file mode 100644 index 0000000..0dc6d09 --- /dev/null +++ b/lilygo-t3s3-sx1280-lora2udp/main.go @@ -0,0 +1,279 @@ +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 +) + +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) + + dio1Pin.SetInterrupt(machine.PinRising, func(machine.Pin) { + 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") + } + }) + + loraConfig := lora.Config{ + Freq: 2400000000, + Bw: lora.Bandwidth_1625_0, + Sf: lora.SpreadingFactor9, + Cr: lora.CodingRate4_7, + HeaderType: sx128x.LORA_EXPLICIT_HEADER, + Preamble: 12, + Ldr: lora.LowDataRateOptimizeOff, + Iq: sx128x.LORA_IQ_STD, + Crc: sx128x.LORA_CRC_DISABLE, + SyncWord: 0x1424, + LoraTxPowerDBm: 13, + } + + radio.WaitWhileBusy() + SetupLora(radio, loraConfig) + println("radio initialized, waiting for messages...") + + for { + runtime.Gosched() + 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") + } + + 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 lora.Config) { + // 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(sx128x.LORA_CAD_08_SYMBOLS) + radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC) + + radio.SetRfFrequency(config.Freq) + radio.SetModulationParams(spreadingFactor(config.Sf), bandwidth(config.Bw), codingRate(config.Cr)) + + data := [1]uint8{} + if config.Sf == lora.SpreadingFactor5 || config.Sf == lora.SpreadingFactor6 { + data[0] = 0x1E + } else if config.Sf == lora.SpreadingFactor7 || config.Sf == 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.LoraTxPowerDBm, sx128x.RADIO_RAMP_02_US) + radio.SetPacketParamsLoRa(uint32(config.Preamble), config.HeaderType, 0xFF, config.Crc, config.Iq) + 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 lora.Config) { + 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) + } +} + +func codingRate(cr uint8) uint8 { + switch cr { + case lora.CodingRate4_5: + return sx128x.LORA_CR_4_5 + case lora.CodingRate4_6: + return sx128x.LORA_CR_4_6 + case lora.CodingRate4_7: + return sx128x.LORA_CR_4_7 + case lora.CodingRate4_8: + return sx128x.LORA_CR_4_8 + default: + return 0 + } +} + +func spreadingFactor(sf uint8) uint8 { + switch sf { + case lora.SpreadingFactor5: + return sx128x.LORA_SF_5 + case lora.SpreadingFactor6: + return sx128x.LORA_SF_6 + case lora.SpreadingFactor7: + return sx128x.LORA_SF_7 + case lora.SpreadingFactor8: + return sx128x.LORA_SF_8 + case lora.SpreadingFactor9: + return sx128x.LORA_SF_9 + case lora.SpreadingFactor10: + return sx128x.LORA_SF_10 + case lora.SpreadingFactor11: + return sx128x.LORA_SF_11 + case lora.SpreadingFactor12: + return sx128x.LORA_SF_12 + default: + return 0 + } +} + +func bandwidth(bw uint8) uint8 { + switch bw { + case lora.Bandwidth_1625_0: + return sx128x.LORA_BW_1600 + case lora.Bandwidth_812_5: + return sx128x.LORA_BW_800 + case lora.Bandwidth_406_25: + return sx128x.LORA_BW_400 + case lora.Bandwidth_203_125: + return sx128x.LORA_BW_200 + default: + return 0 + } +}