Skip to content
This repository was archived by the owner on Sep 20, 2023. It is now read-only.

Commit afa1663

Browse files
NeuralSpazmaruel
authored andcommitted
ina219: add i2c driver for high side current and voltage sensor (#292)
1 parent 62efe11 commit afa1663

File tree

6 files changed

+798
-0
lines changed

6 files changed

+798
-0
lines changed

experimental/cmd/ina219/main.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2018 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
// ina219 communicates with an ina219 sensor reading voltage, current and power.
6+
package main
7+
8+
import (
9+
"flag"
10+
"fmt"
11+
"os"
12+
"os/signal"
13+
"syscall"
14+
"time"
15+
16+
"periph.io/x/periph/conn/i2c/i2creg"
17+
"periph.io/x/periph/experimental/devices/ina219"
18+
"periph.io/x/periph/host"
19+
)
20+
21+
func mainImpl() error {
22+
if _, err := host.Init(); err != nil {
23+
return err
24+
}
25+
address := flag.Int("address", 0x40, "I²C address")
26+
i2cbus := flag.String("bus", "", "I²C bus (/dev/i2c-1)")
27+
28+
flag.Parse()
29+
30+
fmt.Println("Starting INA219 Current Sensor")
31+
if _, err := host.Init(); err != nil {
32+
return err
33+
}
34+
35+
// Open default I²C bus.
36+
bus, err := i2creg.Open(*i2cbus)
37+
if err != nil {
38+
return fmt.Errorf("failed to open I²C: %v", err)
39+
}
40+
defer bus.Close()
41+
42+
// Create a new power sensor a sense with default options of 100 mΩ, 3.2A at
43+
// address of 0x40 if no other address supplied with command line option.
44+
sensor, err := ina219.New(bus, &ina219.Opts{Address: *address})
45+
if err != nil {
46+
return fmt.Errorf("failed to open new sensor: %v", err)
47+
}
48+
49+
// Read values from sensor every second.
50+
everySecond := time.NewTicker(time.Second).C
51+
var halt = make(chan os.Signal)
52+
signal.Notify(halt, syscall.SIGTERM)
53+
signal.Notify(halt, syscall.SIGINT)
54+
55+
fmt.Println("ctrl+c to exit")
56+
for {
57+
select {
58+
case <-everySecond:
59+
p, err := sensor.Sense()
60+
if err != nil {
61+
return fmt.Errorf("sensor reading error: %v", err)
62+
}
63+
fmt.Println(p)
64+
case <-halt:
65+
return nil
66+
}
67+
}
68+
}
69+
70+
func main() {
71+
if err := mainImpl(); err != nil {
72+
fmt.Fprintf(os.Stderr, "ina219: %s.\n", err)
73+
return
74+
}
75+
}

experimental/devices/ina219/doc.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2018 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
// Package ina219 controls a Texas Instruments ina219 high side current,
6+
// voltage and power monitor IC over an i2c bus.
7+
//
8+
// Calibration
9+
//
10+
// Calibration is recommended for accurate current and power measurements.
11+
// Voltage measurements do not require sensor calibration. To calibrate meansure
12+
// the actual value of the shunt resistor.
13+
//
14+
// Datasheet
15+
//
16+
// http://www.ti.com/lit/ds/symlink/ina219.pdf
17+
package ina219
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2018 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
package ina219_test
6+
7+
import (
8+
"fmt"
9+
"log"
10+
11+
"periph.io/x/periph/conn/i2c/i2creg"
12+
"periph.io/x/periph/experimental/devices/ina219"
13+
"periph.io/x/periph/host"
14+
)
15+
16+
func Example() {
17+
// Make sure periph is initialized.
18+
if _, err := host.Init(); err != nil {
19+
log.Fatal(err)
20+
}
21+
22+
// Open default I²C bus.
23+
bus, err := i2creg.Open("")
24+
if err != nil {
25+
log.Fatalf("failed to open I²C: %v", err)
26+
}
27+
defer bus.Close()
28+
29+
// Create a new power sensor.
30+
sensor, err := ina219.New(bus, &ina219.DefaultOpts)
31+
if err != nil {
32+
log.Fatalln(err)
33+
}
34+
35+
// Read values from sensor.
36+
measurement, err := sensor.Sense()
37+
38+
if err != nil {
39+
log.Fatalln(err)
40+
}
41+
42+
fmt.Println(measurement)
43+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// Copyright 2018 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
package ina219
6+
7+
import (
8+
"encoding/binary"
9+
"errors"
10+
"fmt"
11+
"sync"
12+
13+
"periph.io/x/periph/conn/i2c"
14+
"periph.io/x/periph/conn/mmr"
15+
"periph.io/x/periph/conn/physic"
16+
)
17+
18+
// Opts holds the configuration options.
19+
//
20+
// Slave Address
21+
//
22+
// Depending which pins the A1, A0 pins are connected to will change the slave
23+
// address. Default configuration is address 0x40 (both pins to GND). For a full
24+
// address table see datasheet.
25+
type Opts struct {
26+
Address int
27+
SenseResistor physic.ElectricResistance
28+
MaxCurrent physic.ElectricCurrent
29+
}
30+
31+
// DefaultOpts is the recommended default options.
32+
var DefaultOpts = Opts{
33+
Address: 0x40,
34+
SenseResistor: 100 * physic.MilliOhm,
35+
MaxCurrent: 3200 * physic.MilliAmpere,
36+
}
37+
38+
// New opens a handle to an ina219 sensor.
39+
func New(bus i2c.Bus, opts *Opts) (*Dev, error) {
40+
41+
i2cAddress := DefaultOpts.Address
42+
if opts.Address != 0 {
43+
if opts.Address < 0x40 || opts.Address > 0x4f {
44+
return nil, errAddressOutOfRange
45+
}
46+
i2cAddress = opts.Address
47+
}
48+
49+
senseResistor := DefaultOpts.SenseResistor
50+
if opts.SenseResistor != 0 {
51+
if opts.SenseResistor < 1 {
52+
return nil, errSenseResistorValueInvalid
53+
}
54+
senseResistor = opts.SenseResistor
55+
}
56+
57+
maxCurrent := DefaultOpts.MaxCurrent
58+
if opts.MaxCurrent != 0 {
59+
if opts.MaxCurrent < 1 {
60+
return nil, errMaxCurrentInvalid
61+
}
62+
maxCurrent = opts.MaxCurrent
63+
}
64+
65+
dev := &Dev{
66+
m: mmr.Dev8{
67+
Conn: &i2c.Dev{Bus: bus, Addr: uint16(i2cAddress)},
68+
Order: binary.BigEndian,
69+
},
70+
}
71+
72+
if err := dev.calibrate(senseResistor, maxCurrent); err != nil {
73+
return nil, err
74+
}
75+
76+
if err := dev.m.WriteUint16(configRegister, 0x1FFF); err != nil {
77+
return nil, errWritingToConfigRegister
78+
}
79+
80+
return dev, nil
81+
}
82+
83+
// Dev is a handle to the ina219 sensor.
84+
type Dev struct {
85+
m mmr.Dev8
86+
87+
mu sync.Mutex
88+
currentLSB physic.ElectricCurrent
89+
powerLSB physic.Power
90+
}
91+
92+
const (
93+
configRegister = 0x00
94+
shuntVoltageRegister = 0x01
95+
busVoltageRegister = 0x02
96+
powerRegister = 0x03
97+
currentRegister = 0x04
98+
calibrationRegister = 0x05
99+
)
100+
101+
// Sense reads the power values from the ina219 sensor.
102+
func (d *Dev) Sense() (PowerMonitor, error) {
103+
d.mu.Lock()
104+
defer d.mu.Unlock()
105+
106+
var pm PowerMonitor
107+
108+
shunt, err := d.m.ReadUint16(shuntVoltageRegister)
109+
if err != nil {
110+
return PowerMonitor{}, errReadShunt
111+
}
112+
// Least significant bit is 10µV.
113+
pm.Shunt = physic.ElectricPotential(shunt) * 10 * physic.MicroVolt
114+
115+
bus, err := d.m.ReadUint16(busVoltageRegister)
116+
if err != nil {
117+
return PowerMonitor{}, errReadBus
118+
}
119+
// Check if bit zero is set, if set the ADC has overflowed.
120+
if bus&1 > 0 {
121+
return PowerMonitor{}, errRegisterOverflow
122+
}
123+
124+
// Least significant bit is 4mV.
125+
pm.Voltage = physic.ElectricPotential(bus>>3) * 4 * physic.MilliVolt
126+
127+
current, err := d.m.ReadUint16(currentRegister)
128+
if err != nil {
129+
return PowerMonitor{}, errReadCurrent
130+
}
131+
pm.Current = physic.ElectricCurrent(current) * d.currentLSB
132+
133+
power, err := d.m.ReadUint16(powerRegister)
134+
if err != nil {
135+
return PowerMonitor{}, errReadPower
136+
}
137+
pm.Power = physic.Power(power) * d.powerLSB
138+
139+
return pm, nil
140+
}
141+
142+
// Since physic electrical is in nano units we need to scale taking care to not
143+
// overflow int64 or loose resolution.
144+
const calibratescale int64 = ((int64(physic.Ampere) * int64(physic.Ohm)) / 100000) << 12
145+
146+
// calibrate sets the scaling factor of the current and power registers for the
147+
// maximum resolution. calibrate is run on init.
148+
func (d *Dev) calibrate(sense physic.ElectricResistance, maxCurrent physic.ElectricCurrent) error {
149+
// TODO: Check calibration with float implementation in tests.
150+
if sense <= 0 {
151+
return errSenseResistorValueInvalid
152+
}
153+
if maxCurrent <= 0 {
154+
return errMaxCurrentInvalid
155+
}
156+
157+
d.mu.Lock()
158+
defer d.mu.Unlock()
159+
160+
d.currentLSB = maxCurrent / (2 << 15)
161+
d.powerLSB = physic.Power(d.currentLSB * 20)
162+
// Calibration Register = 0.04096 / (current LSB * Shunt Resistance)
163+
// Where lsb is in Amps and resistance is in ohms.
164+
// Calibration register is 16 bits.
165+
cal := uint16(calibratescale / (int64(d.currentLSB) * int64(sense)))
166+
return d.m.WriteUint16(calibrationRegister, cal)
167+
}
168+
169+
// PowerMonitor represents measurements from ina219 sensor.
170+
type PowerMonitor struct {
171+
Shunt physic.ElectricPotential
172+
Voltage physic.ElectricPotential
173+
Current physic.ElectricCurrent
174+
Power physic.Power
175+
}
176+
177+
// String returns a PowerMonitor as string
178+
func (p PowerMonitor) String() string {
179+
return fmt.Sprintf("Bus: %s, Current: %s, Power: %s, Shunt: %s", p.Voltage, p.Current, p.Power, p.Shunt)
180+
}
181+
182+
var (
183+
errReadShunt = errors.New("failed to read shunt voltage")
184+
errReadBus = errors.New("failed to read bus voltage")
185+
errReadPower = errors.New("failed to read power")
186+
errReadCurrent = errors.New("failed to read current")
187+
errAddressOutOfRange = errors.New("i2c address out of range")
188+
errSenseResistorValueInvalid = errors.New("sense resistor value cannot be negative or zero")
189+
errMaxCurrentInvalid = errors.New("max current cannot be negative or zero")
190+
errRegisterOverflow = errors.New("bus voltage register overflow")
191+
errWritingToConfigRegister = errors.New("failed to write to configuration register")
192+
)

0 commit comments

Comments
 (0)