basic sx128x example for lilygo t3s3

This commit is contained in:
Joel Wetzell
2026-04-22 12:16:29 -05:00
parent e989c38eba
commit 876bafaa7c
3 changed files with 301 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
module lilygot3s3
go 1.26.2
require (
github.com/jwetzell/osc-go v0.3.0
tinygo.org/x/drivers v0.35.0
)
require github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
replace tinygo.org/x/drivers => /Users/jwetzell/Projects/drivers
+4
View File
@@ -0,0 +1,4 @@
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=
+285
View File
@@ -0,0 +1,285 @@
package main
import (
"errors"
"machine"
"sync/atomic"
"time"
"github.com/jwetzell/osc-go"
"tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx128x"
)
func main() {
time.Sleep(3 * time.Second)
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})
button := machine.GPIO0
button.Configure(machine.PinConfig{Mode: machine.PinInput})
buttonPressed := atomic.Bool{}
buttonPressed.Store(false)
button.SetInterrupt(machine.PinFalling, func(machine.Pin) {
println("button interrupt")
buttonPressed.Store(true)
})
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()
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)
}
})
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)
oscMessage := osc.OSCMessage{
Address: "/lora/button",
Args: []osc.OSCArg{
{
Type: "i",
Value: 0,
},
},
}
for {
if txDone.Load() && buttonPressed.Load() {
txDone.Store(false)
buttonPressed.Store(false)
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
radio.SetStandby(sx128x.STANDBY_RC)
led.Set(true)
oscMessage.Args[0].Value = int32(time.Now().UnixMilli() / 1000)
bytes, err := oscMessage.ToBytes()
if err != nil {
println("failed to serialize OSC message:", err)
continue
}
if len(bytes) > 255 {
println("OSC message exceeds maximum length of 255 bytes")
continue
}
err = Tx(radio, loraConfig, bytes)
if err != nil {
println("failed to enter transmit:", err)
}
println("transmit started")
}
// if rxDone.Load() {
// rxDone.Store(false)
// if !timeout.Load() {
// rxLength, rxPointer := radio.GetRxBufferStatus()
// rxData := radio.ReadBuffer(rxPointer, rxLength)
// oscMessage, err := osc.MessageFromBytes(rxData)
// if err != nil {
// println("failed to parse OSC message:", err)
// } else {
// println("received OSC message with address:", oscMessage.Address)
// for i, arg := range oscMessage.Args {
// println("arg", i, "type:", arg.Type, "value:", arg.Value)
// }
// }
// led.Set(false)
// }
// Rx(radio, loraConfig)
// }
}
}
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 Tx(radio *sx128x.Device, loraConfig lora.Config, data []byte) error {
if len(data) > 255 {
return errors.New("data length exceeds maximum of 255 bytes")
}
radio.SetStandby(sx128x.STANDBY_RC)
radio.SetPacketParamsLoRa(uint32(loraConfig.Preamble), loraConfig.HeaderType, uint8(len(data)&0xFF), loraConfig.Crc, loraConfig.Iq)
radio.SetBufferBaseAddress(0, 0)
radio.WriteBuffer(0, data)
radio.SetDioIrqParams(sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00)
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
radio.SetTx(sx128x.PERIOD_BASE_4_MS, 250)
return nil
}
func Rx(radio *sx128x.Device, loraConfig lora.Config) {
radio.SetStandby(sx128x.STANDBY_RC)
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.SetBufferBaseAddress(0, 0)
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
radio.SetRfFrequency(loraConfig.Freq)
radio.SetRx(sx128x.PERIOD_BASE_4_MS, 250)
}
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
}
}