diff --git a/examples/lora/sx127x/pingpong.go b/examples/lora/sx127x/pingpong.go new file mode 100644 index 0000000..8e10a14 --- /dev/null +++ b/examples/lora/sx127x/pingpong.go @@ -0,0 +1,127 @@ +// Receives data with LoRa. +package main + +import ( + "fmt" + "machine" + "strconv" + "strings" + + "tinygo.org/x/drivers/lora/sx127x" +) + +var loraConfig = sx127x.Config{ + Frequency: 868100000, + SpreadingFactor: 12, + Bandwidth: 125000, + CodingRate: 6, + TxPower: 17, +} + +const ( + + // Serial console + UART_TX_PIN = machine.PA9 + UART_RX_PIN = machine.PA10 + + // RFM95 SPI Connection to Bluepill + SPI_SCK_PIN = machine.PA5 + SPI_SDO_PIN = machine.PA7 + SPI_SDI_PIN = machine.PA6 + SPI_CS_PIN = machine.PB8 + SPI_RST_PIN = machine.PB9 + + // DIO RFM95 Pin connection to BluePill + DIO0_PIN = machine.PA2 + DIO0_PIN_MODE = machine.PinInputPulldown + DIO0_PIN_CHANGE = machine.PinRising + + pollDelayMs int = 1000 +) + +// configureDioInt sets up the DIO0 interrupt handler +func configureDioInt(radio *sx127x.Device) { + // Set an interrupt on this pin. + err := DIO0_PIN.SetInterrupt(DIO0_PIN_CHANGE, func(machine.Pin) { + if DIO0_PIN.Get() { + radio.CheckIrq() + } + }) + if err != nil { + println("could not configure pin interrupt:", err.Error()) + } +} + +func main() { + // Configure serial console + machine.Serial.Configure(machine.UARTConfig{TX: UART_TX_PIN, RX: UART_RX_PIN, BaudRate: 115200}) + + println("LoRa PingPong Example") + + // Prepare GPIOS (SPI + DIO) + machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + SPI_CS_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) + SPI_RST_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) + DIO0_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + machine.LED.Set(true) + + // Enable SPI + machine.SPI0.Configure(machine.SPIConfig{ + SCK: SPI_SCK_PIN, + SDO: SPI_SDO_PIN, + SDI: SPI_SDI_PIN, + Frequency: 500000, + Mode: 0}) + + // Create and Reset Lora driver + loraRadio := sx127x.New(machine.SPI0, SPI_CS_PIN, SPI_RST_PIN) + loraRadio.Reset() + + // Setup DIO0 interrupt Handling + configureDioInt(&loraRadio) + + // Configure Lora settings (modulation, SF... etc ) + var err = loraRadio.SetupLora(loraConfig) + if err != nil { + fmt.Println(err) + return + } + + // ping counter is incremented after every retransmission + pingCount := 0 + + // Send first packet without using interrupts + println("*** Send a Lora PING ") + machine.LED.Set(false) + loraRadio.TxLora([]byte("PING " + strconv.Itoa(pingCount))) + machine.LED.Set(true) + + println("*** Switch to RX Mode ") + for { + loraRadio.RxLora() + // Wait for Radio event + in := <-loraRadio.GetRadioEventChan() + + // We have a packet + if in.EventType == sx127x.EventRxDone { + data := string(in.EventData) + println("RX: ", data) + + machine.LED.Set(false) + // If it's ping, reply pong + if strings.Contains(data, "PING") { + println("Received PING, Sending PONG") + loraRadio.TxLora([]byte("PONG #" + strconv.Itoa(pingCount))) + } else if strings.Contains(data, "PONG") { + println("Received PONG, Sending PING") + loraRadio.TxLora([]byte("PING #" + strconv.Itoa(pingCount))) + } + machine.LED.Set(true) + + pingCount++ + + } + + } +} diff --git a/examples/lora/sx127x/receiver.go b/examples/lora/sx127x/receiver.go new file mode 100644 index 0000000..2a3b7af --- /dev/null +++ b/examples/lora/sx127x/receiver.go @@ -0,0 +1,50 @@ +// Receives data with LoRa. +package main + +import ( + "fmt" + "machine" + + "tinygo.org/x/drivers/lora/sx127x" +) + +var loraConfig = sx127x.Config{ + Frequency: 433998500, + SpreadingFactor: 7, + Bandwidth: 125000, + CodingRate: 6, + TxPower: 17, +} + +var packet [255]byte + +func main() { + println("LoRa Receiver Example") + + // SPI settings for Feather M0 LoRa board + csPin := machine.PB1 + csPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + rstPin := machine.PB2 + rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + dio0Pin := machine.PB3 + dio0Pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + machine.SPI0.Configure(machine.SPIConfig{}) + loraRadio := sx127x.New(machine.SPI0, csPin, rstPin) + var err = loraRadio.SetupLora(loraConfig) + if err != nil { + fmt.Println(err) + return + } + + println("Receiving LoRa packets...") + + for { + packetSize := loraRadio.ParsePacket(0) + if packetSize > 0 { + println("Got packet, RSSI=", loraRadio.LastPacketRSSI()) + size := loraRadio.ReadPacket(packet[:]) + println(string(packet[:size])) + } + } +} diff --git a/examples/lora/sx127x/sender.go b/examples/lora/sx127x/sender.go new file mode 100644 index 0000000..47bf4d1 --- /dev/null +++ b/examples/lora/sx127x/sender.go @@ -0,0 +1,88 @@ +// Sends data with LoRa. +package main + +import ( + "fmt" + "machine" + "strconv" + "time" + + "tinygo.org/x/drivers/lora/sx127x" +) + +var loraConfig = sx127x.Config{ + Frequency: 868100000, + SpreadingFactor: 12, + Bandwidth: 125000, + CodingRate: 6, + TxPower: 17, +} + +const ( + SPI_SCK_PIN = machine.PA5 + SPI_SDO_PIN = machine.PA7 + SPI_SDI_PIN = machine.PA6 + + SPI_CS_PIN = machine.PB8 + SPI_RST_PIN = machine.PB9 + DIO0_PIN = machine.PA2 + + UART_TX_PIN = machine.PA9 + UART_RX_PIN = machine.PA10 +) + +func main() { + + // Configure serial console + machine.Serial.Configure(machine.UARTConfig{TX: UART_TX_PIN, RX: UART_RX_PIN, BaudRate: 115200}) + + println("LoRa Sender Example") + + // Prepare GPIOS (SPI + DIO) + machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + SPI_CS_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) + SPI_RST_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) + DIO0_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + // Enable SPI + machine.SPI0.Configure(machine.SPIConfig{ + SCK: SPI_SCK_PIN, + SDO: SPI_SDO_PIN, + SDI: SPI_SDI_PIN, + Frequency: 500000, + Mode: 0}) + + // Create and Reset Lora driver + loraRadio := sx127x.New(machine.SPI0, SPI_CS_PIN, SPI_RST_PIN) + loraRadio.Reset() + + // Check module identification + if loraRadio.GetVersion() != 0x12 { + println("SX1276 module not found") + for { + } + } else { + println("Module version 0x12") + } + + // Configure Lora settings (modulation, SF... etc ) + var err = loraRadio.SetupLora(loraConfig) + if err != nil { + fmt.Println(err) + return + } + + // Send a Lora packet every 5 sec + println("Sending LoRa packets every 5 seconds...") + for i := 0; ; i++ { + var packet = "TinyGo LoRa Sender: " + strconv.Itoa(i) + println("Sending:", packet) + + machine.LED.Low() + time.Sleep(time.Millisecond * 150) + machine.LED.High() + + loraRadio.TxLora([]byte(packet)) + time.Sleep(5000 * time.Millisecond) + } +} diff --git a/examples/sx127x/lora_rxtx/lora_rxtx.go b/examples/sx127x/lora_rxtx/lora_rxtx.go new file mode 100644 index 0000000..55a1edf --- /dev/null +++ b/examples/sx127x/lora_rxtx/lora_rxtx.go @@ -0,0 +1,124 @@ +package main + +// This example code enable continuous Wave/Preamble + +import ( + "machine" + "time" + + "tinygo.org/x/drivers/sx127x" +) + +const ( + FREQ = 868100000 +) + +var ( + loraRadio *sx127x.Device + + // We assume the module is connected this way: + SX127X_PIN_RST = machine.PB9 + SX127X_PIN_CS = machine.PB8 + SX127X_PIN_DIO0 = machine.PA0 + SX127X_SPI = machine.SPI0 +) + +func main() { + println("\n# TinyGo Lora continuous Wave/Preamble test") + println("# -----------------------------------------") + + machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + // SPI, RESET, CS configuration + SX127X_PIN_RST.Configure(machine.PinConfig{Mode: machine.PinOutput}) + SX127X_PIN_CS.Configure(machine.PinConfig{Mode: machine.PinOutput}) + SX127X_PIN_DIO0.Configure(machine.PinConfig{Mode: machine.PinInputPulldown}) // Checkthat + SX127X_SPI.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0}) + + // Create the driver + loraRadio = sx127x.New(SX127X_SPI, SX127X_PIN_CS, SX127X_PIN_RST) + + err := SX127X_PIN_DIO0.SetInterrupt(machine.PinRising, func(machine.Pin) { + loraRadio.HandleInterrupt() + }) + if err != nil { + panic("Can't configure DIO int handler") + } + + // Reset the radio module + loraRadio.Reset() + + println("Try to detect sx127x radio") + state := loraRadio.DetectDevice() + if !state { + panic("sx127x not detected. ") + } else { + println("sx127x detected") + } + + /* + // Prepare for Lora operation + loraConf := sx126x.LoraConfig{ + Freq: FREQ, + Bw: sx126x.SX126X_LORA_BW_500_0, + Sf: sx126x.SX126X_LORA_SF9, + Cr: sx126x.SX126X_LORA_CR_4_7, + HeaderType: sx126x.SX126X_LORA_HEADER_EXPLICIT, + Preamble: 12, + Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF, + Iq: sx126x.SX126X_LORA_IQ_STANDARD, + Crc: sx126x.SX126X_LORA_CRC_ON, + SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD, + LoraTxPowerDBm: 14, + } + loraRadio.LoraConfig(loraConf) + */ + + println("Start Configure Lora") + loraRadio.SetOpModeLora() + loraRadio.SetOpMode(sx127x.SX127X_OPMODE_SLEEP) + loraRadio.SetTxPower(11, true) + loraRadio.SetFrequency(FREQ) + loraRadio.SetBandwidth(sx127x.SX127X_LORA_BW_125_0) + loraRadio.SetCodingRate(sx127x.SX127X_LORA_CR_4_5) + loraRadio.SetRxPayloadCrc(sx127x.SX127X_LORA_CRC_ON) + loraRadio.SetHeaderMode(sx127x.SX127X_LORA_HEADER_EXPLICIT) + loraRadio.SetLowDataRateOptim(sx127x.SX127X_LOW_DATARATE_OPTIM_OFF) + println("End Configure Lora") + + for { + loraRadio.TxLora([]byte("Hello")) + machine.LED.Set(!machine.LED.Get()) + time.Sleep(time.Second * 2) + } + /* + + + // Although LoraConfig has already configured most of Lora settings, + // the following lines are still required to enable Continuous Preamble/Wave + loraRadio.SetPacketType(sx126x.SX126X_PACKET_TYPE_LORA) + loraRadio.SetRfFrequency(loraConf.Freq) + loraRadio.SetModulationParams(loraConf.Sf, loraConf.Bw, loraConf.Cr, loraConf.Ldr) + loraRadio.SetTxParams(loraConf.LoraTxPowerDBm, sx126x.SX126X_PA_RAMP_200U) + + for { + println("2 seconds in Continuous Preamble") + loraRadio.SetStandby() + loraRadio.SetTxContinuousPreamble() + time.Sleep(2 * time.Second) + println("Continuous Preamble Stopped") + + loraRadio.SetStandby() + time.Sleep(10 * time.Second) + + println("2 seconds in Continuous Wave") + loraRadio.SetTxContinuousWave() + time.Sleep(2 * time.Second) + println(" Continuous Wave Stopped") + + loraRadio.SetStandby() + time.Sleep(60 * time.Second) + + } + */ +} diff --git a/sx127x/registers.go b/sx127x/registers.go new file mode 100644 index 0000000..d025077 --- /dev/null +++ b/sx127x/registers.go @@ -0,0 +1,124 @@ +package sx127x + +const ( + // registers + REG_FIFO = 0x00 + REG_OP_MODE = 0x01 + REG_FRF_MSB = 0x06 + REG_FRF_MID = 0x07 + REG_FRF_LSB = 0x08 + REG_PA_CONFIG = 0x09 + REG_PA_RAMP = 0x0a + REG_OCP = 0x0b + REG_LNA = 0x0c + 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_MASK = 0x11 + REG_IRQ_FLAGS = 0x12 + REG_RX_NB_BYTES = 0x13 + REG_PKT_SNR_VALUE = 0x19 + REG_PKT_RSSI_VALUE = 0x1a + REG_RSSI_VALUE = 0x1b + REG_MODEM_CONFIG_1 = 0x1d + REG_MODEM_CONFIG_2 = 0x1e + REG_SYMB_TIMEOUT_LSB = 0x1f + REG_PREAMBLE_MSB = 0x20 + REG_PREAMBLE_LSB = 0x21 + REG_PAYLOAD_LENGTH = 0x22 + REG_MAX_PAYLOAD_LENGTH = 0x23 + REG_HOP_PERIOD = 0x24 + REG_MODEM_CONFIG_3 = 0x26 + REG_FREQ_ERROR_MSB = 0x28 + REG_FREQ_ERROR_MID = 0x29 + REG_FREQ_ERROR_LSB = 0x2a + REG_RSSI_WIDEBAND = 0x2c + REG_DETECTION_OPTIMIZE = 0x31 + REG_INVERTIQ = 0x33 + REG_DETECTION_THRESHOLD = 0x37 + REG_SYNC_WORD = 0x39 + REG_INVERTIQ2 = 0x3b + REG_DIO_MAPPING_1 = 0x40 + REG_DIO_MAPPING_2 = 0x41 + REG_VERSION = 0x42 + REG_PA_DAC = 0x4d + + // PA config + PA_BOOST = 0x80 + + // Bits masking the corresponding IRQs from the radio + IRQ_LORA_RXTOUT_MASK = uint8(0x80) + IRQ_LORA_RXDONE_MASK = uint8(0x40) + IRQ_LORA_CRCERR_MASK = uint8(0x20) + IRQ_LORA_HEADER_MASK = uint8(0x10) + IRQ_LORA_TXDONE_MASK = uint8(0x08) + IRQ_LORA_CDDONE_MASK = uint8(0x04) + IRQ_LORA_FHSSCH_MASK = uint8(0x02) + IRQ_LORA_CDDETD_MASK = uint8(0x01) + + // DIO function mappings D0D1D2D3 + MAP_DIO0_LORA_RXDONE = uint8(0x00) // 00------ + MAP_DIO0_LORA_TXDONE = uint8(0x40) // 01------ + MAP_DIO1_LORA_RXTOUT = uint8(0x00) // --00---- + MAP_DIO1_LORA_NOP = uint8(0x30) // --11---- + MAP_DIO2_LORA_NOP = uint8(0xC0) // ----11-- + + PAYLOAD_LENGTH = uint8(0x40) + + // Low Noise Amp + LNA_MAX_GAIN = uint8(0x23) + LNA_OFF_GAIN = uint8(0x00) + LNA_LOW_GAIN = uint8(0x20) + + // Header type + SX127X_LORA_HEADER_EXPLICIT = uint8(0x00) + SX127X_LORA_HEADER_IMPLICIT = uint8(0x01) + // CRC + SX127X_LORA_CRC_OFF = uint8(0x00) + SX127X_LORA_CRC_ON = uint8(0x01) + // IQ Polarity + SX127X_LORA_IQ_STANDARD = uint8(0x00) + SX127X_LORA_IQ_INVERTED = uint8(0x01) + // Coding Rate + SX127X_LORA_CR_4_5 = uint8(0x01) + SX127X_LORA_CR_4_6 = uint8(0x02) + SX127X_LORA_CR_4_7 = uint8(0x03) + SX127X_LORA_CR_4_8 = uint8(0x04) + // Bandwidth + SX127X_LORA_BW_7_8 = uint8(0x00) + SX127X_LORA_BW_10_4 = uint8(0x01) + SX127X_LORA_BW_15_6 = uint8(0x02) + SX127X_LORA_BW_20_8 = uint8(0x03) + SX127X_LORA_BW_31_25 = uint8(0x04) + SX127X_LORA_BW_41_7 = uint8(0x05) + SX127X_LORA_BW_62_5 = uint8(0x06) + SX127X_LORA_BW_125_0 = uint8(0x07) + SX127X_LORA_BW_250_0 = uint8(0x08) + SX127X_LORA_BW_500_0 = uint8(0x09) + // Spreading factor + SX127X_LORA_SF6 = uint8(0x06) + SX127X_LORA_SF7 = uint8(0x07) + SX127X_LORA_SF8 = uint8(0x08) + SX127X_LORA_SF9 = uint8(0x09) + SX127X_LORA_SF10 = uint8(0x0A) + SX127X_LORA_SF11 = uint8(0x0B) + SX127X_LORA_SF12 = uint8(0x0C) + // Optim Low DR + SX127X_LOW_DATARATE_OPTIM_OFF = uint8(0x00) + SX127X_LOW_DATARATE_OPTIM_ON = uint8(0x01) + // Automatic gain control + SX127X_AGC_AUTO_OFF = uint8(0x00) + SX127X_AGC_AUTO_ON = uint8(0x01) + // Operation modes + SX127X_OPMODE_LORA = uint8(0x80) + SX127X_OPMODE_MASK = uint8(0x07) + SX127X_OPMODE_SLEEP = uint8(0x00) + SX127X_OPMODE_STANDBY = uint8(0x01) + SX127X_OPMODE_FSTX = uint8(0x02) + SX127X_OPMODE_TX = uint8(0x03) + SX127X_OPMODE_FSRX = uint8(0x04) + SX127X_OPMODE_RX = uint8(0x05) + SX127X_OPMODE_RX_SINGLE = uint8(0x06) + SX127X_OPMODE_CAD = uint8(0x07) +) diff --git a/sx127x/sx127x.go b/sx127x/sx127x.go new file mode 100644 index 0000000..0946050 --- /dev/null +++ b/sx127x/sx127x.go @@ -0,0 +1,645 @@ +// Package sx127x provides a driver for SX127x LoRa transceivers. +// References: +// https://electronics.stackexchange.com/questions/394296/can-t-get-simple-lora-receiver-to-work +// https://www.st.com/resource/en/user_manual/dm00300436-stm32-lora-expansion-package-for-stm32cube-stmicroelectronics.pdf +package sx127x + +import ( + "errors" + "machine" + "time" + + "tinygo.org/x/drivers" +) + +const ( + RadioEventRxDone = iota + RadioEventTxDone = iota + RadioEventTimeout = iota + RadioEventWatchdog = iota + RadioEventCrcError = iota + RadioEventUnhandled = iota +) + +// So we can keep track of the origin of interruption +const ( + SPI_BUFFER_SIZE = 256 +) + +// RadioEvent are used for communicating in the radio Event Channel +type RadioEvent struct { + EventType int + IRQStatus uint8 + EventData []byte +} + +// Device wraps an SPI connection to a SX127x device. +type Device struct { + spi drivers.SPI // SPI bus for module communication + rstPin, csPin machine.Pin // GPIOs for reset and chip select + radioEventChan chan RadioEvent // Channel for Receiving events + loraConf LoraConfig // Current Lora configuration + deepSleep bool // Internal Sleep state + deviceType int // sx1261,sx1262,sx1268 (defaults sx1261) + spiBuffer [SPI_BUFFER_SIZE]uint8 + packetIndex uint8 // FIXME ... useless ? +} + +// Config holds the LoRa configuration parameters +type LoraConfig struct { + Freq uint32 // Frequency + Cr uint8 // Coding Rate + Sf uint8 // Spread Factor + Bw uint8 // Bandwidth + Ldr uint8 // Low Data Rate + Preamble uint16 // PreambleLength + SyncWord uint16 // Sync Word + HeaderType uint8 // Header : Implicit/explicit + Crc uint8 // CRC : Yes/No + Iq uint8 // iq : Standard/inverted + LoraTxPowerDBm int8 // Tx power in Dbm +} + +// -------------------------------------------------- +// Channel and events +// -------------------------------------------------- +//NewRadioEvent() returns a new RadioEvent that can be used in the RadioChannel +func NewRadioEvent(eType int, irqStatus uint8, eData []byte) RadioEvent { + r := RadioEvent{EventType: eType, IRQStatus: irqStatus, EventData: eData} + return r +} + +// Get the RadioEvent channel of the device +func (d *Device) GetRadioEventChan() chan RadioEvent { + return d.radioEventChan +} + +// New creates a new SX127x connection. The SPI bus must already be configured. +func New(spi machine.SPI, csPin machine.Pin, rstPin machine.Pin) *Device { + k := Device{ + spi: spi, + csPin: csPin, + rstPin: rstPin, + radioEventChan: make(chan RadioEvent, 10), + } + return &k +} + +// Reset re-initialize the sx127x device +func (d *Device) Reset() { + d.rstPin.Low() + time.Sleep(100 * time.Millisecond) + d.rstPin.High() + time.Sleep(100 * time.Millisecond) +} + +// DetectDevice checks if device responds on the SPI bus +func (d *Device) DetectDevice() bool { + id := d.GetVersion() + return (id == 0x12) +} + +// ReadRegister reads register value +func (d *Device) ReadRegister(reg uint8) uint8 { + d.csPin.Low() + d.spi.Tx([]byte{reg & 0x7f}, nil) + var value [1]byte + d.spi.Tx(nil, value[:]) + d.csPin.High() + return value[0] +} + +// WriteRegister writes value to register +func (d *Device) WriteRegister(reg uint8, value uint8) uint8 { + var response [1]byte + d.csPin.Low() + d.spi.Tx([]byte{reg | 0x80}, nil) + d.spi.Tx([]byte{value}, response[:]) + d.csPin.High() + return response[0] +} + +// SetOpMode changes the sx1276 mode +func (d *Device) SetOpMode(mode uint8) { + cur := d.ReadRegister(REG_OP_MODE) + new := (cur & (^SX127X_OPMODE_MASK)) | mode + d.WriteRegister(REG_OP_MODE, new) +} + +// SetOpMode changes the sx1276 mode +func (d *Device) SetOpModeLora() { + d.WriteRegister(REG_OP_MODE, SX127X_OPMODE_LORA) +} + +// SetupLora configures sx127x Lora mode +func (d *Device) SetupLora(config LoraConfig) error { + + d.loraConf = config + + // Reset the device first + d.Reset() + + // Switch to Lora mode + d.SetOpModeLora() + d.SetOpMode(SX127X_OPMODE_SLEEP) + + // Access High Frequency Mode + d.SetLowFrequencyModeOn(false) + + // Set PA Ramp time 50 uS + d.WriteRegister(REG_PA_RAMP, (d.ReadRegister(REG_PA_RAMP)&0xF0)|0x08) // set PA ramp-up time 50 uSec + + // Enable power (manage Over Current, PA_Boost ... etc) + d.SetTxPower(11, true) + + // Set Low Noise Amplifier to MAX + d.WriteRegister(REG_LNA, LNA_MAX_GAIN) + + // Set Frequency + d.SetFrequency(d.loraConf.Freq) + + // Set Bandwidth + d.SetBandwidth(d.loraConf.Bw) + + //Set Coding Rate (TODO : Check) + d.SetCodingRate(d.loraConf.Cr) + + // Set explicit header + d.SetHeaderMode(SX127X_LORA_HEADER_EXPLICIT) + + // Enable CRC + d.SetRxPayloadCrc(SX127X_LORA_CRC_ON) + + // Disable IQ Polarization + d.SetIQPolarity(SX127X_LORA_IQ_STANDARD) + + // Disable HOP PERIOD + d.SetHopPeriod(0x00) + + // Continuous Mode + d.SetTxContinuousMode(false) + + // Set Lora Sync + d.SetSyncWord(0x34) + + //Set Max payload length (default value) + d.WriteRegister(REG_MAX_PAYLOAD_LENGTH, 0xFF) + // Mandatory in Implicit header Mode (default value) + d.WriteRegister(REG_PAYLOAD_LENGTH, 0x01) + + // AGC On + d.SetAgcAuto(SX127X_AGC_AUTO_ON) + + // set FIFO base addresses + d.WriteRegister(REG_FIFO_TX_BASE_ADDR, 0) + d.WriteRegister(REG_FIFO_RX_BASE_ADDR, 0) + return nil +} + +// TxLora sends a packet in Lora mode +// Intmode will enable interrupt mode. +// If disabled, function will probe registers for TXDone before +// returning +func (d *Device) TxLora(payload []byte) error { + + // Are we already in Lora mode ? + r := d.ReadRegister(REG_OP_MODE) + if (r & SX127X_OPMODE_LORA) != SX127X_OPMODE_LORA { + return errors.New("Not in Lora mode") + } + + // set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP + d.WriteRegister(REG_DIO_MAPPING_1, MAP_DIO0_LORA_TXDONE|MAP_DIO1_LORA_NOP|MAP_DIO2_LORA_NOP) + // Clear all radio IRQ Flags + d.WriteRegister(REG_IRQ_FLAGS, 0xFF) + // Mask all but TxDone + d.WriteRegister(REG_IRQ_FLAGS_MASK, ^IRQ_LORA_TXDONE_MASK) + + // initialize the payload size and address pointers + d.WriteRegister(REG_FIFO_TX_BASE_ADDR, 0) + d.WriteRegister(REG_FIFO_ADDR_PTR, 0) + d.WriteRegister(REG_PAYLOAD_LENGTH, uint8(len(payload))) + + // Copy payload to FIFO // TODO: Bulk + for i := 0; i < len(payload); i++ { + d.WriteRegister(REG_FIFO, payload[i]) + } + + // Enable TX + d.SetOpMode(SX127X_OPMODE_TX) + + msg := <-d.GetRadioEventChan() + if msg.EventType != RadioEventTxDone { + return errors.New("Unexpected Radio Event while TX") + } + return nil +} + +/* +//CheckIrq can be called periodicaly to check for RXDONE,TXDONE,RXTOUT +//but It would be more efficient to call it on DIO0/1 pins rising edge +func (d *Device) CheckIrq() { + + irqFlags := d.ReadRegister(REG_IRQ_FLAGS) + //println("sx21276: irq=", irqFlags) + + // We have a packet + if (irqFlags & IRQ_LORA_RXDONE_MASK) > 0 { + //println("sx1276: RXDONE") + // Read current packet + buf := []byte{} + packetLength := d.ReadRegister(REG_RX_NB_BYTES) + d.WriteRegister(REG_FIFO_ADDR_PTR, d.ReadRegister(REG_FIFO_RX_CURRENT_ADDR)) // Reset FIFO Read Addr + for i := uint8(0); i < packetLength; i++ { + buf = append(buf, d.ReadRegister(REG_FIFO)) + } + // Send RXDONE to the defined event channel + d.radioEventChan <- RadioEvent{EventType: EventRxDone, EventData: buf} + } + if (irqFlags & IRQ_LORA_TXDONE_MASK) > 0 { + //println("sx1276: TXDONE") + d.radioEventChan <- RadioEvent{EventType: EventTxDone, EventData: nil} + } + if (irqFlags & IRQ_LORA_RXTOUT_MASK) > 0 { + //println("sx1276: RXTOUT") + d.radioEventChan <- RadioEvent{EventType: EventRxTimeout, EventData: nil} + } + + // Sigh: on some processors, for some unknown reason, doing this only once does not actually + // clear the radio's interrupt flag. So we do it twice. Why? + d.WriteRegister(REG_IRQ_FLAGS, irqFlags) // Clear all IRQ flags + d.WriteRegister(REG_IRQ_FLAGS, irqFlags) // Clear all IRQ flags +} +*/ + +/* +// SetRadioEventChan defines a channel so the driver can send its Radio Events +func (d *Device) SetRadioEventChan(channel chan RadioEvent) { + d.radioEventChan = channel +} +*/ +/* +// Init reboots the SX1276 module +func (d *Device) Init(cfg LoraConfig) (err error) { + d.loraConf = cfg + d.csPin.High() + d.Reset() + return nil +} +*/ +/* + +// ConfigureLoraModem prepares for LORA communications +func (d *Device) ConfigureLoraModem() { + + // Sleep mode required to go LOra + d.OpMode(OPMODE_SLEEP) + // Set Lora mode (from sleep) + d.OpModeLora() + // Switch to standby mode + d.OpMode(OPMODE_STANDBY) + // Set Bandwidth + d.SetBandwidth(d.cnf.Bandwidth) + // Disable IQ Polarization + d.SetInvertedIQ(false) + // Set implicit header + d.SetImplicitHeaderModeOn(false) + // We want CRC + d.SetRxPayloadCrc(true) + d.SetAgcAutoOn(true) + if d.GetBandwidth() == 125000 && (d.GetSpreadingFactor() == 11 || d.GetSpreadingFactor() == 12) { + d.SetLowDataRateOptimOn(true) + } + + // Configure Output Power + d.WriteRegister(REG_PA_RAMP, (d.ReadRegister(REG_PA_RAMP)&0xF0)|0x08) // set PA ramp-up time 50 uSec + d.WriteRegister(REG_PA_CONFIG, 0xFF) //PA_BOOST MAX + d.SetOCP(140) // Over Current protection + + // RX and premamble + d.WriteRegister(REG_PREAMBLE_MSB, 0x00) // Preamble set to 8 symp + d.WriteRegister(REG_PREAMBLE_LSB, 0x08) // -> 0x0008 + 4 = 12 + d.WriteRegister(REG_SYMB_TIMEOUT_LSB, 0x25) //Rx Timeout 37 symbol + + // set FIFO base addresses + d.WriteRegister(REG_FIFO_TX_BASE_ADDR, 0) + d.WriteRegister(REG_FIFO_RX_BASE_ADDR, 0) +} +*/ +//GetVersion returns hardware version of sx1276 chipset +func (d *Device) GetVersion() uint8 { + return (d.ReadRegister(REG_VERSION)) +} + +// IsTransmitting tests if a packet transmission is in progress +func (d *Device) IsTransmitting() bool { + return (d.ReadRegister(REG_OP_MODE) & SX127X_OPMODE_TX) == SX127X_OPMODE_TX +} + +// ReadPacket reads a received packet into a byte array +func (d *Device) ReadPacket(packet []byte) int { + available := int(d.ReadRegister(REG_RX_NB_BYTES) - d.packetIndex) + if available > len(packet) { + available = len(packet) + } + + for i := 0; i < available; i++ { + d.packetIndex++ + packet[i] = d.ReadRegister(REG_FIFO) + } + + return available +} + +// LastPacketRSSI gives the RSSI of the last packet received +func (d *Device) LastPacketRSSI() uint8 { + // section 5.5.5 + var adjustValue uint8 = 157 + if d.loraConf.Freq < 868000000 { + adjustValue = 164 + } + return d.ReadRegister(REG_PKT_RSSI_VALUE) - adjustValue +} + +// LastPacketSNR gives the SNR of the last packet received +func (d *Device) LastPacketSNR() uint8 { + return uint8(d.ReadRegister(REG_PKT_SNR_VALUE) / 4) +} + +/* +// GetFrequency returns the frequency the LoRa module is using +func (d *Device) GetFrequency() uint32 { + f := uint64(d.ReadRegister(REG_FRF_LSB)) + f += uint64(d.ReadRegister(REG_FRF_MID)) << 8 + f += uint64(d.ReadRegister(REG_FRF_MSB)) << 16 + f = (f * 32000000) >> 19 //FSTEP = FXOSC/2^19 + return uint32(f) +} +*/ +// SetFrequency updates the frequency the LoRa module is using +func (d *Device) SetFrequency(frequency uint32) { + d.loraConf.Freq = frequency + var frf = (uint64(frequency) << 19) / 32000000 + d.WriteRegister(REG_FRF_MSB, uint8(frf>>16)) + d.WriteRegister(REG_FRF_MID, uint8(frf>>8)) + d.WriteRegister(REG_FRF_LSB, uint8(frf>>0)) +} + +// GetSpreadingFactor returns the spreading factor the LoRa module is using +func (d *Device) GetSpreadingFactor() uint8 { + return d.ReadRegister(REG_MODEM_CONFIG_2) >> 4 +} + +// GetRSSI returns current RSSI +func (d *Device) GetRSSI() uint8 { + return d.ReadRegister(REG_RSSI_VALUE) +} + +/* +// GetBandwidth returns the bandwidth the LoRa module is using +func (d *Device) GetBandwidth() int32 { + return int32(d.loraConf.Bw) +} +*/ + +//SetSyncWord defines sync word +func (d *Device) SetSyncWord(syncWord uint8) { + d.WriteRegister(REG_SYNC_WORD, syncWord) +} + +// SetIQPolarity Sets I/Q polarity configuration +func (d *Device) SetIQPolarity(val uint8) { + if val == SX127X_LORA_IQ_INVERTED { + //Invert IQ Back + d.WriteRegister(0x33, 0x67) + d.WriteRegister(0x3B, 0x19) + } else { + //Set IQ to normal values + d.WriteRegister(0x33, 0x27) + d.WriteRegister(0x3B, 0x1D) + } +} + +// RxLora sets device in receive mode +func (d *Device) RxLora() { + // set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP + d.WriteRegister(REG_DIO_MAPPING_1, MAP_DIO0_LORA_RXDONE|MAP_DIO1_LORA_NOP|MAP_DIO2_LORA_NOP) + // Clear all radio IRQ Flags + d.WriteRegister(REG_IRQ_FLAGS, 0xFF) + // Mask all but TxDone + d.WriteRegister(REG_IRQ_FLAGS_MASK, ^IRQ_LORA_RXDONE_MASK) + + d.SetOpMode(SX127X_OPMODE_RX) // RX Mode +} + +/* +// setLdoFlag() enables LowDataRateOptimize bit (mandated when symbol length >16ms) +// LGTM +func (d *Device) setLdoFlag() { + // Section 4.1.1.5 + var symbolDuration = 1000 / (d.GetBandwidth() / (1 << d.GetSpreadingFactor())) + + var config3 = d.ReadRegister(REG_MODEM_CONFIG_3) + + // Section 4.1.1.6 + if symbolDuration > 16 { + config3 = config3 | 0x08 + } else { + config3 = config3 & 0xF7 + } + + d.WriteRegister(REG_MODEM_CONFIG_3, config3) +} +*/ + +// SetTxPower sets the transmitter output power +func (d *Device) SetTxPower(txPower int8, paBoost bool) { + if !paBoost { + // RFO + if txPower < 0 { + txPower = 0 + } else if txPower > 14 { + txPower = 14 + } + d.WriteRegister(REG_PA_CONFIG, uint8(0x70)|uint8(txPower)) + + } else { + //PA_BOOST + if txPower > 17 { + if txPower > 20 { + txPower = 20 + } + + txPower -= 3 + + // High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.) + d.WriteRegister(REG_PA_DAC, 0x87) + d.SetOCP(140) + } else { + if txPower < 2 { + txPower = 2 + } + + d.WriteRegister(REG_PA_DAC, 0x84) + d.SetOCP(100) + + } + + d.WriteRegister(REG_PA_CONFIG, uint8(PA_BOOST)|uint8(txPower-2)) + + } +} + +// SetRxTimeout defines RX Timeout expressed as number of symbols +func (d *Device) SetRxTimeout(tmoutSymb uint8) { + d.WriteRegister(REG_SYMB_TIMEOUT_LSB, tmoutSymb) +} + +// SetOCP defines Overload Current Protection configuration +func (d *Device) SetOCP(mA uint8) { + + ocpTrim := uint8(27) + + if mA < 45 { + mA = 45 + } + if mA <= 120 { + ocpTrim = (mA - 45) / 5 + } else if mA <= 240 { + ocpTrim = (mA + 30) / 10 + } + + d.WriteRegister(REG_OCP, 0x20|(0x1F&ocpTrim)) +} + +// --------------- +// RegModemConfig1 +// --------------- + +// SetBandwidth updates the bandwidth the LoRa module is using +func (d *Device) SetBandwidth(bw uint8) { + d.loraConf.Bw = bw + d.WriteRegister(REG_MODEM_CONFIG_1, (d.ReadRegister(REG_MODEM_CONFIG_1)&0x0f)|(bw<<4)) +} + +// SetCodingRate updates the coding rate the LoRa module is using +func (d *Device) SetCodingRate(cr uint8) { + d.loraConf.Cr = cr + d.WriteRegister(REG_MODEM_CONFIG_1, (d.ReadRegister(REG_MODEM_CONFIG_1)&0xf1)|(cr<<1)) +} + +// SetImplicitHeaderModeOn Enables implicit header mode *** +func (d *Device) SetHeaderMode(headerType uint8) { + d.loraConf.HeaderType = headerType + if headerType == SX127X_LORA_HEADER_IMPLICIT { + d.WriteRegister(REG_MODEM_CONFIG_1, d.ReadRegister(REG_MODEM_CONFIG_1)|0x01) + } else { + d.WriteRegister(REG_MODEM_CONFIG_1, d.ReadRegister(REG_MODEM_CONFIG_1)&0xfe) + } +} + +// --------------- +// RegModemConfig2 +// --------------- + +// SetSpreadingFactor updates the spreading factor the LoRa module is using +func (d *Device) SetSpreadingFactor(sf uint8) { + d.loraConf.Sf = sf + if sf == SX127X_LORA_SF6 { + d.WriteRegister(REG_DETECTION_OPTIMIZE, 0xc5) + d.WriteRegister(REG_DETECTION_THRESHOLD, 0x0c) + } else { + d.WriteRegister(REG_DETECTION_OPTIMIZE, 0xc3) + d.WriteRegister(REG_DETECTION_THRESHOLD, 0x0a) + } + var newValue = (d.ReadRegister(REG_MODEM_CONFIG_2) & 0x0f) | ((sf << 4) & 0xf0) + d.WriteRegister(REG_MODEM_CONFIG_2, newValue) +} + +// SetTxContinuousMode enable Continuous Tx mode +func (d *Device) SetTxContinuousMode(val bool) { + if val { + d.WriteRegister(REG_MODEM_CONFIG_2, d.ReadRegister(REG_MODEM_CONFIG_2)|0x08) + } else { + d.WriteRegister(REG_MODEM_CONFIG_2, d.ReadRegister(REG_MODEM_CONFIG_2)&0xf7) + } +} + +// SetRxPayloadCrc Enable CRC generation and check on payload +func (d *Device) SetRxPayloadCrc(val uint8) { + if val == SX127X_LORA_CRC_ON { + d.WriteRegister(REG_MODEM_CONFIG_2, d.ReadRegister(REG_MODEM_CONFIG_2)|0x04) + } else { + d.WriteRegister(REG_MODEM_CONFIG_2, d.ReadRegister(REG_MODEM_CONFIG_2)&0xfb) + } +} + +// --------------- +// RegModemConfig3 +// --------------- + +// SetAgcAutoOn enables Automatic Gain Control +func (d *Device) SetAgcAuto(val uint8) { + if val == SX127X_AGC_AUTO_ON { + d.WriteRegister(REG_MODEM_CONFIG_3, d.ReadRegister(REG_MODEM_CONFIG_3)|0x04) + } else { + d.WriteRegister(REG_MODEM_CONFIG_3, d.ReadRegister(REG_MODEM_CONFIG_3)&0xfb) + } +} + +// SetLowDataRateOptimize enables Low Data Rate Optimization +func (d *Device) SetLowDataRateOptim(val uint8) { + if val == SX127X_LOW_DATARATE_OPTIM_ON { + d.WriteRegister(REG_MODEM_CONFIG_3, d.ReadRegister(REG_MODEM_CONFIG_3)|0x08) + } else { + d.WriteRegister(REG_MODEM_CONFIG_3, d.ReadRegister(REG_MODEM_CONFIG_3)&0xf7) + } +} + +// SetLowFrequencyModeOn enables Low Data Rate Optimization +func (d *Device) SetLowFrequencyModeOn(val bool) { + if val { + d.WriteRegister(REG_OP_MODE, d.ReadRegister(REG_OP_MODE)|0x04) + } else { + d.WriteRegister(REG_OP_MODE, d.ReadRegister(REG_OP_MODE)&0xfb) + } +} + +// SetHopPeriod sets number of symbol periods between frequency hops. (0 = disabled). +func (d *Device) SetHopPeriod(val uint8) { + d.WriteRegister(REG_HOP_PERIOD, val) +} + +// HandleInterrupt must be called by main code on DIO state change. +func (d *Device) HandleInterrupt() { + // Get IRQ and clear + st := d.ReadRegister(REG_IRQ_FLAGS) + d.WriteRegister(REG_IRQ_FLAGS, 0xFF) + + rChan := d.GetRadioEventChan() + + if (st & IRQ_LORA_RXDONE_MASK) > 0 { + rChan <- NewRadioEvent(RadioEventRxDone, st, nil) + } + + if (st & IRQ_LORA_TXDONE_MASK) > 0 { + rChan <- NewRadioEvent(RadioEventTxDone, st, nil) + } + + if (st & IRQ_LORA_RXTOUT_MASK) > 0 { + rChan <- NewRadioEvent(RadioEventTimeout, st, nil) + } + + if (st & IRQ_LORA_CRCERR_MASK) > 0 { + rChan <- NewRadioEvent(RadioEventCrcError, st, nil) + } +} + +// PrintRegisters outputs the sx127x transceiver registers +func (d *Device) PrintRegisters(compact bool) { + for i := uint8(0); i < 128; i++ { + v := d.ReadRegister(i) + print(v, " ") + } + println() +}