diff --git a/lilygo-t3s3-sx128x/go.mod b/lilygo-t3s3-sx128x/go.mod new file mode 100644 index 0000000..66b6c02 --- /dev/null +++ b/lilygo-t3s3-sx128x/go.mod @@ -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 diff --git a/lilygo-t3s3-sx128x/go.sum b/lilygo-t3s3-sx128x/go.sum new file mode 100644 index 0000000..36d3664 --- /dev/null +++ b/lilygo-t3s3-sx128x/go.sum @@ -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= diff --git a/lilygo-t3s3-sx128x/main.go b/lilygo-t3s3-sx128x/main.go new file mode 100644 index 0000000..143ebf2 --- /dev/null +++ b/lilygo-t3s3-sx128x/main.go @@ -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 + } +}