mirror of
https://github.com/soypat/lora.git
synced 2026-07-26 07:28:38 +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