You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/ina219/ina219.go

197 lines
5.4 KiB
Go

// 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 / (1 << 15)
d.powerLSB = physic.Power((maxCurrent*20 + (1 << 14)) / (1 << 15))
// Calibration Register = 0.04096 / (current LSB * Shunt Resistance)
// Where lsb is in Amps and resistance is in ohms.
// Calibration register is 16 bits.
cal := calibratescale / (int64(d.currentLSB) * int64(sense))
if cal >= (1 << 16) {
return errCalibrationOverflow
}
return d.m.WriteUint16(calibrationRegister, uint16(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")
errCalibrationOverflow = errors.New("calibration would exceed maximum scaling")
)