add tests for power management

This commit is contained in:
soypat
2023-05-22 23:04:26 -03:00
parent 048f89366f
commit 87658d8b62
6 changed files with 181 additions and 34 deletions
+1 -1
View File
@@ -15,5 +15,5 @@
# vendor/
*.c
c/*
*.log
.vscode
+3
View File
@@ -51,6 +51,8 @@ func main() {
// This blocks forever reading messages received.
ctx := context.Background()
for {
value, err := dev.ReadTemperature()
println("temperature:", value, err)
time.Sleep(10 * time.Second)
err = dev.RxContinuous(ctx, func(r io.Reader) (_ error) {
n, err := r.Read(rx[:])
@@ -60,6 +62,7 @@ func main() {
println("received LoRa:", string(rx[:n]))
return nil
})
if err != nil {
println(err.Error())
} else {
+21
View File
@@ -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)
}
}
+11
View File
@@ -156,6 +156,17 @@ const (
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{
bw7_8: 7.8e3 * lora.Hertz,
bw10_4: 10.4e3 * lora.Hertz,
+94 -33
View File
@@ -89,7 +89,7 @@ func DefaultConfig(freq lora.Frequency) lora.Config {
MaxImplicitPayloadLength: 0, // No need to be set when working with explicit headers.
CRC: true,
SyncWord: publicSyncword,
TxPower: 0,
TxPower: 0, // Low power by default.
LDRO: 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.
errPreambleTooShort = errors.New("preamble length too short")
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")
errUnsupportedBandwidth = errors.New("bandwidth too high for frequency around 169MHz")
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")
case cfg.HeaderType == lora.HeaderImplicit && cfg.MaxImplicitPayloadLength == 0:
err = errors.New("MaxImplicitPayloadLength parameter must be set when working with implicit headers")
case cfg.TxPower > 20:
err = errors.New("bad tx power")
case cfg.TxPower > 20 || cfg.TxPower < -4:
err = errors.New("tx power not in operating range -4..20")
}
if err != nil {
return err
@@ -268,8 +269,13 @@ func (d *DeviceLoRa) GetOpMode() (OpMode, error) {
if err != nil {
return invalidOpMode, err
}
if got&byte(opLoRaBit) == 0 || // LongRangeMode bit influences operation.
got&(1<<6) != 0 { // AccessSharedReg bit allows access to FSK registers in LoRa mode, should not be set.
// We expect the LoRa bit to be set, access shared reg bit to be unset.
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
// FSK/OOK mode or disconnected.
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)
}
// 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))
}
// PA_HF and PA_LF are high efficiency amplifiers capable of yielding RF power programmable in 1 dB steps from -4 to
// +14dBm directly into a 50 ohm load with low current consumption. PA_LF covers the lower bands (up to 525 MHz), whilst
// PA_HF will cover the upper bands (from 779 MHz). The output power is sensitive to the power supply voltage, and typically
// their performance is expressed at 3.3V.
// 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
}
MaxPower, OutputPower := rfoPowReg(txPow)
// 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 {
return err
}
@@ -737,6 +722,56 @@ func (d *DeviceLoRa) setTxPower(txPow int8) error {
// 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.
func (d *DeviceLoRa) setFrequency(freq lora.Frequency) error {
var freqReg [3]byte
@@ -904,6 +939,32 @@ func (d *DeviceLoRa) read(addr uint8, buf []byte) error {
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) {
// IRQ check according to page 41 of the datasheet.
// Flags must not be asserted in order to ensure packet reception has terminated succesfully.
+51
View File
@@ -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)
}
}
}