first commit

This commit is contained in:
soypat
2023-05-07 17:49:05 -03:00
parent 144f7080ab
commit 42d795e62d
7 changed files with 669 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
*.c
c/*
.vscode
+54
View File
@@ -0,0 +1,54 @@
package main
import (
"machine"
"time"
"github.com/soypat/lora"
"github.com/soypat/lora/sx127x"
)
var (
SX127X_SPI = machine.SPI0
)
const (
SX127X_PIN_RST = machine.GP16
// SPI definition for SX127x
SX127X_PIN_SCK = machine.GP2
SX127X_PIN_TX = machine.GP3
SX127X_PIN_RX = machine.GP4
SX127X_PIN_CS = machine.GP5
)
func main() {
time.Sleep(500 * time.Millisecond)
defer println("program end")
SX127X_PIN_RST.Configure(machine.PinConfig{Mode: machine.PinOutput})
SX127X_PIN_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
err := SX127X_SPI.Configure(machine.SPIConfig{
Frequency: 100000,
SCK: SX127X_PIN_SCK,
SDO: SX127X_PIN_TX,
SDI: SX127X_PIN_RX,
})
if err != nil {
panic(err.Error())
}
dev := sx127x.NewLoRa(SX127X_SPI, SX127X_PIN_CS.Set, SX127X_PIN_RST.Set)
err = dev.Configure(lora.Config{
Bandwidth: lora.BW125k,
Frequency: lora.Freq433_0M,
CodingRate: lora.CR4_5,
SpreadFactor: lora.SF7,
HeaderType: lora.HeaderExplicit,
TxPower: 0,
CRC: true,
PreambleLength: 12,
})
if err != nil {
panic(err.Error())
}
println("config success")
}
+3
View File
@@ -0,0 +1,3 @@
module github.com/soypat/lora
go 1.19
+130
View File
@@ -0,0 +1,130 @@
package lora
import "time"
type Config struct {
// Bandwidth relates to data rate of communication. Double the bandwidth means
// double the data rate, which implies faster communications and less energy usage.
Bandwidth Frequency
Frequency Frequency
// Length of the preamble preceding the header. Can be arbitrarily long.
// Longer preambles ensure more robust communications but can lead to congestion.
//
// Note: The preamble length on a receiver should be configured equal-to or greater
// than the expected packet preamble length on the SX1278.
PreambleLength uint16
HeaderType HeaderType
CodingRate CodingRate
SpreadFactor SpreadFactor
SyncWord uint8
TxPower int8 // Tx Power in dBm.
CRC bool
// Low data rate optimisation flag. The use of this flag is mandated when
// the symbol duration exceeds 16ms. Increases reliability at high spreading factors.
LDRO bool
IQMode uint8
}
func (cfg *Config) TimeOnAir(payloadLength int) time.Duration {
if cfg.Bandwidth == 0 {
return 0
}
crc := int64(b2u8(cfg.CRC))
ih := int64(cfg.HeaderType)
ldr := int64(b2u8(cfg.LDRO))
cr := int64(cfg.CodingRate)
spread := int64(cfg.SpreadFactor)
// Page 31 SX1276IMLTRT SEMTECH | Alldatasheet.
Npayload := 8*int64(payloadLength) - 4*spread + 28 + 16*crc - 20*ih
div := 4 * (spread - 2*ldr)
// Apply Ceil and max with minimal branching.
if Npayload < 0 || div <= 0 {
Npayload = 0
} else if Npayload%div == 0 {
Npayload /= div
Npayload *= (cr + 4)
} else {
Npayload /= div
Npayload++
Npayload *= (cr + 4)
}
Npayload += 8 + int64(cfg.PreambleLength) + 5 // Says 4.25 in manual but we round up.
// Calculate LoRa Transmission Parameter Relationship page 28.
Ts_us := 1000_000 * cfg.SpreadFactor.ChipsPerSymbol() / cfg.Bandwidth.Hertz()
return time.Microsecond * (time.Duration(Npayload * Ts_us))
}
type HeaderType uint8
const (
HeaderExplicit HeaderType = 0
HeaderImplicit HeaderType = 1
)
type CodingRate uint8
const (
CR4_5 CodingRate = 1
CR4_6 CodingRate = 2
CR4_7 CodingRate = 3
CR4_8 CodingRate = 4
)
type SpreadFactor uint8
const (
SF6 SpreadFactor = iota + 6
SF7
SF8
SF9
SF10
SF11
SF12
)
func (sf SpreadFactor) ChipsPerSymbol() int64 {
return 1 << sf
}
type Frequency int64
func (f Frequency) Hertz() int64 { return int64(f) }
const (
Hertz Frequency = 1
KiloHertz Frequency = 1000 * Hertz
MegaHertz Frequency = 1000 * KiloHertz
)
// Common LoRa bandwidths
const (
BW125k = 125 * KiloHertz
BW250k = 250 * KiloHertz
BW500k = 500 * KiloHertz
)
// Common LoRa frequencies
const (
// 433.05MHz Low limit medical, scientific and industrial band.
Freq433_0M = 433050000 * Hertz
// 434.8MHz High limit medical, scientific and industrial band.
Freq434_8M = 434790000 * Hertz
Freq868_1M = 868100000 * Hertz
Freq868_5M = 868500000 * Hertz
Freq916_8M = 916800000 * Hertz
Freq923_3M = 923300000 * Hertz
)
func max[T ~int | ~int64 | ~uint8](a, b T) T {
if a > b {
return a
}
return b
}
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}
+60
View File
@@ -0,0 +1,60 @@
package lora_test
import (
"math"
"testing"
"time"
"github.com/soypat/lora"
)
func TestTimeOnAir(t *testing.T) {
const (
loraWANCodeRate = lora.CR4_5
loraWANPreambleLength = 12
)
testCases := []struct {
desc string
expected time.Duration
plen int
cfg lora.Config
}{
{
desc: "LoRaWAN 240bytes", // https://www.thethingsnetwork.org/airtime-calculator
cfg: lora.Config{
Bandwidth: lora.BW125k,
Frequency: 915e6,
SpreadFactor: lora.SF7,
HeaderType: lora.HeaderExplicit,
CodingRate: loraWANCodeRate,
CRC: true,
PreambleLength: loraWANPreambleLength,
},
plen: 240,
expected: 394.5e3 * time.Microsecond,
},
{
desc: "LoRaWAN 5byte BW=500kHz", // https://www.thethingsnetwork.org/airtime-calculator
cfg: lora.Config{
Bandwidth: lora.BW500k,
Frequency: 915e6,
SpreadFactor: lora.SF7,
HeaderType: lora.HeaderExplicit,
CodingRate: loraWANCodeRate,
CRC: true,
PreambleLength: loraWANPreambleLength,
},
plen: 10,
expected: 15.4e3 * time.Microsecond,
},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
got := tC.cfg.TimeOnAir(tC.plen)
expect := tC.expected.Seconds()
if math.Abs(got.Seconds()-expect) > 10*expect/100 { // TODO: improve accuracy of TimeOnAir
t.Errorf("%s: got %s, expected %s", tC.desc, got.String(), tC.expected.String())
}
})
}
}
+190
View File
@@ -0,0 +1,190 @@
package sx127x
import "github.com/soypat/lora"
const (
// 32MHz frequency for crystal oscillator is typical.
fXOSC = 32_000_000
// PLL uses a 19-bit sigma-delta modulator whose frequency resolution, constant over the whole frequency range, is given by
fSTEP = fXOSC / (1 << 19)
)
const (
// registers
regFIFO = 0x00
regOP_MODE = 0x01
regFRF_MSB = 0x06
regFRF_MID = 0x07
regFRF_LSB = 0x08
regPA_CONFIG = 0x09
regPA_RAMP = 0x0a
regOCP = 0x0b
regLNA = 0x0c
regFIFO_ADDR_PTR = 0x0d
regFIFO_TX_BASE_ADDR = 0x0e
regFIFO_RX_BASE_ADDR = 0x0f
regFIFO_RX_CURRENT_ADDR = 0x10
regIRQ_FLAGS_MASK = 0x11
regIRQ_FLAGS = 0x12
regRX_NB_BYTES = 0x13
regPKT_SNR_VALUE = 0x19
regPKT_RSSI_VALUE = 0x1a
regRSSI_VALUE = 0x1b
regMODEM_CONFIG_1 = 0x1d
regMODEM_CONFIG_2 = 0x1e
regSYMB_TIMEOUT_LSB = 0x1f
regPREAMBLE_MSB = 0x20
regPREAMBLE_LSB = 0x21
regPAYLOAD_LENGTH = 0x22
regMAX_PAYLOAD_LENGTH = 0x23
regHOP_PERIOD = 0x24
regMODEM_CONFIG_3 = 0x26
regFREQ_ERROR_MSB = 0x28
regFREQ_ERROR_MID = 0x29
regFREQ_ERROR_LSB = 0x2a
regRSSI_WIDEBAND = 0x2c
regDETECTION_OPTIMIZE = 0x31
regINVERTIQ = 0x33
regDETECTION_THRESHOLD = 0x37
regSYNC_WORD = 0x39
regINVERTIQ2 = 0x3b
regDIO_MAPPING_1 = 0x40
regDIO_MAPPING_2 = 0x41
regVERSION = 0x42
regPA_DAC = 0x4d
// PA config
paBOOST = 0x80
expectedVersion = 0x12
// Bits masking the corresponding IRQs from the radio
irqRXTOUT_MASK uint8 = 0x80
irqRXDONE_MASK uint8 = 0x40
irqCRCERR_MASK uint8 = 0x20
irqHEADER_MASK uint8 = 0x10
irqTXDONE_MASK uint8 = 0x08
irqCDDONE_MASK uint8 = 0x04
irqFHSSCH_MASK uint8 = 0x02
irqCDDETD_MASK uint8 = 0x01
// DIO function mappings D0D1D2D3
mapDIO0_LORA_RXDONE uint8 = 0x00 // 00------
mapDIO0_LORA_TXDONE uint8 = 0x40 // 01------
mapDIO1_LORA_RXTOUT uint8 = 0x00 // --00----
mapDIO1_LORA_NOP uint8 = 0x30 // --11----
mapDIO2_LORA_NOP uint8 = 0xC0 // ----11--
// SX127X_PAYLOAD_LENGTH uint8 = 0x40
// Low Noise Amp
lnaMAX_GAIN uint8 = 0x23
lnaOFF_GAIN uint8 = 0x00
lnaLOW_GAIN uint8 = 0x20
// Bandwidth
bw7_8 uint8 = 0x00
bw10_4 uint8 = 0x01
bw15_6 uint8 = 0x02
bw20_8 uint8 = 0x03
bw31_25 uint8 = 0x04
bw41_7 uint8 = 0x05
bw62_5 uint8 = 0x06
bw125_0 uint8 = 0x07
bw250_0 uint8 = 0x08
bw500_0 uint8 = 0x09
// Automatic gain control
agcAUTO_OFF uint8 = 0x00
agcAUTO_ON uint8 = 0x01
publicSyncword = 0x34
privateSyncword = 0x14
)
func bwReg(bandwidth lora.Frequency) (value byte) {
switch {
case bandwidth <= 7.8e3*lora.Hertz:
value = bw7_8
case bandwidth <= 10.4e3*lora.Hertz:
value = bw10_4
case bandwidth <= 15.6e3*lora.Hertz:
value = bw15_6
case bandwidth <= 20.8e3*lora.Hertz:
value = bw20_8
case bandwidth <= 31.25e3*lora.Hertz:
value = bw31_25
case bandwidth <= 41.7e3*lora.Hertz:
value = bw41_7
case bandwidth <= 62.5e3*lora.Hertz:
value = bw62_5
case bandwidth <= 125*lora.KiloHertz:
value = bw125_0
case bandwidth <= 250e3*lora.KiloHertz:
value = bw250_0
default:
value = bw500_0
}
return value
}
// Operation modes.
const (
opmMASK uint8 = 0x07
opmSLEEP uint8 = 0x00
opmSTANDBY uint8 = 0x01
opmFSTX uint8 = 0x02
opmTX uint8 = 0x03
opmFSRX uint8 = 0x04
opmRX uint8 = 0x05
opmRX_SINGLE uint8 = 0x06
opmCAD uint8 = 0x07
opmLORA uint8 = 0x80
)
// OpMode represents the available operation modes of the SX127x devices.
type OpMode uint8
// LoRa Operation modes.
const (
// Sleep mode will sleep goroutine for 15ms on SetOpmode call.
OpSleep OpMode = iota
// Standby mode is the default mode when waiting to operate.
OpStandby
// Frequency synthesis TX (FSTX)
OpFSTx
// Transmit (TX)
OpTx
// Frequency synthesis RX (FSRX)
OpFSRx
// Receive continuous (RXCONTINUOUS)
OpRx
// receive single (RXSINGLE)
OpRxSingle
// Channel activity detection (CAD)
OpCAD
opLoRaBit = OpMode(1 << 7)
)
func (op OpMode) String() (s string) {
op = op &^ opLoRaBit
switch op {
case OpSleep:
s = "sleep"
case OpStandby:
s = "standby"
case OpFSTx:
s = "fstx"
case OpTx:
s = "tx"
case OpFSRx:
s = "fsrx"
case OpRx:
s = "rx"
case OpRxSingle:
s = "rx-single"
case OpCAD:
s = "cad"
default:
s = "unknown"
}
return s
}
+213
View File
@@ -0,0 +1,213 @@
package sx127x
import (
"encoding/binary"
"errors"
"time"
"github.com/soypat/lora"
)
// TODO: page 82: To minimize the current consumption of the SX1276/77/78/79, please ensure that the CLKOUT signal is disabled when not required.
type PinOutput func(level bool)
type SPI interface {
Tx(writeBuffer, readBuffer []byte) error
}
type DeviceLoRa struct {
rst PinOutput
cs PinOutput
bus SPI
}
func NewLoRa(bus SPI, cs, reset PinOutput) *DeviceLoRa {
d := DeviceLoRa{bus: bus, cs: cs, rst: reset}
return &d
}
func (d *DeviceLoRa) Configure(cfg lora.Config) error {
switch {
case cfg.SpreadFactor < lora.SF6 || cfg.SpreadFactor > lora.SF12:
return errors.New("bad spread factor")
case cfg.SpreadFactor == lora.SF6 && cfg.HeaderType != lora.HeaderImplicit:
return errors.New("SF6 can only be used with implicit header type") // Page 30: Implicit Header Mode.
case cfg.PreambleLength < 6:
return errors.New("preamble length too short")
}
d.Reset()
// We need to be in sleep mode to set LoRa mode if in FSK/OOK.
d.write8(regOP_MODE, opmSLEEP) // No need to check error, do it in SetOpmode.
if !d.IsConnected() {
return errors.New("sx127x not detected")
}
err := d.SetOpMode(OpSleep)
if err != nil {
return err
}
err = d.setTxPower(cfg.TxPower)
if err != nil {
return err
}
return nil
}
func (d *DeviceLoRa) SetOpMode(mode OpMode) error {
// We always write the LoRa mode bit
err := d.write8(regOP_MODE, byte(mode|opLoRaBit))
if err != nil {
return err
}
if mode == OpSleep {
time.Sleep(15 * time.Millisecond) // TODO: do we need this sleep?
}
got, err := d.GetOpMode()
if err != nil {
return err
}
if got != mode {
return errors.New("tried to set opmode " + mode.String() + ", got " + got.String())
}
return nil
}
func (d *DeviceLoRa) GetOpMode() (OpMode, error) {
const invalidOpMode = 0xff
got, err := d.read8(regOP_MODE)
if err != nil {
return invalidOpMode, err
}
if got&byte(opLoRaBit) == 0 {
// if the LoRa mode bit is not set, which would mean the device is in
// FSK/OOK mode or disconnected.
return invalidOpMode, errors.New("device in FSK/OOK mode")
}
return OpMode(got & opmMASK), nil
}
// setPreambleLength defines number of preamble
func (d *DeviceLoRa) setPreambleLength(pLen uint16) error {
var buf [2]byte
binary.BigEndian.PutUint16(buf[:], pLen)
d.write8(regPREAMBLE_MSB, buf[0])
return d.write8(regPREAMBLE_LSB, buf[1])
}
// SetCrc Enable CRC generation and check on payload.
func (d *DeviceLoRa) enableCRC(enable bool) error {
return d.writeMasked8(regMODEM_CONFIG_2, 1<<2, b2u8(enable)<<2)
}
// setOCP defines Overload Current Protection configuration. It receives
// the max current (Imax) in milliamperes.
func (d *DeviceLoRa) setOCP(mA uint8) error {
const ocpEnabledMask = 1 << 5
if mA < 45 {
mA = 45 // Absolute minimum is 45mA.
}
var ocpTrim uint8
switch {
case mA <= 120: // Imax [mA] = 45 +5*OcpTrim
ocpTrim = (mA - 45) / 5
case mA <= 240: // Imax [mA] = -30 + 10*OcpTrim
ocpTrim = (mA + 30) / 10
default: // Imax = 240mA
ocpTrim = 27
}
return d.write8(regOCP, ocpEnabledMask|(0x1F&ocpTrim))
}
// setTxPower sets the transmit power without using te PA_BOOST.
func (d *DeviceLoRa) setTxPower(txPow int8) error {
if txPow >= 16 {
return errors.New("requested tx power exceeds capabilities without PA_BOOST")
}
// Pout=Pmax-(15-OutputPower)
// Pmax=10.8+0.6*MaxPower [dBm]
const Pmax = 0b111 // Use Pmax ceiling.
const PoutMask = 0b1111
Pout := Pmax - (15 - txPow)
if Pout < 0 {
Pout = 0
}
// This unsets PaSelect bit which switches mode of operation to RFO pin (limited to 14dBm power).
err := d.write8(regPA_CONFIG, (Pmax<<4)|(PoutMask&uint8(Pout)))
if err != nil {
return err
}
return d.write8(regOCP, 0) // TODO: Disable OCP?
}
// setFrequency sets the radio frequency.
func (d *DeviceLoRa) setFrequency(freq uint32) error {
var freqReg [3]byte
frf := freq / fSTEP // Page 82, 5.3.3 PLL.
freqReg[0] = byte(frf >> 16)
freqReg[1] = byte(frf >> 8)
freqReg[2] = byte(frf >> 0)
d.write8(regFRF_MSB, freqReg[0])
d.write8(regFRF_MID, freqReg[1])
// Note pg82: A change in the center frequency will only be taken into account when the
// least significant byte FrfLsb in RegFrfLsb is written.
return d.write8(regFRF_LSB, freqReg[2]) // Write LSB last!
}
func (d *DeviceLoRa) writeMasked8(addr uint8, mask, value byte) error {
if value != 0 && value&^mask != 0 {
panic("misuse of writeMasked8") // Bug in this package if hit.
}
existing, err := d.read8(addr)
if err != nil {
return err
}
existing &^= mask // remove mask bits from register value.
existing |= mask & value // add value's bits as masked.
return d.write8(addr, existing)
}
func (d *DeviceLoRa) read8(addr byte) (byte, error) {
readBuf := make([]byte, 2)
writeBuf := []byte{addr &^ (1 << 7), 0} // unset write bit. Should be pretty much useless though we play it safe.
d.csEnable(true)
err := d.bus.Tx(writeBuf, readBuf)
d.csEnable(false)
return readBuf[1], err
}
func (d *DeviceLoRa) write8(addr, value byte) error {
readBuf := make([]byte, 2)
writeBuf := []byte{addr | (1 << 7), value} // set write bit.
d.csEnable(true)
err := d.bus.Tx(writeBuf, readBuf)
d.csEnable(false)
return err
}
func (d *DeviceLoRa) csEnable(b bool) {
d.cs(!b)
}
func (d *DeviceLoRa) Reset() {
d.rst(true)
time.Sleep(200 * time.Millisecond)
d.rst(false)
time.Sleep(200 * time.Millisecond)
d.rst(true)
time.Sleep(200 * time.Millisecond)
}
func (d *DeviceLoRa) IsConnected() bool {
version, err := d.read8(regVERSION)
if version == expectedVersion && err == nil {
return true
}
return false
}
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}