|
| 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