mirror of
https://github.com/soypat/lora.git
synced 2026-08-20 12:28:59 +00:00
add tests for power management
This commit is contained in:
+1
-1
@@ -15,5 +15,5 @@
|
|||||||
# vendor/
|
# vendor/
|
||||||
*.c
|
*.c
|
||||||
c/*
|
c/*
|
||||||
|
*.log
|
||||||
.vscode
|
.vscode
|
||||||
@@ -51,6 +51,8 @@ func main() {
|
|||||||
// This blocks forever reading messages received.
|
// This blocks forever reading messages received.
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for {
|
for {
|
||||||
|
value, err := dev.ReadTemperature()
|
||||||
|
println("temperature:", value, err)
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
err = dev.RxContinuous(ctx, func(r io.Reader) (_ error) {
|
err = dev.RxContinuous(ctx, func(r io.Reader) (_ error) {
|
||||||
n, err := r.Read(rx[:])
|
n, err := r.Read(rx[:])
|
||||||
@@ -60,6 +62,7 @@ func main() {
|
|||||||
println("received LoRa:", string(rx[:n]))
|
println("received LoRa:", string(rx[:n]))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(err.Error())
|
println(err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -58,3 +58,24 @@ func TestTimeOnAir(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemperature(t *testing.T) {
|
||||||
|
for _, cs := range []struct {
|
||||||
|
tempVal uint8
|
||||||
|
expect uint8
|
||||||
|
celsius int
|
||||||
|
}{
|
||||||
|
{tempVal: 63, expect: 0, celsius: -40},
|
||||||
|
{tempVal: 33, expect: 36, celsius: -15},
|
||||||
|
{tempVal: 0, expect: 100, celsius: 15},
|
||||||
|
{tempVal: 245, expect: 128, celsius: 10},
|
||||||
|
{tempVal: 211, expect: 220, celsius: 55},
|
||||||
|
{tempVal: 181, expect: 255, celsius: 85},
|
||||||
|
} {
|
||||||
|
val := cs.tempVal
|
||||||
|
val -= 64
|
||||||
|
val = uint8(255 - 14*uint16(val)/8)
|
||||||
|
// val += 128
|
||||||
|
t.Errorf("regTemp=%d val=%d expect=%d celsius=%d", cs.tempVal, val, cs.expect, cs.celsius)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -156,6 +156,17 @@ const (
|
|||||||
privateSyncword = 0x14
|
privateSyncword = 0x14
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FSK/OOK registers.
|
||||||
|
const (
|
||||||
|
regImageCal = 0x3b
|
||||||
|
regTemp = 0x3c
|
||||||
|
|
||||||
|
// Values that would be read from regTemp:
|
||||||
|
lowTempVal uint8 = 64 // -40 C
|
||||||
|
medTempVal uint8 = 245 // +25 C
|
||||||
|
hiTempVal uint8 = 181 // +85 C
|
||||||
|
)
|
||||||
|
|
||||||
var bandwidths = [bw500_0 + 1]lora.Frequency{
|
var bandwidths = [bw500_0 + 1]lora.Frequency{
|
||||||
bw7_8: 7.8e3 * lora.Hertz,
|
bw7_8: 7.8e3 * lora.Hertz,
|
||||||
bw10_4: 10.4e3 * lora.Hertz,
|
bw10_4: 10.4e3 * lora.Hertz,
|
||||||
|
|||||||
+94
-33
@@ -89,7 +89,7 @@ func DefaultConfig(freq lora.Frequency) lora.Config {
|
|||||||
MaxImplicitPayloadLength: 0, // No need to be set when working with explicit headers.
|
MaxImplicitPayloadLength: 0, // No need to be set when working with explicit headers.
|
||||||
CRC: true,
|
CRC: true,
|
||||||
SyncWord: publicSyncword,
|
SyncWord: publicSyncword,
|
||||||
TxPower: 0,
|
TxPower: 0, // Low power by default.
|
||||||
LDRO: false,
|
LDRO: false,
|
||||||
IQInversion: false,
|
IQInversion: false,
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,8 @@ var (
|
|||||||
errSF6Implicit = errors.New("SF6 can only be used with implicit header type") // Page 30: Implicit Header Mode.
|
errSF6Implicit = errors.New("SF6 can only be used with implicit header type") // Page 30: Implicit Header Mode.
|
||||||
errPreambleTooShort = errors.New("preamble length too short")
|
errPreambleTooShort = errors.New("preamble length too short")
|
||||||
ErrNotDetected = errors.New("sx127x not detected")
|
ErrNotDetected = errors.New("sx127x not detected")
|
||||||
errBadMode = errors.New("bad mode: sx127x in FSK/OOK mode, not LoRa or viceversa")
|
errBadMode = errors.New("bad op mode: sx127x in FSK/OOK mode, not LoRa or viceversa")
|
||||||
|
errSharedMode = errors.New("bad op mode: sx127x in shared access mode")
|
||||||
errBadCodingRate = errors.New("bad coding rate")
|
errBadCodingRate = errors.New("bad coding rate")
|
||||||
errUnsupportedBandwidth = errors.New("bandwidth too high for frequency around 169MHz")
|
errUnsupportedBandwidth = errors.New("bandwidth too high for frequency around 169MHz")
|
||||||
errIRQNotCleared = errors.New("IRQs not cleared")
|
errIRQNotCleared = errors.New("IRQs not cleared")
|
||||||
@@ -133,8 +134,8 @@ func (d *DeviceLoRa) Configure(cfg lora.Config) (err error) {
|
|||||||
err = errors.New("bad header type")
|
err = errors.New("bad header type")
|
||||||
case cfg.HeaderType == lora.HeaderImplicit && cfg.MaxImplicitPayloadLength == 0:
|
case cfg.HeaderType == lora.HeaderImplicit && cfg.MaxImplicitPayloadLength == 0:
|
||||||
err = errors.New("MaxImplicitPayloadLength parameter must be set when working with implicit headers")
|
err = errors.New("MaxImplicitPayloadLength parameter must be set when working with implicit headers")
|
||||||
case cfg.TxPower > 20:
|
case cfg.TxPower > 20 || cfg.TxPower < -4:
|
||||||
err = errors.New("bad tx power")
|
err = errors.New("tx power not in operating range -4..20")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -268,8 +269,13 @@ func (d *DeviceLoRa) GetOpMode() (OpMode, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return invalidOpMode, err
|
return invalidOpMode, err
|
||||||
}
|
}
|
||||||
if got&byte(opLoRaBit) == 0 || // LongRangeMode bit influences operation.
|
// We expect the LoRa bit to be set, access shared reg bit to be unset.
|
||||||
got&(1<<6) != 0 { // AccessSharedReg bit allows access to FSK registers in LoRa mode, should not be set.
|
const forbiddenBits = opLoRaBit | (1 << 6)
|
||||||
|
if got&byte(forbiddenBits) != 0b10<<6 {
|
||||||
|
// d.write8(regOP_MODE, (got&opmMASK)|byte(opLoRaBit))// We force set a "correct" mode before returning the error.
|
||||||
|
if got&(1<<6) != 0 {
|
||||||
|
return invalidOpMode, errSharedMode // AccessSharedReg bit allows access to FSK registers in LoRa mode, should not be set.
|
||||||
|
}
|
||||||
// if the LoRa mode bit is not set, which would mean the device is in
|
// if the LoRa mode bit is not set, which would mean the device is in
|
||||||
// FSK/OOK mode or disconnected.
|
// FSK/OOK mode or disconnected.
|
||||||
return invalidOpMode, errBadMode
|
return invalidOpMode, errBadMode
|
||||||
@@ -695,40 +701,19 @@ func (d *DeviceLoRa) enableTxContinuousMode(enable bool) error {
|
|||||||
return d.writeMasked8(regMODEM_CONFIG_2, 1<<3, b2u8(enable)<<3)
|
return d.writeMasked8(regMODEM_CONFIG_2, 1<<3, b2u8(enable)<<3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setOCP defines Overload Current Protection configuration. It receives
|
// PA_HF and PA_LF are high efficiency amplifiers capable of yielding RF power programmable in 1 dB steps from -4 to
|
||||||
// the max current (Imax) in milliamperes.
|
// +14dBm directly into a 50 ohm load with low current consumption. PA_LF covers the lower bands (up to 525 MHz), whilst
|
||||||
func (d *DeviceLoRa) setOCP(mA uint8) error {
|
// PA_HF will cover the upper bands (from 779 MHz). The output power is sensitive to the power supply voltage, and typically
|
||||||
const ocpEnabledMask = 1 << 5
|
// their performance is expressed at 3.3V.
|
||||||
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.
|
// setTxPower sets the transmit power without using te PA_BOOST.
|
||||||
func (d *DeviceLoRa) setTxPower(txPow int8) error {
|
func (d *DeviceLoRa) setTxPower(txPow int8) error {
|
||||||
if txPow >= 16 {
|
if txPow >= 16 {
|
||||||
return errors.New("requested tx power exceeds capabilities without PA_BOOST")
|
return errors.New("requested tx power exceeds capabilities without PA_BOOST")
|
||||||
}
|
}
|
||||||
// Pout=Pmax-(15-OutputPower)
|
MaxPower, OutputPower := rfoPowReg(txPow)
|
||||||
// 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).
|
// 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)))
|
err := d.write8(regPA_CONFIG, ((0b111&MaxPower)<<4)|(OutputPower&0b1111))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -737,6 +722,56 @@ func (d *DeviceLoRa) setTxPower(txPow int8) error {
|
|||||||
// return d.write8(regOCP, 0) // TODO: Disable OCP?
|
// return d.write8(regOCP, 0) // TODO: Disable OCP?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rfoPowReg(txPow int8) (MaxPower, OutputPower uint8) {
|
||||||
|
// To get the desired power level we must balance two register values, MaxPower and OutputPower.
|
||||||
|
// The main equation is:
|
||||||
|
// Pout = Pmax - (15 - OutputPower) => OutputPower = 15 - (Pmax - Pout)
|
||||||
|
// Where Pmax=10.8+0.6*MaxPower [dBm]
|
||||||
|
// To solve this combined equation we set MaxPower depending on txPow
|
||||||
|
// and solve from there on. The result is error margin +/- 0.3dBm. See tests.
|
||||||
|
switch {
|
||||||
|
case txPow < 2:
|
||||||
|
MaxPower = 0b000
|
||||||
|
case txPow < 10:
|
||||||
|
MaxPower = 0b010
|
||||||
|
case txPow < 14:
|
||||||
|
MaxPower = 0b100
|
||||||
|
default:
|
||||||
|
MaxPower = 0b111
|
||||||
|
}
|
||||||
|
Pmax := 11 + MaxPower*6/10
|
||||||
|
OutputPower = 15 - (Pmax - uint8(txPow))
|
||||||
|
return MaxPower, OutputPower
|
||||||
|
}
|
||||||
|
|
||||||
|
func paBoostPowReg(txPow int8) (OutputPower uint8) {
|
||||||
|
OutputPower = 15 - (17 - uint8(txPow))
|
||||||
|
return OutputPower
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
return d.write8(regOCP, ocpEnabledMask|(0x1F&ocpTrim(mA)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ocpTrim(imax uint8) uint8 {
|
||||||
|
if imax < 45 {
|
||||||
|
imax = 45 // Absolute minimum is 45mA.
|
||||||
|
}
|
||||||
|
var ocpTrim uint8
|
||||||
|
switch {
|
||||||
|
case imax <= 120: // Imax [mA] = 45 +5*OcpTrim
|
||||||
|
ocpTrim = (imax - 45) / 5
|
||||||
|
case imax <= 240: // Imax [mA] = -30 + 10*OcpTrim
|
||||||
|
ocpTrim = uint8((uint16(imax) + 30) / 10)
|
||||||
|
default: // Imax = 240mA
|
||||||
|
ocpTrim = 27
|
||||||
|
}
|
||||||
|
return ocpTrim
|
||||||
|
}
|
||||||
|
|
||||||
// setFrequency sets the center radio frequency.
|
// setFrequency sets the center radio frequency.
|
||||||
func (d *DeviceLoRa) setFrequency(freq lora.Frequency) error {
|
func (d *DeviceLoRa) setFrequency(freq lora.Frequency) error {
|
||||||
var freqReg [3]byte
|
var freqReg [3]byte
|
||||||
@@ -904,6 +939,32 @@ func (d *DeviceLoRa) read(addr uint8, buf []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadTemperature returns the temperature of the chip in degrees Celsius.
|
||||||
|
func (d *DeviceLoRa) ReadTemperature() (celsius int8, err error) {
|
||||||
|
// We do things dirtily in this function since we are
|
||||||
|
// switching to FSK/OOK mode and back to LoRa mode.
|
||||||
|
defer func() {
|
||||||
|
// Put chip in sleep to let us switch back to LoRa mode.
|
||||||
|
d.write8(regOP_MODE, opmSLEEP)
|
||||||
|
time.Sleep(15 * time.Millisecond)
|
||||||
|
err = d.SetOpMode(OpSleep) // Back to LoRa mode. Only error that matters.
|
||||||
|
}()
|
||||||
|
// Sequence taken from page 89.
|
||||||
|
d.write8(regOP_MODE, opmSLEEP) // Put chip in sleep to let us switch to FSK/OOK mode.
|
||||||
|
time.Sleep(15 * time.Millisecond)
|
||||||
|
d.write8(regOP_MODE, opmSTANDBY)
|
||||||
|
time.Sleep(1000 * time.Microsecond) // Wait for oscillator startup.
|
||||||
|
d.write8(regOP_MODE, opmFSRX)
|
||||||
|
d.writeMasked8(regImageCal, 1, 0) // Set TempMonitorOff = 0 (enables the sensor). It is not required to wait for the PLL Lock indication
|
||||||
|
time.Sleep(140 * time.Microsecond)
|
||||||
|
d.writeMasked8(regImageCal, 1, 1) // Set TempMonitorOff = 1 (disables the sensor).
|
||||||
|
d.write8(regOP_MODE, opmSLEEP)
|
||||||
|
value, _ := d.read8(regTemp)
|
||||||
|
// Following calculation maps
|
||||||
|
value = 255 - 2*(value-64)
|
||||||
|
return int8(value), err
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DeviceLoRa) readerToLastPacket() (_ *fifoReader, err error) {
|
func (d *DeviceLoRa) readerToLastPacket() (_ *fifoReader, err error) {
|
||||||
// IRQ check according to page 41 of the datasheet.
|
// IRQ check according to page 41 of the datasheet.
|
||||||
// Flags must not be asserted in order to ensure packet reception has terminated succesfully.
|
// Flags must not be asserted in order to ensure packet reception has terminated succesfully.
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package sx127x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRFOPowReg(t *testing.T) {
|
||||||
|
for txPow := int8(-4); txPow < 17; txPow++ {
|
||||||
|
maxPow, outPow := rfoPowReg(txPow)
|
||||||
|
Pmax := 10.8 + 0.6*float64(maxPow)
|
||||||
|
Pout := Pmax - (15 - float64(outPow))
|
||||||
|
diff := math.Abs(Pout - float64(txPow))
|
||||||
|
if diff > .3 {
|
||||||
|
t.Errorf("wanted %d, got %f", txPow, Pout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPAPowReg(t *testing.T) {
|
||||||
|
for txPow := int8(2); txPow < 20; txPow++ {
|
||||||
|
outPow := paBoostPowReg(txPow)
|
||||||
|
Pout := 17.0 - (15 - float64(outPow))
|
||||||
|
diff := math.Abs(Pout - float64(txPow))
|
||||||
|
if diff > .01 {
|
||||||
|
t.Errorf("wanted %d, got %f", txPow, Pout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOCPTrim(t *testing.T) {
|
||||||
|
for imax := uint8(45); imax < 241; imax++ {
|
||||||
|
var gotImax float64
|
||||||
|
trim := ocpTrim(imax)
|
||||||
|
switch {
|
||||||
|
case trim <= 15:
|
||||||
|
gotImax = 45.0 + 5.0*float64(trim)
|
||||||
|
case trim <= 27:
|
||||||
|
gotImax = -30.0 + 10.0*float64(trim)
|
||||||
|
default:
|
||||||
|
gotImax = 240.0
|
||||||
|
}
|
||||||
|
if trim > 27 {
|
||||||
|
t.Error("trim > 27")
|
||||||
|
}
|
||||||
|
diff := math.Abs(gotImax - float64(imax))
|
||||||
|
if diff > 11 {
|
||||||
|
t.Errorf("wanted %d, got %f", imax, gotImax)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user