From f65bce6d36b2725ceacff0f0ff50c8c649d4eae9 Mon Sep 17 00:00:00 2001 From: Kenneth Bell Date: Sun, 28 Mar 2021 15:02:18 -0700 Subject: [PATCH] ina260: add new i2c device --- Makefile | 2 + README.md | 1 + examples/ina260/main.go | 63 ++++++++++++++++++++++ ina260/ina260.go | 112 ++++++++++++++++++++++++++++++++++++++++ ina260/ina260_test.go | 80 ++++++++++++++++++++++++++++ ina260/registers.go | 50 ++++++++++++++++++ 6 files changed, 308 insertions(+) create mode 100644 examples/ina260/main.go create mode 100644 ina260/ina260.go create mode 100644 ina260/ina260_test.go create mode 100644 ina260/registers.go diff --git a/Makefile b/Makefile index 2a37f2e..872ccfa 100644 --- a/Makefile +++ b/Makefile @@ -185,6 +185,8 @@ endif @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/timer/ @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina260/main.go + @md5sum ./build/test.hex DRIVERS = $(wildcard */) NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \ diff --git a/README.md b/README.md index 8e84715..aaa8836 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ The following 61 devices are supported. | [HD44780 LCD controller](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) | GPIO/I2C | | [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI | | [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI | +| [INA260 Volt/Amp/Power meter](https://www.ti.com/lit/ds/symlink/ina260.pdf) | I2C | | [4x4 Membrane Keypad](https://cdn.sparkfun.com/assets/f/f/a/5/0/DS-16038.pdf) | GPIO | | [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM | | [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM | diff --git a/examples/ina260/main.go b/examples/ina260/main.go new file mode 100644 index 0000000..8153a13 --- /dev/null +++ b/examples/ina260/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "machine" + "time" + + "tinygo.org/x/drivers/ina260" +) + +func main() { + machine.I2C0.Configure(machine.I2CConfig{}) + + dev := ina260.New(machine.I2C0) + dev.Configure(ina260.Config{ + AverageMode: ina260.AVGMODE_16, + VoltConvTime: ina260.CONVTIME_140USEC, + CurrentConvTime: ina260.CONVTIME_140USEC, + Mode: ina260.MODE_CONTINUOUS | ina260.MODE_VOLTAGE | ina260.MODE_CURRENT, + }) + + if dev.Connected() { + println("INA260 detected") + } else { + println("INA260 NOT detected") + return + } + + for { + microvolts := dev.Voltage() + microamps := dev.Current() + microwatts := dev.Power() + + println(fmtD(microvolts, 4, 3), "mV,", fmtD(microamps, 4, 3), "mA,", fmtD(microwatts, 4, 3), "mW") + + time.Sleep(10 * time.Millisecond) + } +} + +func fmtD(val int32, i int, f int) string { + result := make([]byte, i+f+1) + neg := false + + if val < 0 { + val = -val + neg = true + } + + for p := len(result) - 1; p >= 0; p-- { + result[p] = byte(int32('0') + (val % 10)) + val = val / 10 + + if p == i+1 && p > 0 { + p-- + result[p] = '.' + } + } + + if neg { + result[0] = '-' + } + + return string(result) +} diff --git a/ina260/ina260.go b/ina260/ina260.go new file mode 100644 index 0000000..b9a4518 --- /dev/null +++ b/ina260/ina260.go @@ -0,0 +1,112 @@ +package ina260 + +import "tinygo.org/x/drivers" + +// Device wraps an I2C connection to an INA260 device. +type Device struct { + bus drivers.I2C + Address uint16 +} + +// Config holds the configuration of the INA260 device. +type Config struct { + // One of AVGMODE_XXX + AverageMode byte + + // One of CONVTIME_XXXXUSEC + VoltConvTime byte + + // One of CONVTIME_XXXXUSEC + CurrentConvTime byte + + // Multiple of MODE_XXXX + Mode byte +} + +// New creates a new INA260 connection. The I2C bus must already be +// configured. +// +// This function only creates the Device object, it does not touch the device. +func New(bus drivers.I2C) Device { + return Device{ + bus: bus, + Address: Address, + } +} + +// Configure sets up the device. +// +// This only needs to be called to override built-in defaults. By default, +// the device starts with: +// +// * AverageMode = AVGMODE_1 +// * VoltConvTime = CONVTIME_1100USEC +// * CurrentConvTime = CONVTIME_1100USEC +// * Mode = MODE_CONTINUOUS | MODE_VOLTAGE | MODE_CURRENT +// +func (d *Device) Configure(cfg Config) { + var val uint16 + + val = uint16(cfg.AverageMode&0x7) << 9 + val |= uint16(cfg.VoltConvTime&0x7) << 6 + val |= uint16(cfg.CurrentConvTime&0x7) << 3 + val |= uint16(cfg.Mode & 0x7) + + d.WriteRegister(REG_CONFIG, val) +} + +// Resets the device, setting all registers to default values +func (d *Device) Reset() { + d.WriteRegister(REG_CONFIG, 0x8000) +} + +// Connected returns whether an INA260 has been found. +func (d *Device) Connected() bool { + return d.ReadRegister(REG_MANF_ID) == MANF_ID && + (d.ReadRegister(REG_DIE_ID)&DEVICE_ID_MASK) == DEVICE_ID +} + +// Gets the measured current in µA (max resolution 1.25mA) +func (d *Device) Current() int32 { + val := d.ReadRegister(REG_CURRENT) + + if val&0x8000 == 0 { + return int32(val) * 1250 + } + + // Two's complement, convert to signed int + return -(int32(^val) + 1) * 1250 +} + +// Gets the measured voltage in µV (max resolution 1.25mV) +func (d *Device) Voltage() int32 { + val := d.ReadRegister(REG_BUSVOLTAGE) + + if val&0x8000 == 0 { + return int32(val) * 1250 + } + + // Two's complement, convert to signed int + return -(int32(^val) + 1) * 1250 +} + +// Gets the measured power in µW (max resolution 10mW) +func (d *Device) Power() int32 { + return int32(d.ReadRegister(REG_POWER)) * 10000 +} + +// Read a register +func (d *Device) ReadRegister(reg uint8) uint16 { + data := []byte{0, 0} + d.bus.ReadRegister(uint8(d.Address), reg, data) + return (uint16(data[0]) << 8) | uint16(data[1]) +} + +// Write to a register +func (d *Device) WriteRegister(reg uint8, v uint16) { + data := []byte{0, 0} + data[0] = byte(v >> 8) + data[1] = byte(v & 0xff) + + d.bus.WriteRegister(uint8(d.Address), reg, data) +} diff --git a/ina260/ina260_test.go b/ina260/ina260_test.go new file mode 100644 index 0000000..92e7e97 --- /dev/null +++ b/ina260/ina260_test.go @@ -0,0 +1,80 @@ +package ina260 + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "tinygo.org/x/drivers/tester" +) + +func TestDefaultI2CAddress(t *testing.T) { + c := qt.New(t) + bus := tester.NewI2CBus(c) + dev := New(bus) + c.Assert(dev.Address, qt.Equals, uint16(Address)) +} + +func TestConnected(t *testing.T) { + c := qt.New(t) + bus := tester.NewI2CBus(c) + fake := tester.NewI2CDevice16(c, Address) + fake.Registers = defaultRegisters() + bus.AddDevice(fake) + + dev := New(bus) + c.Assert(dev.Connected(), qt.Equals, true) +} + +func TestVoltage(t *testing.T) { + c := qt.New(t) + bus := tester.NewI2CBus(c) + fake := tester.NewI2CDevice16(c, Address) + fake.Registers = defaultRegisters() + fake.Registers[REG_BUSVOLTAGE] = 0x2570 + bus.AddDevice(fake) + + dev := New(bus) + // Datasheet: 2570h = 11.98V = 11980mV = 11980000uV + c.Assert(dev.Voltage(), qt.Equals, int32(11980000)) +} + +func TestCurrent(t *testing.T) { + c := qt.New(t) + bus := tester.NewI2CBus(c) + fake := tester.NewI2CDevice16(c, Address) + fake.Registers = defaultRegisters() + fake.Registers[REG_CURRENT] = 0x2710 + bus.AddDevice(fake) + + dev := New(bus) + // Datasheet: 2710h = 12.5A = 12500mA = 12500000uA + c.Assert(dev.Current(), qt.Equals, int32(12500000)) +} + +func TestPower(t *testing.T) { + c := qt.New(t) + bus := tester.NewI2CBus(c) + fake := tester.NewI2CDevice16(c, Address) + fake.Registers = defaultRegisters() + fake.Registers[REG_POWER] = 0x3A7F + bus.AddDevice(fake) + + dev := New(bus) + // 3A7Fh = 149.75W = 149750mW = 149750000uW + c.Assert(dev.Power(), qt.Equals, int32(149750000)) +} + +// defaultRegisters returns the default values for all of the device's registers. +// set TI INA260 datasheet for power-on defaults +func defaultRegisters() map[uint8]uint16 { + return map[uint8]uint16{ + REG_CONFIG: 0x6127, + REG_CURRENT: 0x0000, + REG_BUSVOLTAGE: 0x0000, + REG_POWER: 0x0000, + REG_MASKENABLE: 0x0000, + REG_ALERTLIMIT: 0x0000, + REG_MANF_ID: 0x5449, + REG_DIE_ID: 0x2270, + } +} diff --git a/ina260/registers.go b/ina260/registers.go new file mode 100644 index 0000000..c1ca788 --- /dev/null +++ b/ina260/registers.go @@ -0,0 +1,50 @@ +package ina260 + +// The default I2C address for this device. +// +// The actual address is configurable by connecting address pins. +const Address = 0x40 + +// Registers +const ( + REG_CONFIG = 0x00 + REG_CURRENT = 0x01 + REG_BUSVOLTAGE = 0x02 + REG_POWER = 0x03 + REG_MASKENABLE = 0x06 + REG_ALERTLIMIT = 0x07 + REG_MANF_ID = 0xFE + REG_DIE_ID = 0xFF +) + +// Well-Known Values +const ( + MANF_ID = 0x5449 // TI + DEVICE_ID = 0x2270 // 227h + DEVICE_ID_MASK = 0xFFF0 + + AVGMODE_1 = 0 + AVGMODE_4 = 1 + AVGMODE_16 = 2 + AVGMODE_64 = 3 + AVGMODE_128 = 4 + AVGMODE_256 = 5 + AVGMODE_512 = 6 + AVGMODE_1024 = 7 + + CONVTIME_140USEC = 0 + CONVTIME_204USEC = 1 + CONVTIME_332USEC = 2 + CONVTIME_588USEC = 3 + CONVTIME_1100USEC = 4 // 1.1 ms + CONVTIME_2116USEC = 5 // 2.1 ms + CONVTIME_4156USEC = 6 // 4.2 ms + CONVTIME_8244USEC = 7 // 8.2 ms + + MODE_CONTINUOUS = 0x4 + MODE_TRIGGERED = 0x0 + MODE_VOLTAGE = 0x2 + MODE_NO_VOLTAGE = 0x0 + MODE_CURRENT = 0x1 + MODE_NO_CURRENT = 0x0 +)