mirror of
https://github.com/soypat/lora.git
synced 2026-08-21 12:58:59 +00:00
first commit
This commit is contained in:
+19
@@ -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
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user