mirror of https://github.com/periph/devices
ina219: add i2c driver for high side current and voltage sensor (#292)
parent
5d01024987
commit
f54b53f207
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2018 The Periph Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed under the Apache License, Version 2.0
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package ina219 controls a Texas Instruments ina219 high side current,
|
||||||
|
// voltage and power monitor IC over an i2c bus.
|
||||||
|
//
|
||||||
|
// Calibration
|
||||||
|
//
|
||||||
|
// Calibration is recommended for accurate current and power measurements.
|
||||||
|
// Voltage measurements do not require sensor calibration. To calibrate meansure
|
||||||
|
// the actual value of the shunt resistor.
|
||||||
|
//
|
||||||
|
// Datasheet
|
||||||
|
//
|
||||||
|
// http://www.ti.com/lit/ds/symlink/ina219.pdf
|
||||||
|
package ina219
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2018 The Periph Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed under the Apache License, Version 2.0
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package ina219_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/i2c/i2creg"
|
||||||
|
"periph.io/x/periph/experimental/devices/ina219"
|
||||||
|
"periph.io/x/periph/host"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// Make sure periph is initialized.
|
||||||
|
if _, err := host.Init(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open default I²C bus.
|
||||||
|
bus, err := i2creg.Open("")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to open I²C: %v", err)
|
||||||
|
}
|
||||||
|
defer bus.Close()
|
||||||
|
|
||||||
|
// Create a new power sensor.
|
||||||
|
sensor, err := ina219.New(bus, &ina219.DefaultOpts)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read values from sensor.
|
||||||
|
measurement, err := sensor.Sense()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(measurement)
|
||||||
|
}
|
||||||
@ -0,0 +1,192 @@
|
|||||||
|
// Copyright 2018 The Periph Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed under the Apache License, Version 2.0
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package ina219
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/i2c"
|
||||||
|
"periph.io/x/periph/conn/mmr"
|
||||||
|
"periph.io/x/periph/conn/physic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Opts holds the configuration options.
|
||||||
|
//
|
||||||
|
// Slave Address
|
||||||
|
//
|
||||||
|
// Depending which pins the A1, A0 pins are connected to will change the slave
|
||||||
|
// address. Default configuration is address 0x40 (both pins to GND). For a full
|
||||||
|
// address table see datasheet.
|
||||||
|
type Opts struct {
|
||||||
|
Address int
|
||||||
|
SenseResistor physic.ElectricResistance
|
||||||
|
MaxCurrent physic.ElectricCurrent
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultOpts is the recommended default options.
|
||||||
|
var DefaultOpts = Opts{
|
||||||
|
Address: 0x40,
|
||||||
|
SenseResistor: 100 * physic.MilliOhm,
|
||||||
|
MaxCurrent: 3200 * physic.MilliAmpere,
|
||||||
|
}
|
||||||
|
|
||||||
|
// New opens a handle to an ina219 sensor.
|
||||||
|
func New(bus i2c.Bus, opts *Opts) (*Dev, error) {
|
||||||
|
|
||||||
|
i2cAddress := DefaultOpts.Address
|
||||||
|
if opts.Address != 0 {
|
||||||
|
if opts.Address < 0x40 || opts.Address > 0x4f {
|
||||||
|
return nil, errAddressOutOfRange
|
||||||
|
}
|
||||||
|
i2cAddress = opts.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
senseResistor := DefaultOpts.SenseResistor
|
||||||
|
if opts.SenseResistor != 0 {
|
||||||
|
if opts.SenseResistor < 1 {
|
||||||
|
return nil, errSenseResistorValueInvalid
|
||||||
|
}
|
||||||
|
senseResistor = opts.SenseResistor
|
||||||
|
}
|
||||||
|
|
||||||
|
maxCurrent := DefaultOpts.MaxCurrent
|
||||||
|
if opts.MaxCurrent != 0 {
|
||||||
|
if opts.MaxCurrent < 1 {
|
||||||
|
return nil, errMaxCurrentInvalid
|
||||||
|
}
|
||||||
|
maxCurrent = opts.MaxCurrent
|
||||||
|
}
|
||||||
|
|
||||||
|
dev := &Dev{
|
||||||
|
m: mmr.Dev8{
|
||||||
|
Conn: &i2c.Dev{Bus: bus, Addr: uint16(i2cAddress)},
|
||||||
|
Order: binary.BigEndian,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dev.calibrate(senseResistor, maxCurrent); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dev.m.WriteUint16(configRegister, 0x1FFF); err != nil {
|
||||||
|
return nil, errWritingToConfigRegister
|
||||||
|
}
|
||||||
|
|
||||||
|
return dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dev is a handle to the ina219 sensor.
|
||||||
|
type Dev struct {
|
||||||
|
m mmr.Dev8
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
currentLSB physic.ElectricCurrent
|
||||||
|
powerLSB physic.Power
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
configRegister = 0x00
|
||||||
|
shuntVoltageRegister = 0x01
|
||||||
|
busVoltageRegister = 0x02
|
||||||
|
powerRegister = 0x03
|
||||||
|
currentRegister = 0x04
|
||||||
|
calibrationRegister = 0x05
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sense reads the power values from the ina219 sensor.
|
||||||
|
func (d *Dev) Sense() (PowerMonitor, error) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
var pm PowerMonitor
|
||||||
|
|
||||||
|
shunt, err := d.m.ReadUint16(shuntVoltageRegister)
|
||||||
|
if err != nil {
|
||||||
|
return PowerMonitor{}, errReadShunt
|
||||||
|
}
|
||||||
|
// Least significant bit is 10µV.
|
||||||
|
pm.Shunt = physic.ElectricPotential(shunt) * 10 * physic.MicroVolt
|
||||||
|
|
||||||
|
bus, err := d.m.ReadUint16(busVoltageRegister)
|
||||||
|
if err != nil {
|
||||||
|
return PowerMonitor{}, errReadBus
|
||||||
|
}
|
||||||
|
// Check if bit zero is set, if set the ADC has overflowed.
|
||||||
|
if bus&1 > 0 {
|
||||||
|
return PowerMonitor{}, errRegisterOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
// Least significant bit is 4mV.
|
||||||
|
pm.Voltage = physic.ElectricPotential(bus>>3) * 4 * physic.MilliVolt
|
||||||
|
|
||||||
|
current, err := d.m.ReadUint16(currentRegister)
|
||||||
|
if err != nil {
|
||||||
|
return PowerMonitor{}, errReadCurrent
|
||||||
|
}
|
||||||
|
pm.Current = physic.ElectricCurrent(current) * d.currentLSB
|
||||||
|
|
||||||
|
power, err := d.m.ReadUint16(powerRegister)
|
||||||
|
if err != nil {
|
||||||
|
return PowerMonitor{}, errReadPower
|
||||||
|
}
|
||||||
|
pm.Power = physic.Power(power) * d.powerLSB
|
||||||
|
|
||||||
|
return pm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since physic electrical is in nano units we need to scale taking care to not
|
||||||
|
// overflow int64 or loose resolution.
|
||||||
|
const calibratescale int64 = ((int64(physic.Ampere) * int64(physic.Ohm)) / 100000) << 12
|
||||||
|
|
||||||
|
// calibrate sets the scaling factor of the current and power registers for the
|
||||||
|
// maximum resolution. calibrate is run on init.
|
||||||
|
func (d *Dev) calibrate(sense physic.ElectricResistance, maxCurrent physic.ElectricCurrent) error {
|
||||||
|
// TODO: Check calibration with float implementation in tests.
|
||||||
|
if sense <= 0 {
|
||||||
|
return errSenseResistorValueInvalid
|
||||||
|
}
|
||||||
|
if maxCurrent <= 0 {
|
||||||
|
return errMaxCurrentInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
d.currentLSB = maxCurrent / (2 << 15)
|
||||||
|
d.powerLSB = physic.Power(d.currentLSB * 20)
|
||||||
|
// Calibration Register = 0.04096 / (current LSB * Shunt Resistance)
|
||||||
|
// Where lsb is in Amps and resistance is in ohms.
|
||||||
|
// Calibration register is 16 bits.
|
||||||
|
cal := uint16(calibratescale / (int64(d.currentLSB) * int64(sense)))
|
||||||
|
return d.m.WriteUint16(calibrationRegister, cal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PowerMonitor represents measurements from ina219 sensor.
|
||||||
|
type PowerMonitor struct {
|
||||||
|
Shunt physic.ElectricPotential
|
||||||
|
Voltage physic.ElectricPotential
|
||||||
|
Current physic.ElectricCurrent
|
||||||
|
Power physic.Power
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a PowerMonitor as string
|
||||||
|
func (p PowerMonitor) String() string {
|
||||||
|
return fmt.Sprintf("Bus: %s, Current: %s, Power: %s, Shunt: %s", p.Voltage, p.Current, p.Power, p.Shunt)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errReadShunt = errors.New("failed to read shunt voltage")
|
||||||
|
errReadBus = errors.New("failed to read bus voltage")
|
||||||
|
errReadPower = errors.New("failed to read power")
|
||||||
|
errReadCurrent = errors.New("failed to read current")
|
||||||
|
errAddressOutOfRange = errors.New("i2c address out of range")
|
||||||
|
errSenseResistorValueInvalid = errors.New("sense resistor value cannot be negative or zero")
|
||||||
|
errMaxCurrentInvalid = errors.New("max current cannot be negative or zero")
|
||||||
|
errRegisterOverflow = errors.New("bus voltage register overflow")
|
||||||
|
errWritingToConfigRegister = errors.New("failed to write to configuration register")
|
||||||
|
)
|
||||||
@ -0,0 +1,394 @@
|
|||||||
|
// Copyright 2018 The Periph Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed under the Apache License, Version 2.0
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package ina219
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/i2c"
|
||||||
|
"periph.io/x/periph/conn/i2c/i2ctest"
|
||||||
|
"periph.io/x/periph/conn/mmr"
|
||||||
|
"periph.io/x/periph/conn/physic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
stringErr := errors.New("use err.Error() error")
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
currentLSB physic.ElectricCurrent
|
||||||
|
powerLSB physic.Power
|
||||||
|
}
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
opts Opts
|
||||||
|
want fields
|
||||||
|
tx []i2ctest.IO
|
||||||
|
err error
|
||||||
|
errString string
|
||||||
|
}{
|
||||||
|
{name: "defaults",
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
},
|
||||||
|
want: fields{
|
||||||
|
currentLSB: 48828 * physic.NanoAmpere,
|
||||||
|
powerLSB: 976560 * physic.NanoWatt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{name: "badAddressOption",
|
||||||
|
opts: Opts{Address: 0x60},
|
||||||
|
err: errAddressOutOfRange,
|
||||||
|
},
|
||||||
|
{name: "badSenseResistorOption",
|
||||||
|
opts: Opts{SenseResistor: -1},
|
||||||
|
err: errSenseResistorValueInvalid,
|
||||||
|
},
|
||||||
|
{name: "badMaxCurrentOption",
|
||||||
|
opts: Opts{MaxCurrent: -1},
|
||||||
|
err: errMaxCurrentInvalid,
|
||||||
|
},
|
||||||
|
{name: "setAddress",
|
||||||
|
opts: Opts{Address: 0x41},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x41, W: []byte{calibrationRegister, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
{Addr: 0x41, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
},
|
||||||
|
want: fields{
|
||||||
|
currentLSB: 48828 * physic.NanoAmpere,
|
||||||
|
powerLSB: 976560 * physic.NanoWatt,
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{name: "setMaxCurrent",
|
||||||
|
opts: Opts{MaxCurrent: 1000 * physic.MilliAmpere},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x68, 0xdc}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
},
|
||||||
|
want: fields{
|
||||||
|
currentLSB: 15258 * physic.NanoAmpere,
|
||||||
|
powerLSB: 305160 * physic.NanoWatt,
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{name: "setSenseResistor",
|
||||||
|
opts: Opts{SenseResistor: 10 * physic.MilliOhm},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x47, 0xae}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
},
|
||||||
|
want: fields{
|
||||||
|
currentLSB: 48828 * physic.NanoAmpere,
|
||||||
|
powerLSB: 976560 * physic.NanoWatt,
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{name: "txError",
|
||||||
|
tx: []i2ctest.IO{{Addr: 0x40, W: []byte{}, R: []byte{}}},
|
||||||
|
want: fields{
|
||||||
|
currentLSB: 48828 * physic.NanoAmpere,
|
||||||
|
powerLSB: 976560 * physic.NanoWatt,
|
||||||
|
},
|
||||||
|
err: stringErr,
|
||||||
|
errString: "unexpected write",
|
||||||
|
},
|
||||||
|
{name: "errWritingToConfigRegister",
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister}, R: []byte{}},
|
||||||
|
},
|
||||||
|
want: fields{
|
||||||
|
currentLSB: 48828 * physic.NanoAmpere,
|
||||||
|
powerLSB: 976560 * physic.NanoWatt,
|
||||||
|
},
|
||||||
|
err: errWritingToConfigRegister,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
bus := &i2ctest.Playback{
|
||||||
|
Ops: test.tx,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ina, err := New(bus, &test.opts)
|
||||||
|
|
||||||
|
if test.err != nil {
|
||||||
|
if err != test.err {
|
||||||
|
if test.err == stringErr {
|
||||||
|
if !strings.Contains(err.Error(), test.errString) {
|
||||||
|
t.Errorf("%v wanted err: %v, but got: %v", test.name, test.errString, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("%v wanted err: %v, but got: %v", test.name, test.err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.err == nil {
|
||||||
|
if ina == nil {
|
||||||
|
t.Errorf("%v wanted no err but got: %v", test.name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var got = fields{
|
||||||
|
currentLSB: ina.currentLSB,
|
||||||
|
powerLSB: ina.powerLSB,
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Errorf("%v wanted: %v, but got: %v", test.name, test.want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSense(t *testing.T) {
|
||||||
|
stringErr := errors.New("use err.Error() error")
|
||||||
|
type fields struct {
|
||||||
|
currentLSB physic.ElectricCurrent
|
||||||
|
powerLSB physic.Power
|
||||||
|
}
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
args Opts
|
||||||
|
want PowerMonitor
|
||||||
|
tx []i2ctest.IO
|
||||||
|
err error
|
||||||
|
errString string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "errReadShunt",
|
||||||
|
err: errReadShunt,
|
||||||
|
args: Opts{},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{shuntVoltageRegister}, R: []byte{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "errReadBus",
|
||||||
|
err: errReadBus,
|
||||||
|
args: Opts{},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{shuntVoltageRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{busVoltageRegister}, R: []byte{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "errReadCurrent",
|
||||||
|
err: errReadCurrent,
|
||||||
|
args: Opts{},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{shuntVoltageRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{busVoltageRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{currentRegister}, R: []byte{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "errReadPower",
|
||||||
|
err: errReadPower,
|
||||||
|
args: Opts{},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{shuntVoltageRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{busVoltageRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{currentRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{powerRegister}, R: []byte{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "readZero",
|
||||||
|
err: nil,
|
||||||
|
args: Opts{},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{shuntVoltageRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{busVoltageRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{currentRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{powerRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
},
|
||||||
|
want: PowerMonitor{Shunt: 0, Voltage: 0, Current: 0, Power: 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "busVoltageOverflow",
|
||||||
|
err: errRegisterOverflow,
|
||||||
|
args: Opts{},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{calibrationRegister, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{configRegister, 0x1f, 0xff}, R: []byte{}},
|
||||||
|
{Addr: 0x40, W: []byte{shuntVoltageRegister}, R: []byte{0x00, 0x00}},
|
||||||
|
{Addr: 0x40, W: []byte{busVoltageRegister}, R: []byte{0x00, 0x01}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
bus := &i2ctest.Playback{
|
||||||
|
Ops: test.tx,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
ina, err := New(bus, &Opts{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("set setup failure %v", err)
|
||||||
|
}
|
||||||
|
if ina == nil {
|
||||||
|
t.Fatalf("device init failed")
|
||||||
|
}
|
||||||
|
got, err := ina.Sense()
|
||||||
|
if test.err != nil {
|
||||||
|
if err != test.err {
|
||||||
|
if test.err == stringErr {
|
||||||
|
if !strings.Contains(err.Error(), test.errString) {
|
||||||
|
t.Errorf("%v wanted err: %v, but got: %v", test.name, test.errString, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("%v wanted err: %v, but got: %v", test.name, test.err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.err == nil {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v wanted no err but got: %v", test.name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != test.want {
|
||||||
|
t.Errorf("%v wanted: %v, but got: %v", test.name, test.want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalibrate(t *testing.T) {
|
||||||
|
stringErr := errors.New("use err.Error() error")
|
||||||
|
|
||||||
|
type fields struct {
|
||||||
|
sense physic.ElectricResistance
|
||||||
|
maxCurrent physic.ElectricCurrent
|
||||||
|
currentLSB physic.ElectricCurrent
|
||||||
|
powerLSB physic.Power
|
||||||
|
caibrated bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tx []i2ctest.IO
|
||||||
|
args fields
|
||||||
|
want fields
|
||||||
|
err error
|
||||||
|
errString string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "errBadSense",
|
||||||
|
err: errSenseResistorValueInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "errBadMaxCurrent",
|
||||||
|
args: fields{
|
||||||
|
sense: physic.MilliOhm,
|
||||||
|
},
|
||||||
|
err: errMaxCurrentInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "errIO",
|
||||||
|
args: fields{
|
||||||
|
sense: physic.MilliOhm,
|
||||||
|
maxCurrent: physic.Ampere,
|
||||||
|
},
|
||||||
|
err: stringErr,
|
||||||
|
errString: "unexpected Tx",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
args: fields{
|
||||||
|
sense: 100 * physic.MilliOhm,
|
||||||
|
maxCurrent: 3200 * physic.MilliAmpere,
|
||||||
|
},
|
||||||
|
want: fields{
|
||||||
|
currentLSB: 48828 * physic.NanoAmpere,
|
||||||
|
powerLSB: 976560 * physic.NanoWatt,
|
||||||
|
},
|
||||||
|
tx: []i2ctest.IO{
|
||||||
|
{Addr: 0x40, W: []byte{0x05, 0x20, 0xc4}, R: []byte{}},
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
bus := i2ctest.Playback{
|
||||||
|
Ops: test.tx,
|
||||||
|
DontPanic: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
ina := &Dev{
|
||||||
|
m: mmr.Dev8{
|
||||||
|
Conn: &i2c.Dev{Bus: &bus, Addr: 0x40},
|
||||||
|
Order: binary.BigEndian},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ina.calibrate(test.args.sense, test.args.maxCurrent)
|
||||||
|
if test.err != nil {
|
||||||
|
if err != test.err {
|
||||||
|
if test.err == stringErr {
|
||||||
|
if !strings.Contains(err.Error(), test.errString) {
|
||||||
|
t.Errorf("%v wanted err: %v, but got: %v", test.name, test.errString, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("%v wanted err: %v, but got: %v", test.name, test.err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if test.err == nil {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v wanted no err but got: %v", test.name, err)
|
||||||
|
}
|
||||||
|
got := fields{
|
||||||
|
currentLSB: ina.currentLSB,
|
||||||
|
powerLSB: ina.powerLSB,
|
||||||
|
}
|
||||||
|
if got != test.want {
|
||||||
|
t.Errorf("%v wanted: %v, but got: %v", test.name, test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPowerStringer(t *testing.T) {
|
||||||
|
var p = PowerMonitor{
|
||||||
|
Shunt: 1,
|
||||||
|
Voltage: 1,
|
||||||
|
Current: 1,
|
||||||
|
Power: 1,
|
||||||
|
}
|
||||||
|
want := "Bus: 1nV, Current: 1nA, Power: 1nW, Shunt: 1nV"
|
||||||
|
got := p.String()
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("wanted %s\n, but got: %s", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2018 The Periph Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed under the Apache License, Version 2.0
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package ina219smoketest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"periph.io/x/periph/conn/i2c/i2creg"
|
||||||
|
"periph.io/x/periph/conn/physic"
|
||||||
|
"periph.io/x/periph/experimental/devices/ina219"
|
||||||
|
"periph.io/x/periph/host"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SmokeTest is imported by periph-smoketest.
|
||||||
|
type SmokeTest struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements the SmokeTest interface.
|
||||||
|
func (s *SmokeTest) Name() string {
|
||||||
|
return "ina219"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description implements the SmokeTest interface.
|
||||||
|
func (s *SmokeTest) Description() string {
|
||||||
|
return "Tests INA219 over I²C"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SmokeTest) Run(f *flag.FlagSet, args []string) (err error) {
|
||||||
|
i2cID := f.String("i2c", "", "I²C bus to use")
|
||||||
|
i2cAddr := f.Int("ia", 0x40, "I²C bus address use: 0x40 to 0x4f")
|
||||||
|
if err := f.Parse(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f.NArg() != 0 {
|
||||||
|
f.Usage()
|
||||||
|
return errors.New("unrecognized arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Starting INA219 Current Sensor\nctrl+c to exit")
|
||||||
|
if _, err := host.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open default i2c bus.
|
||||||
|
bus, err := i2creg.Open(*i2cID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err2 := bus.Close(); err == nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Create a new power sensor a sense resistor of 100 mΩ.
|
||||||
|
config := &ina219.Opts{
|
||||||
|
Address: *i2cAddr,
|
||||||
|
SenseResistor: 100 * physic.MilliOhm,
|
||||||
|
MaxCurrent: 3200 * physic.MilliAmpere,
|
||||||
|
}
|
||||||
|
|
||||||
|
sensor, err := ina219.New(bus, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pm, err := sensor.Sense()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(pm)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue