INA monitors/Two builds and a check/10. Three rails and an alert
Two builds and a check · 10 of 11

Three rails and an alert

Three channels read on one schedule, and two limits per channel that the chip checks itself — with an LED already wired to each, so the board notices a fault whether your sketch is looking or not.

Three channels, one schedule

The three screw terminals along the back edge are the three channels, marked CH3, CH2 and CH1 from left to right, each with a + screw facing its supply and a screw facing its load. The three-way terminal beside them is ground, one screw per rail.

Everything is also on the 14-pin header along the front — pins 4 to 9 are the same six nets as the channel terminals — so a rail can be wired with a jumper instead of a screwdriver. Use one or the other for a given channel, never both: two paths across one shunt means only part of the current goes through the resistor.

The alerts are the reason to use this board

Two limits per channel, checked by the chip
CH1 · 600.0 mA
Channel
Current on CH1600.0 mA
Reported
600.0 mA
Warning
clear
Critical
clear
Both alerts clear, both LEDs dark. At power-up both limits sit at full scale, which disables them — the chip does nothing until your sketch writes a number into the limit registers. Until then the two alert LEDs are permanently off, which looks like a board that does not work.

Two limits per channel, compared inside the chip:

Critical looks at a single conversion. It catches a spike — a motor stalling, a short — and it does it in a fraction of a millisecond, long before a sketch polling every half second would see anything.

Warning looks at the averaged current. It catches a load that is steadily drawing more than it should and ignores brief peaks, which is the opposite behaviour and the one you want for a rail that should be idling.

Both pins are open drain, and both already have a red LED on the board wired from the supply rail through 5.1 kΩ. So an alert lights an LED with nothing connected to the header and no code running — which is what makes it useful on a build you leave alone.

The two other alert pins

TC, timing control, asserts while the chip is between conversion rounds. It is for synchronising a host that wants to read only fresh data.

PV, power valid, is the one to know about because it looks broken by default. It starts low and only goes high when all three channels report a bus voltage above its upper limit, which powers up at 10 V. On a board with one channel in use it will never go high, and nothing is wrong. The datasheet's own advice is to tie an unused channel's IN− pin to a rail that is in use and leave its IN+ floating.

One thing not to do

The header has a pin marked VPU. On the chip, VPU is a separate supply that biases the power-valid output, and it is rated to 26 V — which makes the label look like an invitation to feed it a higher voltage.

On this board it is not. VPU and VS are the same copper net, so anything you put on that pin is on the chip's own supply, whose absolute maximum is 6 V. Leave it unconnected.

Setting a limit in amps

The limit registers hold a shunt voltage, not a current, in 40 microvolt steps — and the bottom three bits are unused, so the value is shifted up three places. The limitFor() helper in the sketch does the two conversions: amps × 0.05 ohms gives volts, divided by 40 microvolts gives counts, shifted left three gives the register.

1 A is 50 mV is 1250 counts, and 2 A is 2500. The register holds up to 4095, which is the 3.28 A full scale. Write it once in setup() and the chip enforces it for as long as it has power.

The code

ina3221_three_rails.ino

It reads all three channels, sets a warning limit at 1 A and a critical limit at 2 A on each, and prints which flags the chip has raised. The limits are written straight to the registers, so the two LEDs on the board light with no help from the sketch — pull the plug on the USB cable and they still work.

// INA3221 wiring for this sketch.
//
//   ESP32 GND  -> GND       (first pin on the four-pin header)
//   ESP32 3V3  -> VCC       (second pin; 2.7 to 5.5 V, and it sets the bus level)
//   ESP32 SDA  -> SDA       (THIRD pin - data before clock on this board)
//   ESP32 SCL  -> SCL       (fourth pin)
//
//   CH1 +  -> rail 1 supply     CH1 -  -> rail 1 load
//   CH2 +  -> rail 2 supply     CH2 -  -> rail 2 load
//   CH3 +  -> rail 3 supply     CH3 -  -> rail 3 load
//   every rail's negative -> one screw of the three-way GND terminal
//
// Do NOT put anything on the header pin marked VPU: on this board VPU is
// the same net as VCC, so a voltage there is a voltage on the chip's supply,
// whose absolute maximum is 6 V.
//
// Arduino IDE: any ESP32 board. No library to install.

#include <Wire.h>

const uint8_t ADDR = 0x40;     // 0x41 if the I2C ADDR pad is bridged
const float SHUNT_OHMS = 0.05; // 50 mohm on every channel - not 0.1, or every current reads half

// Registers. 0x00 config, then per channel: shunt, bus, critical, warning.
const uint8_t SHUNT[3] = {0x01, 0x03, 0x05};
const uint8_t BUS[3]   = {0x02, 0x04, 0x06};
const uint8_t CRIT[3]  = {0x07, 0x09, 0x0B};
const uint8_t WARN[3]  = {0x08, 0x0A, 0x0C};

void write16(uint8_t reg, uint16_t v) {
  Wire.beginTransmission(ADDR);
  Wire.write(reg);
  Wire.write(v >> 8);
  Wire.write(v & 0xFF);
  Wire.endTransmission();
}

int16_t read16(uint8_t reg) {
  Wire.beginTransmission(ADDR);
  Wire.write(reg);
  Wire.endTransmission(false);
  Wire.requestFrom(ADDR, (uint8_t)2);
  return (int16_t)((Wire.read() << 8) | Wire.read());
}

// A limit register holds a shunt voltage in 40 uV steps, shifted up three
// bits. Amps -> volts -> counts -> register.
uint16_t limitFor(float amps) {
  long counts = lround(amps * SHUNT_OHMS / 0.000040);
  if (counts > 4095) counts = 4095;   // 12 bits plus sign
  return (uint16_t)(counts << 3);
}

void setup() {
  Serial.begin(115200);
  delay(500);
  Wire.begin();

  // All three channels, shunt and bus, 1.1 ms conversions, 16 averages,
  // continuous. 0x7127.
  write16(0x00, 0x7127);

  for (int i = 0; i < 3; i++) {
    write16(WARN[i], limitFor(1.0));   // averaged current, gentle
    write16(CRIT[i], limitFor(2.0));   // one conversion, catches a spike
  }

  Serial.println("ch1 mA\tch1 V\tch2 mA\tch2 V\tch3 mA\tch3 V\tflags");
}

void loop() {
  for (int i = 0; i < 3; i++) {
    // Both registers ignore their bottom three bits.
    float shunt_v = (read16(SHUNT[i]) >> 3) * 0.000040;
    float bus_v   = (read16(BUS[i]) >> 3) * 0.008;
    Serial.printf("%.1f\t%.3f\t", shunt_v / SHUNT_OHMS * 1000.0, bus_v);
  }

  // Mask/Enable register 0x0F: bits 8..6 are the warning flags for
  // channels 1..3, bits 4..2 the critical flags. Reading it clears them.
  uint16_t flags = (uint16_t)read16(0x0F);
  for (int i = 0; i < 3; i++) {
    if (flags & (1 << (8 - i))) Serial.printf("WARN%d ", i + 1);
    if (flags & (1 << (4 - i))) Serial.printf("CRIT%d ", i + 1);
  }
  Serial.println();
  delay(500);
}

Registers rather than a library, because the INA3221's libraries disagree about names and this is eleven lines. Note that the limit registers hold a *shunt voltage* in 40 µV steps, not a current: 1 A across the board's 50 mΩ is 50 mV, which is 1250 counts, and the value is left-shifted three places because the bottom three bits of the register are unused.

When it does not work

Neither alert LED ever lights

At power-up both limit registers hold the positive full-scale value, which disables them — the chip does nothing until a sketch writes a real number in. Until then the two LEDs are permanently dark, which looks exactly like a board with dead LEDs. Run the sketch above, then connect a load that draws more than 2 A on one channel.

The pin marked VPU put 12 V into my board

On this board VPU is wired to the same net as VCC, so it is not the independent pull-up supply the datasheet describes — it is the chip's own supply pin brought out to the header. Feeding it anything above 5.5 V exceeds the chip's absolute maximum of 6 V. Leave that pin alone; everything the alerts need is already on the board.

A channel reads full scale and never moves

It is above 3.28 A, where the ±163.84 mV input saturates across 50 mΩ — the figure printed on the back of the board. At 3.28 A the 50 mΩ resistor dissipates about half a watt, so nothing is damaged, but a pinned number has stopped tracking the current.

The power-valid LED, or the PV pin, is stuck low

PV starts low and only goes high when *all three* channels see a bus voltage above its upper limit, which defaults to 10 V. With one or two channels unused there is nothing on them to satisfy it. Either tie the unused channels' − pins to a rail that is in use, as the datasheet suggests, or ignore PV and use critical and warning instead.

Only one channel reads and the others are zero

Each channel needs its own pair of screws — + to its supply and − to its load — and each needs the load's return in the three-way ground terminal. A channel with only one wire in it measures a shunt with no current through it, which is genuinely zero. Also check the config register: bits 14 to 12 enable the three channels individually, and 0x7127 turns on all three.

Where this goes next

Check it against a meter

Edit this page — content/books/inaset/three-rails-and-an-alert.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

This page covers several products. Choose yours to see the right discussions.

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum