Files
drivers/sx127x/sx127x.go
T

541 lines
16 KiB
Go

// 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
}
var (
errUndefinedLoraConf = errors.New("Undefined Lora configuration")
)
// --------------------------------------------------
// 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(SX127X_REG_OP_MODE)
new := (cur & (^SX127X_OPMODE_MASK)) | mode
d.WriteRegister(SX127X_REG_OP_MODE, new)
}
// SetOpMode changes the sx1276 mode
func (d *Device) SetOpModeLora() {
d.WriteRegister(SX127X_REG_OP_MODE, SX127X_OPMODE_LORA)
}
//GetVersion returns hardware version of sx1276 chipset
func (d *Device) GetVersion() uint8 {
return (d.ReadRegister(SX127X_REG_VERSION))
}
// IsTransmitting tests if a packet transmission is in progress
func (d *Device) IsTransmitting() bool {
return (d.ReadRegister(SX127X_REG_OP_MODE) & SX127X_OPMODE_TX) == SX127X_OPMODE_TX
}
// 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(SX127X_REG_PKT_RSSI_VALUE) - adjustValue
}
// LastPacketSNR gives the SNR of the last packet received
func (d *Device) LastPacketSNR() uint8 {
return uint8(d.ReadRegister(SX127X_REG_PKT_SNR_VALUE) / 4)
}
// GetRSSI returns current RSSI
func (d *Device) GetRSSI() uint8 {
return d.ReadRegister(SX127X_REG_RSSI_VALUE)
}
/*
// GetBandwidth returns the bandwidth the LoRa module is using
func (d *Device) GetBandwidth() int32 {
return int32(d.loraConf.Bw)
}
*/
// 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(SX127X_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(SX127X_REG_PA_DAC, 0x87)
d.SetOCP(140)
} else {
if txPower < 2 {
txPower = 2
}
d.WriteRegister(SX127X_REG_PA_DAC, 0x84)
d.SetOCP(100)
}
d.WriteRegister(SX127X_REG_PA_CONFIG, uint8(SX127X_PA_BOOST)|uint8(txPower-2))
}
}
// ---------------
// Internal functions
// ---------------
// SetRxTimeout defines RX Timeout expressed as number of symbols
// Default timeout is 64 * Ts
func (d *Device) SetRxTimeout(tmoutSymb uint8) {
d.WriteRegister(SX127X_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(SX127X_REG_OCP, 0x20|(0x1F&ocpTrim))
}
// SetAgcAutoOn enables Automatic Gain Control
func (d *Device) SetAgcAuto(val uint8) {
if val == SX127X_AGC_AUTO_ON {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)|0x04)
} else {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_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(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)|0x08)
} else {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)&0xf7)
}
}
// SetLowFrequencyModeOn enables Low Data Rate Optimization
func (d *Device) SetLowFrequencyModeOn(val bool) {
if val {
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)|0x04)
} else {
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)&0xfb)
}
}
// SetHopPeriod sets number of symbol periods between frequency hops. (0 = disabled).
func (d *Device) SetHopPeriod(val uint8) {
d.WriteRegister(SX127X_REG_HOP_PERIOD, val)
}
//
// LORA FUNCTIONS
//
// LoraConfig() defines Lora configuration for next Lora operations
func (d *Device) LoraConfig(cnf LoraConfig) {
// Save given configuration
d.loraConf = cnf
}
// SetLoraFrequency updates the frequency the LoRa module is using
func (d *Device) SetLoraFrequency(frequency uint32) {
d.loraConf.Freq = frequency
var frf = (uint64(frequency) << 19) / 32000000
d.WriteRegister(SX127X_REG_FRF_MSB, uint8(frf>>16))
d.WriteRegister(SX127X_REG_FRF_MID, uint8(frf>>8))
d.WriteRegister(SX127X_REG_FRF_LSB, uint8(frf>>0))
}
// SetBandwidth updates the bandwidth the LoRa module is using
func (d *Device) SetLoraBandwidth(bw uint8) {
d.loraConf.Bw = bw
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, (d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0x0f)|(bw<<4))
}
// SetCodingRate updates the coding rate the LoRa module is using
func (d *Device) SetLoraCodingRate(cr uint8) {
d.loraConf.Cr = cr
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, (d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0xf1)|(cr<<1))
}
// SetImplicitHeaderModeOn Enables implicit header mode ***
func (d *Device) SetLoraHeaderMode(headerType uint8) {
d.loraConf.HeaderType = headerType
if headerType == SX127X_LORA_HEADER_IMPLICIT {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)|0x01)
} else {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0xfe)
}
}
// SetLoraSpreadingFactor changes spreading factor
func (d *Device) SetLoraSpreadingFactor(sf uint8) {
d.loraConf.Sf = sf
if sf == SX127X_LORA_SF6 {
d.WriteRegister(SX127X_REG_DETECTION_OPTIMIZE, 0xc5)
d.WriteRegister(SX127X_REG_DETECTION_THRESHOLD, 0x0c)
} else {
d.WriteRegister(SX127X_REG_DETECTION_OPTIMIZE, 0xc3)
d.WriteRegister(SX127X_REG_DETECTION_THRESHOLD, 0x0a)
}
var newValue = (d.ReadRegister(SX127X_REG_MODEM_CONFIG_2) & 0x0f) | ((sf << 4) & 0xf0)
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, newValue)
}
// SetTxContinuousMode enable Continuous Tx mode
func (d *Device) SetTxContinuousMode(val bool) {
if val {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)|0x08)
} else {
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)&0xf7)
}
}
// SetLoraCrc Enable CRC generation and check on payload
func (d *Device) SetLoraCrc(enable bool) {
if enable {
d.loraConf.Crc = SX127X_LORA_CRC_ON
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)|0x04)
} else {
d.loraConf.Crc = SX127X_LORA_CRC_OFF
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)&0xfb)
}
}
func (d *Device) SetLoraPreamble(pLen uint16) {
// Sets preamble length
d.WriteRegister(SX127X_REG_PREAMBLE_MSB, uint8((pLen>>8)&0xFF))
d.WriteRegister(SX127X_REG_PREAMBLE_LSB, uint8(pLen&0xFF))
}
//SetLoraSyncWord defines sync word
func (d *Device) SetLoraSyncWord(syncWord uint16) {
d.loraConf.SyncWord = syncWord
sw := uint8(syncWord & 0xFF)
d.WriteRegister(SX127X_REG_SYNC_WORD, sw)
}
// SetLoraIQMode Sets I/Q polarity configuration
func (d *Device) SetLoraIqMode(val uint8) {
d.loraConf.Iq = val
if val == SX127X_LORA_IQ_STANDARD {
//Set IQ to normal values
d.WriteRegister(SX127X_REG_INVERTIQ, 0x27)
d.WriteRegister(SX127X_REG_INVERTIQ2, 0x1D)
} else {
//Invert IQ Back
d.WriteRegister(SX127X_REG_INVERTIQ, 0x66)
d.WriteRegister(SX127X_REG_INVERTIQ2, 0x19)
}
}
// LoraTx sends a lora packet, (with timeout)
func (d *Device) LoraTx(pkt []uint8, timeoutMs uint32) error {
//println("sx127x: LoraTx:", len(pkt), " bytes", hex.EncodeToString(pkt))
d.SetOpModeLora()
d.SetOpMode(SX127X_OPMODE_SLEEP)
d.SetHopPeriod(0x00)
d.SetLowFrequencyModeOn(false) // High freq mode
d.WriteRegister(SX127X_REG_PA_RAMP, (d.ReadRegister(SX127X_REG_PA_RAMP)&0xF0)|0x08) // set PA ramp-up time 50 uSec
d.WriteRegister(SX127X_REG_LNA, SX127X_LNA_MAX_GAIN) // Set Low Noise Amplifier to MAX
d.SetLoraFrequency(d.loraConf.Freq)
d.SetLoraPreamble(d.loraConf.Preamble) //OK
d.SetLoraSyncWord(d.loraConf.SyncWord) // Should be ok
d.SetLoraBandwidth(d.loraConf.Bw) // OK
d.SetLoraSpreadingFactor(d.loraConf.Sf) // OK
d.SetLoraIqMode(d.loraConf.Iq) //OK
d.SetLoraCodingRate(d.loraConf.Cr)
d.SetLoraCrc(d.loraConf.Crc == SX127X_LORA_CRC_ON)
d.SetTxPower(10, true)
d.SetLoraHeaderMode(d.loraConf.HeaderType)
d.SetAgcAuto(SX127X_AGC_AUTO_ON)
// set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP
d.WriteRegister(SX127X_REG_DIO_MAPPING_1, SX127X_MAP_DIO0_LORA_TXDONE|SX127X_MAP_DIO1_LORA_NOP|SX127X_MAP_DIO2_LORA_NOP)
// Clear all radio IRQ Flags
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
// Mask all but TxDone
d.WriteRegister(SX127X_REG_IRQ_FLAGS_MASK, ^SX127X_IRQ_LORA_TXDONE_MASK)
// initialize the payload size and address pointers
d.WriteRegister(SX127X_REG_PAYLOAD_LENGTH, uint8(len(pkt)))
d.WriteRegister(SX127X_REG_FIFO_TX_BASE_ADDR, 0)
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, 0)
// FIFO OPs cannot take place in Sleep mode !!!
d.SetOpMode(SX127X_OPMODE_STANDBY)
time.Sleep(time.Millisecond)
// Copy payload to FIFO // TODO: Bulk
for i := 0; i < len(pkt); i++ {
d.WriteRegister(SX127X_REG_FIFO, pkt[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
}
// LoraRx tries to receive a Lora packet (with timeout in milliseconds)
func (d *Device) LoraRx(timeoutMs uint32) ([]uint8, error) {
if d.loraConf.Freq == 0 {
return nil, errUndefinedLoraConf
}
d.SetOpModeLora()
d.SetOpMode(SX127X_OPMODE_SLEEP)
d.SetHopPeriod(0x00)
d.SetLowFrequencyModeOn(false) // High freq mode
d.WriteRegister(SX127X_REG_PA_RAMP, (d.ReadRegister(SX127X_REG_PA_RAMP)&0xF0)|0x08) // set PA ramp-up time 50 uSec
d.WriteRegister(SX127X_REG_LNA, SX127X_LNA_MAX_GAIN) // Set Low Noise Amplifier to MAX
d.SetLoraFrequency(d.loraConf.Freq)
d.SetLoraPreamble(d.loraConf.Preamble) //OK
d.SetLoraSyncWord(d.loraConf.SyncWord) // Should be ok
d.SetLoraBandwidth(d.loraConf.Bw) // OK
d.SetLoraSpreadingFactor(d.loraConf.Sf) // OK
d.SetLoraIqMode(d.loraConf.Iq) //OK
d.SetLoraCodingRate(d.loraConf.Cr)
d.SetLoraCrc(d.loraConf.Crc == SX127X_LORA_CRC_ON)
d.SetTxPower(10, true)
d.SetLoraHeaderMode(d.loraConf.HeaderType)
d.SetAgcAuto(SX127X_AGC_AUTO_ON)
// set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP
d.WriteRegister(SX127X_REG_DIO_MAPPING_1, SX127X_MAP_DIO0_LORA_RXDONE|SX127X_MAP_DIO1_LORA_NOP|SX127X_MAP_DIO2_LORA_NOP)
// Clear all radio IRQ Flags
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
// Mask all but TxDone
d.WriteRegister(SX127X_REG_IRQ_FLAGS_MASK, ^SX127X_IRQ_LORA_RXDONE_MASK)
// Switch to RX Mode
d.SetOpMode(SX127X_OPMODE_RX)
// Wait for Radio Event
radioCh := d.GetRadioEventChan()
go func() {
time.Sleep(time.Millisecond * time.Duration(timeoutMs))
radioCh <- NewRadioEvent(RadioEventTimeout, SX127X_IRQ_LORA_RXTOUT_MASK, nil)
}()
msg := <-radioCh
if msg.EventType == RadioEventTimeout {
return nil, nil
} else if msg.EventType != RadioEventRxDone {
return nil, errors.New("Unexpected Radio Event while RX")
}
d.WriteRegister(SX127X_REG_FIFO_RX_BASE_ADDR, 0)
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, 0)
pLen := d.ReadRegister(SX127X_REG_RX_NB_BYTES)
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, d.ReadRegister(SX127X_REG_FIFO_RX_CURRENT_ADDR))
for i := uint8(0); i < pLen; i++ {
d.spiBuffer[i] = d.ReadRegister(SX127X_REG_FIFO)
}
return d.spiBuffer[:pLen], nil
}
//
// HELPER FUNCTIONS
//
// 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()
}
// PrintRegisters outputs the sx127x transceiver registers
func (d *Device) RandomU32() uint32 {
// Disable ALL irqs
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
d.SetOpModeLora()
d.SetOpMode(SX127X_OPMODE_SLEEP)
d.SetLoraFrequency(d.loraConf.Freq)
d.SetOpMode(SX127X_OPMODE_RX)
rnd := uint32(0)
for i := 0; i < 32; i++ {
time.Sleep(time.Millisecond * 10)
// Unfiltered RSSI value reading. Only takes the LSB value
rnd |= (uint32(d.ReadRegister(SX127X_REG_RSSI_WIDEBAND)) & 0x01) << i
}
return rnd
}
// HandleInterrupt must be called by main code on DIO state change.
func (d *Device) HandleInterrupt() {
// Get IRQ and clear
st := d.ReadRegister(SX127X_REG_IRQ_FLAGS)
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
rChan := d.GetRadioEventChan()
if (st & SX127X_IRQ_LORA_RXDONE_MASK) > 0 {
rChan <- NewRadioEvent(RadioEventRxDone, st, nil)
}
if (st & SX127X_IRQ_LORA_TXDONE_MASK) > 0 {
rChan <- NewRadioEvent(RadioEventTxDone, st, nil)
}
if (st & SX127X_IRQ_LORA_RXTOUT_MASK) > 0 {
rChan <- NewRadioEvent(RadioEventTimeout, st, nil)
}
if (st & SX127X_IRQ_LORA_CRCERR_MASK) > 0 {
rChan <- NewRadioEvent(RadioEventCrcError, st, nil)
}
}