forked from google/periph
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ina219: add i2c driver for high side current and voltage sensor (goog…
- Loading branch information
1 parent
62efe11
commit afa1663
Showing
6 changed files
with
798 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// 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. | ||
|
||
// ina219 communicates with an ina219 sensor reading voltage, current and power. | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
"time" | ||
|
||
"periph.io/x/periph/conn/i2c/i2creg" | ||
"periph.io/x/periph/experimental/devices/ina219" | ||
"periph.io/x/periph/host" | ||
) | ||
|
||
func mainImpl() error { | ||
if _, err := host.Init(); err != nil { | ||
return err | ||
} | ||
address := flag.Int("address", 0x40, "I²C address") | ||
i2cbus := flag.String("bus", "", "I²C bus (/dev/i2c-1)") | ||
|
||
flag.Parse() | ||
|
||
fmt.Println("Starting INA219 Current Sensor") | ||
if _, err := host.Init(); err != nil { | ||
return err | ||
} | ||
|
||
// Open default I²C bus. | ||
bus, err := i2creg.Open(*i2cbus) | ||
if err != nil { | ||
return fmt.Errorf("failed to open I²C: %v", err) | ||
} | ||
defer bus.Close() | ||
|
||
// Create a new power sensor a sense with default options of 100 mΩ, 3.2A at | ||
// address of 0x40 if no other address supplied with command line option. | ||
sensor, err := ina219.New(bus, &ina219.Opts{Address: *address}) | ||
if err != nil { | ||
return fmt.Errorf("failed to open new sensor: %v", err) | ||
} | ||
|
||
// Read values from sensor every second. | ||
everySecond := time.NewTicker(time.Second).C | ||
var halt = make(chan os.Signal) | ||
signal.Notify(halt, syscall.SIGTERM) | ||
signal.Notify(halt, syscall.SIGINT) | ||
|
||
fmt.Println("ctrl+c to exit") | ||
for { | ||
select { | ||
case <-everySecond: | ||
p, err := sensor.Sense() | ||
if err != nil { | ||
return fmt.Errorf("sensor reading error: %v", err) | ||
} | ||
fmt.Println(p) | ||
case <-halt: | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
func main() { | ||
if err := mainImpl(); err != nil { | ||
fmt.Fprintf(os.Stderr, "ina219: %s.\n", err) | ||
return | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
) |
Oops, something went wrong.