[firmware] Add support for Zedmon v2.

Because v2 uses the ina233 which is not interface compatible with the
ina231, some refactoring was needed.

Change-Id: I0cb5fcd0bedf982b8e76ec299acec5303c80d6c4
diff --git a/firmware/app/zedmon/ina.c b/firmware/app/zedmon/ina.c
new file mode 100644
index 0000000..2bb3238
--- /dev/null
+++ b/firmware/app/zedmon/ina.c
@@ -0,0 +1,112 @@
+#include <app/zedmon/ina.h>
+
+#include <app/zedmon/usb.h>
+#include <assert.h>
+#include <err.h>
+#include <dev/i2c.h>
+#include <lib/cbuf.h>
+#include <lib/console.h>
+#include <platform.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <target/gpioconfig.h>
+
+#include "ina231.h"
+#include "ina233.h"
+
+typedef struct __attribute__((packed)) {
+    lk_bigtime_t time;
+    uint8_t channel;
+} ina_alert_event_t;
+
+static cbuf_t ina_alert_events;
+
+static bool (*ina_sample)(uint16_t *v_shunt, uint16_t *v_bus);
+static bool ina_initialized = false;
+
+static uint32_t ina_errors;
+static uint32_t ina_samples;
+static uint16_t ina_v_shunt;
+static uint16_t ina_v_bus;
+
+
+bool ina_alert_irq(int index, uint64_t timestamp) {
+    // We only handle a single channel right now.
+    assert(index == 0);
+
+    if (!ina_initialized) {
+        return false;
+    }
+
+    ina_alert_event_t event = {
+        .channel = 0,
+        .time = timestamp,
+    };
+
+    // This won't race because we're in interrupt context.
+    if (cbuf_space_avail(&ina_alert_events) >= sizeof(event)) {
+        cbuf_write(&ina_alert_events, &event, sizeof(event), false);
+        return true;
+    }
+    return false;
+}
+
+void ina_init(void) {
+    cbuf_initialize(&ina_alert_events, 32);
+
+    if (ina233_init()) {
+        printf("ina233 found\n");
+        ina_sample = ina233_sample;
+    } else if (ina231_init()) {
+        printf("ina231 found\n");
+        ina_sample = ina231_sample;
+    }
+
+    ina_initialized = true;
+
+    // call sample once to unstick the conversion ready flag.
+    uint16_t v_shunt;
+    uint16_t v_bus;
+    ina_sample(&v_shunt, &v_bus);
+}
+
+
+void ina_loop(void) {
+    while (true) {
+        ina_alert_event_t event;
+        cbuf_read(&ina_alert_events, &event, sizeof(event), true);
+
+        if (ina_sample == NULL) {
+            continue;
+        }
+
+        uint16_t v_shunt;
+        uint16_t v_bus;
+
+        if (!ina_sample(&v_shunt, &v_bus)) {
+            ina_errors++;
+            continue;
+        }
+
+        ina_v_shunt = v_shunt;
+        ina_v_bus = v_bus;
+        ina_samples++;
+        zedmon_usb_add_sample(event.time, v_shunt, v_bus);
+    }
+}
+
+static int cmd_ina_status(int argc, const cmd_args *argv) {
+    float v_shunt = ina_v_shunt * 0.0025;
+    float v_bus = ina_v_bus * 0.00125;
+
+    printf("samples: %u\n", ina_samples);
+    printf("errors: %u\n", ina_errors);
+    printf("v_bus: %f V \n", v_bus);
+    printf("v_shunt: %f mV\n", v_shunt);
+    return 0;
+}
+
+STATIC_COMMAND_START
+STATIC_COMMAND("ina_status", "ina status", &cmd_ina_status)
+STATIC_COMMAND_END(ina);
+
diff --git a/firmware/app/zedmon/ina231.c b/firmware/app/zedmon/ina231.c
index dd45bc7..3d0dd53 100644
--- a/firmware/app/zedmon/ina231.c
+++ b/firmware/app/zedmon/ina231.c
@@ -1,4 +1,4 @@
-#include <app/zedmon/ina231.h>
+#include "ina231.h"
 
 #include <app/zedmon/usb.h>
 #include <assert.h>
@@ -77,32 +77,16 @@
     INA231_ME_SOL  = 1 << 15, // Assert Alert on Shunt Over Voltage Limit.
 };
 
-typedef struct __attribute__((packed)) {
-    lk_bigtime_t time;
-    uint8_t channel;
-} ina231_alert_event_t;
-
-static cbuf_t ina231_alert_events;
-static stm32_timer_capture_t ina231_tc;
 
 static const uint8_t INA231_ADDR = 0x40;
-
-static const float INA231_SHUNT_R = 0.010;  // [Ohms]
-static lk_bigtime_t ina231_start_time;
 static uint32_t ina231_errors;
-static uint32_t ina231_samples;
-static uint16_t ina231_v_shunt;
-static uint16_t ina231_v_bus;
-
-static bool ina231_initialized = false;
-
 
 static status_t ina231_write(uint8_t reg, uint16_t val) {
     uint8_t data[2];
     data[0] = val >> 8;
     data[1] = val & 0xff;
 
-    status_t ret = i2c_write_reg_bytes(INA231_BUS, INA231_ADDR, reg, data, 2);
+    status_t ret = i2c_write_reg_bytes(INA_BUS, INA231_ADDR, reg, data, 2);
     if (ret != NO_ERROR) {
         return ret;
     }
@@ -113,7 +97,7 @@
 static status_t ina231_read(uint8_t reg, uint16_t *val) {
     uint8_t data[2];
 
-    status_t ret = i2c_read_reg_bytes(INA231_BUS, INA231_ADDR, reg, data, 2);
+    status_t ret = i2c_read_reg_bytes(INA_BUS, INA231_ADDR, reg, data, 2);
     if (ret != NO_ERROR) {
         return ret;
     }
@@ -122,46 +106,11 @@
     return NO_ERROR;
 }
 
-bool ina231_alert_irq(int index, uint64_t timestamp) {
-    // We only handle a single channel right now.
-    assert(index == 0);
-
-    if (!ina231_initialized) {
-        return false;
-    }
-
-    ina231_alert_event_t event = {
-        .channel = 0,
-        .time = timestamp,
-    };
-
-    // This won't race because we're in interrupt context.
-    if (cbuf_space_avail(&ina231_alert_events) >= sizeof(event)) {
-        cbuf_write(&ina231_alert_events, &event, sizeof(event), false);
-        return true;
-    }
-    return false;
-}
-
-static bool ina231_alert0_capture_irq(uint64_t val) {
-    return ina231_alert_irq(0, val);
-}
-
-void ina231_init() {
-    cbuf_initialize(&ina231_alert_events, 32);
-
-    if (false) {
-        ina231_tc.chan[0] = (stm32_timer_capture_channel_t){
-            .flags = STM32_TIMER_CAPTURE_CHAN_FLAG_FALLING | STM32_TIMER_CAPTURE_CHAN_FLAG_ENABLE,
-                .cb = ina231_alert0_capture_irq,
-        };
-
-        stm32_timer_capture_setup(&ina231_tc, 3, 48);
-    }
+bool ina231_init() {
 
     status_t ret = ina231_write(INA231_REG_MASK_ENABLE, INA231_ME_CNVR);
     if (ret != NO_ERROR) {
-        printf("Error configuring ina231: %d\n", ret);
+        return false;
     }
 
     ret = ina231_write(INA231_REG_CONFIG,
@@ -170,117 +119,37 @@
                        | INA231_CONFIG_V_BUS_CT_332_US
                        | INA231_CONFIG_AVG_1);
     if (ret != NO_ERROR) {
-        printf("Error configuring ina231: %d\n", ret);
+        return false;
     }
 
-    ina231_start_time = current_time_hires();
-    ina231_initialized = true;
+    return true;
 }
 
 
-void ina231_loop(void) {
-    while (true) {
-        ina231_alert_event_t event;
-        cbuf_read(&ina231_alert_events, &event, sizeof(event), true);
-        status_t ret;
-
-        uint16_t status;
-        ret = ina231_read(INA231_REG_MASK_ENABLE, &status);
-        if (ret != NO_ERROR) {
-            ina231_errors++;
-            continue;
-        }
-
-        if (!(status & INA231_ME_CRVF)) {
-            printf("alert with no crvf!\n");
-            continue;
-        }
-
-        uint16_t v_shunt;
-        uint16_t v_bus;
-        ret = ina231_read(INA231_REG_SHUNT_VOLTAGE, &v_shunt);
-        if (ret != NO_ERROR) {
-            ina231_errors++;
-            continue;
-        }
-
-        ret = ina231_read(INA231_REG_BUS_VOLTAGE, &v_bus);
-        if (ret != NO_ERROR) {
-            ina231_errors++;
-            continue;
-        }
-
-        ina231_v_shunt = v_shunt;
-        ina231_v_bus = v_bus;
-
-        ina231_samples++;
-        zedmon_usb_add_sample(event.time, v_shunt, v_bus);
-    }
-}
-
-uint64_t ina231_get_current_time(void) {
-    return stm32_timer_capture_get_counter(&ina231_tc);
-}
-
-static int cmd_ina231_status(int argc, const cmd_args *argv) {
-    lk_bigtime_t delta = current_time_hires() - ina231_start_time;
-    float samples_per_sec = (float) ina231_samples / delta * 1000000.0f;
-
-    float v_shunt = ina231_v_shunt * 0.0025;
-    float v_bus = ina231_v_bus * 0.00125;
-    float i_shunt = v_shunt / INA231_SHUNT_R;
-    float power = i_shunt * v_bus;
-
-    printf("samples: %u\n", ina231_samples);
-    printf("samples/sec: %f\n", samples_per_sec);
-    printf("errors: %u\n", ina231_errors);
-    printf("v_bus: %f V \n", v_bus);
-    printf("v_shunt: %f mV\n", v_shunt);
-    printf("i_shunt: %f mA\n", i_shunt);
-    printf("power: %f mW\n", power);
-    return 0;
-}
-
-static int cmd_ina231_dump(int argc, const cmd_args *argv) {
-    int i;
-
-    for (i = 0; i < 8; i++) {
-        uint16_t data;
-        status_t ret = ina231_read(i, &data);
-        printf("%d: ", i);
-
-        if (ret != NO_ERROR) {
-            printf("error %d\n", ret);
-        } else {
-            printf("%04x\n", data);
-        }
-    }
-
-    return 0;
-}
-
-static int cmd_ina231_read(int argc, const cmd_args *argv) {
-    if (argc != 2) {
-        printf("usage: ina_231_read <reg>\n");
-        return 1;
-    }
-
-    uint8_t reg = argv[1].u;
-    uint16_t data;
-
-    status_t ret = ina231_read(reg, &data);
+bool ina231_sample(uint16_t *v_shunt, uint16_t *v_bus) {
+    status_t ret;
+    uint16_t status;
+    ret = ina231_read(INA231_REG_MASK_ENABLE, &status);
     if (ret != NO_ERROR) {
-        printf("error: %d\n", ret);
-        return 1;
+        ina231_errors++;
+        return false;
     }
 
-    printf("%02x: %04x\n", reg, data);
-    return 0;
+    if (!(status & INA231_ME_CRVF)) {
+        printf("alert with no crvf!\n");
+        return false;
+    }
+
+    ret = ina231_read(INA231_REG_SHUNT_VOLTAGE, v_shunt);
+    if (ret != NO_ERROR) {
+        ina231_errors++;
+        return false;
+    }
+
+    ret = ina231_read(INA231_REG_BUS_VOLTAGE, v_bus);
+    if (ret != NO_ERROR) {
+        ina231_errors++;
+        return false;
+    }
+    return true;
 }
-
-STATIC_COMMAND_START
-STATIC_COMMAND("ina231_read", "read an ina231 register", &cmd_ina231_read)
-STATIC_COMMAND("ina231_dump", "dump ina231 registers", &cmd_ina231_dump)
-STATIC_COMMAND("ina231_status", "ina231 status", &cmd_ina231_status)
-STATIC_COMMAND_END(ina231);
-
diff --git a/firmware/app/zedmon/include/app/zedmon/ina231.h b/firmware/app/zedmon/ina231.h
similarity index 86%
copy from firmware/app/zedmon/include/app/zedmon/ina231.h
copy to firmware/app/zedmon/ina231.h
index bbf42a9..6bc2220 100644
--- a/firmware/app/zedmon/include/app/zedmon/ina231.h
+++ b/firmware/app/zedmon/ina231.h
@@ -25,16 +25,12 @@
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
+//
 #pragma once
 
 #include <stdbool.h>
 #include <stdint.h>
 
-void ina231_init(void);
-void ina231_loop(void);
-uint64_t ina231_get_current_time(void);
+bool ina231_init(void);
+bool ina231_sample(uint16_t *v_shunt, uint16_t *v_bus);
 
-// Target specific code should call this when it gets a falling edge on the
-// alert line (from interrupt context).
-bool ina231_alert_irq(int index, uint64_t timestamp);
diff --git a/firmware/app/zedmon/ina233.c b/firmware/app/zedmon/ina233.c
new file mode 100644
index 0000000..bc04930
--- /dev/null
+++ b/firmware/app/zedmon/ina233.c
@@ -0,0 +1,275 @@
+#include "ina233.h"
+
+#include <app/zedmon/usb.h>
+#include <assert.h>
+#include <err.h>
+#include <dev/i2c.h>
+#include <lib/cbuf.h>
+#include <lib/console.h>
+#include <platform.h>
+#include <platform/timer_capture.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <target/gpioconfig.h>
+
+enum {
+    INA233_RER_CLEAR_FAULTS        = 0x03,
+    INA233_REG_RESTORE_DEFAULT_ALL = 0x12,
+    INA233_REG_CAPABILITY          = 0x19,
+    INA233_REG_IOUT_OC_WARN_LIMIT  = 0x4a,
+    INA233_REG_VIN_OV_WARN_LIMIT   = 0x57,
+    INA233_REG_VIN_UV_WARN_LIMIT   = 0x58,
+    INA233_REG_PIN_OP_WANR_LIMIT   = 0x6B,
+    INA233_REG_STATUS_BYTE         = 0x78,
+    INA233_REG_STATUS_WORD         = 0x79,
+    INA233_REF_STATUS_IOUT         = 0x7b,
+    INA233_REG_STATUS_INPUT        = 0x7c,
+    INA233_REG_STATUS_CML          = 0x7e,
+    INA233_REG_STATUS_MFR_SPECIFIC = 0x80,
+    INA233_REG_READ_EIN            = 0x86,
+    INA233_REG_READ_VIN            = 0x88,
+    INA233_REG_READ_IN             = 0x89,
+    INA233_REG_READ_VOUT           = 0x8b,
+    INA233_REG_READ_IOUT           = 0x8c,
+    INA233_REG_READ_POUT           = 0x96,
+    INA233_REG_READ_PIN            = 0x97,
+    INA233_REG_MFR_ID              = 0x99,
+    INA233_REG_MFR_MODEL           = 0x9A,
+    INA233_REG_MFR_REVISION        = 0x9b,
+    INA233_REG_MFR_ADC_CONFIG      = 0xd0,
+    INA233_REG_MFR_READ_VSHUNT     = 0xd1,
+    INA233_REG_MFR_ALERT_MASK      = 0xd2,
+    INA233_REG_MFR_CALIBRATION     = 0xd4,
+    INA233_REG_MFR_DEVICE_CONFIG   = 0xd5,
+    INA233_REG_CLEAR_EIN           = 0xd6,
+    INA233_REG_TI_MFR_ID           = 0xe0,
+    INA233_REG_TI_MFR_MODEL        = 0xe1,
+    INA233_REF_TI_MFR_REVISION     = 0xe2,
+};
+
+enum {
+    INA233_ADC_CONFIG_MODE_POWER_DOWN     = 0 << 0,
+    INA233_ADC_CONFIG_MODE_TRIG_SHUNT     = 1 << 0,
+    INA233_ADC_CONFIG_MODE_TRIG_BUS       = 2 << 0,
+    INA233_ADC_CONFIG_MODE_TRIG_SHUNT_BUS = 3 << 0,
+    INA233_ADC_CONFIG_MODE_POWER_DOWN2    = 4 << 0,
+    INA233_ADC_CONFIG_MODE_CONT_SHUNT     = 5 << 0,
+    INA233_ADC_CONFIG_MODE_CONT_BUS       = 6 << 0,
+    INA233_ADC_CONFIG_MODE_CONT_SHUNT_BUS = 7 << 0,
+
+    INA233_ADC_CONFIG_V_SHUNT_CT_140_US  = 0 << 3,
+    INA233_ADC_CONFIG_V_SHUNT_CT_204_US  = 1 << 3,
+    INA233_ADC_CONFIG_V_SHUNT_CT_332_US  = 2 << 3,
+    INA233_ADC_CONFIG_V_SHUNT_CT_588_US  = 3 << 3,
+    INA233_ADC_CONFIG_V_SHUNT_CT_1100_US = 4 << 3,
+    INA233_ADC_CONFIG_V_SHUNT_CT_2116_US = 5 << 3,
+    INA233_ADC_CONFIG_V_SHUNT_CT_4156_US = 6 << 3,
+    INA233_ADC_CONFIG_V_SHUNT_CT_8244_US = 7 << 3,
+
+    INA233_ADC_CONFIG_V_BUS_CT_140_US  = 0 << 6,
+    INA233_ADC_CONFIG_V_BUS_CT_204_US  = 1 << 6,
+    INA233_ADC_CONFIG_V_BUS_CT_332_US  = 2 << 6,
+    INA233_ADC_CONFIG_V_BUS_CT_588_US  = 3 << 6,
+    INA233_ADC_CONFIG_V_BUS_CT_1100_US = 4 << 6,
+    INA233_ADC_CONFIG_V_BUS_CT_2116_US = 5 << 6,
+    INA233_ADC_CONFIG_V_BUS_CT_4156_US = 6 << 6,
+    INA233_ADC_CONFIG_V_BUS_CT_8244_US = 7 << 6,
+
+    INA233_ADC_CONFIG_AVG_1    = 0 << 9,
+    INA233_ADC_CONFIG_AVG_4    = 1 << 9,
+    INA233_ADC_CONFIG_AVG_16   = 2 << 9,
+    INA233_ADC_CONFIG_AVG_64   = 3 << 9,
+    INA233_ADC_CONFIG_AVG_128  = 4 << 9,
+    INA233_ADC_CONFIG_AVG_256  = 5 << 9,
+    INA233_ADC_CONFIG_AVG_512  = 6 << 9,
+    INA233_ADC_CONFIG_AVG_1024 = 7 << 9,
+
+    INA233_ADC_CONFIG_RESERVED = 1 << 14,
+};
+
+enum {
+    INA233_STATUS_MFR_IN_UV_WARNING    = 1 << 0,
+    INA233_STATUS_MFR_IN_OV_WARNING    = 1 << 1,
+    INA233_STATUS_MFR_IN_OC_WARNING    = 1 << 2,
+    INA233_STATUS_MFR_IN_OP_WARNING    = 1 << 3,
+    INA233_STATUS_MFR_COMM_FAILURE     = 1 << 4,
+    INA233_STATUS_MFR_POR              = 1 << 5,
+    INA233_STATUS_MFR_ADC_OVERFLOW     = 1 << 6,
+    INA233_STATUS_MFR_CONVERSION_READY = 1 << 7,
+};
+
+enum {
+    INA233_ALERT_MASK_IN_UV_WARNING    = 1 << 0,
+    INA233_ALERT_MASK_IN_OV_WARNING    = 1 << 1,
+    INA233_ALERT_MASK_IN_OC_WARNING    = 1 << 2,
+    INA233_ALERT_MASK_IN_OP_WARNING    = 1 << 3,
+    INA233_ALERT_MASK_COMM_FAILURE     = 1 << 4,
+    INA233_ALERT_MASK_POR              = 1 << 5,
+    INA233_ALERT_MASK_ADC_OVERFLOW     = 1 << 6,
+    INA233_ALERT_MASK_CONVERSION_READY = 1 << 7,
+};
+
+static const uint16_t INA233_MFR_ID = 0x5449;
+static const uint16_t INA233_MFR_MODEL = 0x3333;
+static const uint8_t INA233_ADDR = 0x40;
+
+static uint32_t ina233_errors;
+
+static status_t ina233_write16(uint8_t reg, uint16_t val) {
+    uint8_t data[2];
+    data[1] = val >> 8;
+    data[0] = val & 0xff;
+
+    status_t ret = i2c_write_reg_bytes(INA_BUS, INA233_ADDR, reg, data, 2);
+    if (ret != NO_ERROR) {
+        return ret;
+    }
+
+    return NO_ERROR;
+}
+
+static status_t ina233_write8(uint8_t reg, uint8_t val) {
+    status_t ret = i2c_write_reg_bytes(INA_BUS, INA233_ADDR, reg, &val, 1);
+    if (ret != NO_ERROR) {
+        return ret;
+    }
+
+    return NO_ERROR;
+}
+
+static status_t ina233_read8(uint8_t reg, uint8_t *val) {
+    status_t ret = i2c_read_reg_bytes(INA_BUS, INA233_ADDR, reg, val, 1);
+    if (ret != NO_ERROR) {
+        return ret;
+    }
+    return NO_ERROR;
+}
+
+static status_t ina233_read16(uint8_t reg, uint16_t *val) {
+    uint8_t data[2];
+
+    status_t ret = i2c_read_reg_bytes(INA_BUS, INA233_ADDR, reg, data, 2);
+    if (ret != NO_ERROR) {
+        return ret;
+    }
+
+    *val = data[1] << 8 | data[0];
+    return NO_ERROR;
+}
+
+bool ina233_init(void) {
+    status_t ret;
+
+    // Try to read TI_MFR_ID and TI_MFR_MODEL to detect device
+    uint16_t mfr_id;
+    ret = ina233_read16(INA233_REG_TI_MFR_ID, &mfr_id);
+    if (ret != NO_ERROR || mfr_id != INA233_MFR_ID) {
+        return false;
+    }
+
+    uint16_t mfr_model;
+    ret = ina233_read16(INA233_REG_TI_MFR_MODEL, &mfr_model);
+    if (ret != NO_ERROR || mfr_model != INA233_MFR_MODEL) {
+        return false;
+    }
+
+    // At this point we know we're speaking to an ina233.
+    ret = ina233_write16(INA233_REG_MFR_ADC_CONFIG,
+                       INA233_ADC_CONFIG_MODE_CONT_SHUNT_BUS
+                       | INA233_ADC_CONFIG_V_SHUNT_CT_332_US
+                       | INA233_ADC_CONFIG_V_BUS_CT_332_US
+                       | INA233_ADC_CONFIG_AVG_1
+                       | INA233_ADC_CONFIG_RESERVED);
+    if (ret != NO_ERROR) {
+        return false;
+    }
+
+    ret = ina233_write8(INA233_REG_MFR_ALERT_MASK,
+                                 ~INA233_ALERT_MASK_CONVERSION_READY);
+    if (ret != NO_ERROR) {
+        return false;
+    }
+
+    ret = ina233_write8(INA233_REG_STATUS_MFR_SPECIFIC, 0xff);
+    if (ret != NO_ERROR) {
+        return false;
+    }
+
+    return true;
+}
+
+
+bool ina233_sample(uint16_t *v_shunt, uint16_t *v_bus) {
+    status_t ret;
+
+    uint8_t status;
+    ret = ina233_read8(INA233_REG_STATUS_MFR_SPECIFIC, &status);
+    if (ret != NO_ERROR) {
+        ina233_errors++;
+        return false;
+    }
+
+    // Ack the status.
+    ret = ina233_write8(INA233_REG_STATUS_MFR_SPECIFIC, status);
+    if (ret != NO_ERROR) {
+        ina233_errors++;
+        return false;
+    }
+
+    ret = ina233_read16(INA233_REG_MFR_READ_VSHUNT, v_shunt);
+    if (ret != NO_ERROR) {
+        ina233_errors++;
+        return false;
+    }
+
+    ret = ina233_read16(INA233_REG_READ_VIN, v_bus);
+    if (ret != NO_ERROR) {
+        ina233_errors++;
+        return false;
+    }
+
+    return true;
+}
+
+static int cmd_ina_read16(int argc, const cmd_args *argv) {
+    if (argc != 2) {
+        printf("usage: ina_233_read <reg>\n");
+        return 1;
+    }
+
+    uint8_t reg = argv[1].u;
+    uint16_t data;
+
+    status_t ret = ina233_read16(reg, &data);
+    if (ret != NO_ERROR) {
+        printf("error: %d\n", ret);
+        return 1;
+    }
+
+    printf("%02x: %04x\n", reg, data);
+    return 0;
+}
+
+static int cmd_ina_read8(int argc, const cmd_args *argv) {
+    if (argc != 2) {
+        printf("usage: ina_233_read <reg>\n");
+        return 1;
+    }
+
+    uint8_t reg = argv[1].u;
+    uint8_t data;
+
+    status_t ret = ina233_read8(reg, &data);
+    if (ret != NO_ERROR) {
+        printf("error: %d\n", ret);
+        return 1;
+    }
+
+    printf("%02x: %02x\n", reg, data);
+    return 0;
+}
+
+STATIC_COMMAND_START
+STATIC_COMMAND("ina_read8", "read an ina233 register", &cmd_ina_read8)
+STATIC_COMMAND("ina_read16", "read an ina233 register", &cmd_ina_read16)
+STATIC_COMMAND_END(ina233);
diff --git a/firmware/app/zedmon/include/app/zedmon/ina231.h b/firmware/app/zedmon/ina233.h
similarity index 85%
copy from firmware/app/zedmon/include/app/zedmon/ina231.h
copy to firmware/app/zedmon/ina233.h
index bbf42a9..efeca06 100644
--- a/firmware/app/zedmon/include/app/zedmon/ina231.h
+++ b/firmware/app/zedmon/ina233.h
@@ -25,16 +25,11 @@
 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
+//
 #pragma once
 
 #include <stdbool.h>
 #include <stdint.h>
 
-void ina231_init(void);
-void ina231_loop(void);
-uint64_t ina231_get_current_time(void);
-
-// Target specific code should call this when it gets a falling edge on the
-// alert line (from interrupt context).
-bool ina231_alert_irq(int index, uint64_t timestamp);
+bool ina233_init(void);
+bool ina233_sample(uint16_t *v_shunt, uint16_t *v_bus);
diff --git a/firmware/app/zedmon/include/app/zedmon/ina231.h b/firmware/app/zedmon/include/app/zedmon/ina.h
similarity index 87%
rename from firmware/app/zedmon/include/app/zedmon/ina231.h
rename to firmware/app/zedmon/include/app/zedmon/ina.h
index bbf42a9..ca998c1 100644
--- a/firmware/app/zedmon/include/app/zedmon/ina231.h
+++ b/firmware/app/zedmon/include/app/zedmon/ina.h
@@ -31,10 +31,13 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-void ina231_init(void);
-void ina231_loop(void);
-uint64_t ina231_get_current_time(void);
+void ina_init(void);
+void ina_loop(void);
 
 // Target specific code should call this when it gets a falling edge on the
 // alert line (from interrupt context).
-bool ina231_alert_irq(int index, uint64_t timestamp);
+bool ina_alert_irq(int index, uint64_t timestamp);
+
+// Target specific code that returns the current time in the same time domain
+// as ina_alert_irq()s timestamp field.
+uint64_t ina_get_current_time(void);
diff --git a/firmware/app/zedmon/main.c b/firmware/app/zedmon/main.c
index 289e10a..0e35140 100644
--- a/firmware/app/zedmon/main.c
+++ b/firmware/app/zedmon/main.c
@@ -1,5 +1,5 @@
 #include <app.h>
-#include <app/zedmon/ina231.h>
+#include <app/zedmon/ina.h>
 #include <dev/gpio.h>
 #include <kernel/thread.h>
 #include <lib/console.h>
@@ -9,14 +9,14 @@
 
 
 static void zedmon_init(const struct app_descriptor *app) {
-    ina231_init();
+    ina_init();
     printf("zedmon app initialized\n");
 }
 
 static void zedmon_entry(const struct app_descriptor *app, void *args)
 {
     printf("zedmon app started\n");
-    ina231_loop();
+    ina_loop();
 }
 
 static int cmd_relay(int argc, const cmd_args *argv) {
diff --git a/firmware/app/zedmon/rules.mk b/firmware/app/zedmon/rules.mk
index 24651ef..c7b2d78 100644
--- a/firmware/app/zedmon/rules.mk
+++ b/firmware/app/zedmon/rules.mk
@@ -3,7 +3,9 @@
 MODULE := $(LOCAL_DIR)
 
 MODULE_SRCS += \
+	$(LOCAL_DIR)/ina.c \
 	$(LOCAL_DIR)/ina231.c \
+	$(LOCAL_DIR)/ina233.c \
 	$(LOCAL_DIR)/main.c \
 	$(LOCAL_DIR)/usb.c
 
diff --git a/firmware/app/zedmon/usb.c b/firmware/app/zedmon/usb.c
index 6b31556..96f0b7e 100644
--- a/firmware/app/zedmon/usb.c
+++ b/firmware/app/zedmon/usb.c
@@ -1,7 +1,7 @@
 #include <app/zedmon/usb.h>
 
 #include <assert.h>
-#include <app/zedmon/ina231.h>
+#include <app/zedmon/ina.h>
 #include <app/zedmon/usb_proto.h>
 #include <compiler.h>
 #include <debug.h>
@@ -254,7 +254,7 @@
             break;
         case ZEDMON_USB_PACKET_QUERY_TIME:
             zedmon_usb_timestamp_packet.packet_type = ZEDMON_USB_PACKET_TIMESTAMP;
-            zedmon_usb_timestamp_packet.timestamp = ina231_get_current_time();
+            zedmon_usb_timestamp_packet.timestamp = ina_get_current_time();
             queue_tx(&zedmon_usb_timestamp_packet, sizeof(zedmon_usb_timestamp_packet));
             break;
         case ZEDMON_USB_PACKET_ENABLE_REPORTING:
diff --git a/firmware/target/twinkie/include/target/gpioconfig.h b/firmware/target/twinkie/include/target/gpioconfig.h
index 0d95432..784804f 100644
--- a/firmware/target/twinkie/include/target/gpioconfig.h
+++ b/firmware/target/twinkie/include/target/gpioconfig.h
@@ -40,5 +40,5 @@
 #define GPIO_UART_TX   GPIO(GPIO_PORT_A, 9)
 #define GPIO_UART_RX   GPIO(GPIO_PORT_A, 10)
 
-#define INA231_BUS 1
+#define INA_BUS 1
 #endif
diff --git a/firmware/target/twinkie/init.c b/firmware/target/twinkie/init.c
index f52e0ef..7ea8645 100644
--- a/firmware/target/twinkie/init.c
+++ b/firmware/target/twinkie/init.c
@@ -26,7 +26,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-#include <app/zedmon/ina231.h>
+#include <app/zedmon/ina.h>
 #include <err.h>
 #include <debug.h>
 #include <target.h>
@@ -45,9 +45,14 @@
 int irqs;
 bool stm32_exti2_irq(void) {
     irqs++;
-    return ina231_alert_irq(0, current_time_hires());
+    return ina_alert_irq(0, current_time_hires());
 }
 
+uint64_t ina_get_current_time(void) {
+    return current_time_hires();
+}
+
+
 void target_early_init(void)
 {
     gpio_config(GPIO_UART_TX, GPIO_STM32_AF | GPIO_STM32_AFn(1));
diff --git a/firmware/target/zedmon/include/target/gpioconfig.h b/firmware/target/zedmon/include/target/gpioconfig.h
index de251bd..3d955c5 100644
--- a/firmware/target/zedmon/include/target/gpioconfig.h
+++ b/firmware/target/zedmon/include/target/gpioconfig.h
@@ -44,6 +44,6 @@
 
 #define GPIO_POWER_ENABLE_J GPIO(GPIO_PORT_B, 12)
 
-#define INA231_BUS 2
+#define INA_BUS 2
 
 #endif
diff --git a/firmware/target/zedmon/init.c b/firmware/target/zedmon/init.c
index 9ca5d08..b4cc607 100644
--- a/firmware/target/zedmon/init.c
+++ b/firmware/target/zedmon/init.c
@@ -26,20 +26,31 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+#include <app/zedmon/ina.h>
 #include <err.h>
 #include <debug.h>
-#include <target.h>
 #include <compiler.h>
 #include <dev/gpio.h>
 #include <dev/i2c.h>
 #include <lib/console.h>
 #include <platform/gpio.h>
 #include <platform/stm32.h>
+#include <platform/timer_capture.h>
 #include <platform/usbc.h>
+#include <target.h>
 #include <target/gpioconfig.h>
 #include <target/usb.h>
 
 static uint8_t board_id;
+static stm32_timer_capture_t zedmon_alert_tc;
+
+static bool zedmon_alert0_capture_irq(uint64_t val) {
+    return ina_alert_irq(0, val);
+}
+
+uint64_t ina_get_current_time(void) {
+    return stm32_timer_capture_get_counter(&zedmon_alert_tc);
+}
 
 void target_early_init(void)
 {
@@ -51,10 +62,10 @@
     gpio_config(GPIO_ALERT_HIGH, GPIO_STM32_AF | GPIO_STM32_AFn(1));
     gpio_config(GPIO_ALERT_LOW, GPIO_STM32_AF | GPIO_STM32_AFn(1));
 
-    gpio_config(GPIO_BOARD_ID0, GPIO_INPUT);
-    gpio_config(GPIO_BOARD_ID1, GPIO_INPUT);
-    gpio_config(GPIO_BOARD_ID2, GPIO_INPUT);
-    gpio_config(GPIO_BOARD_ID3, GPIO_INPUT);
+    gpio_config(GPIO_BOARD_ID0, GPIO_INPUT | GPIO_PULLDOWN);
+    gpio_config(GPIO_BOARD_ID1, GPIO_INPUT | GPIO_PULLDOWN);
+    gpio_config(GPIO_BOARD_ID2, GPIO_INPUT | GPIO_PULLDOWN);
+    gpio_config(GPIO_BOARD_ID3, GPIO_INPUT | GPIO_PULLDOWN);
 
     gpio_set(GPIO_LED_RED, 1);
     gpio_set(GPIO_LED_GREEN, 1);
@@ -74,6 +85,13 @@
 
     stm32_debug_early_init();
     i2c_init_early();
+
+    zedmon_alert_tc.chan[0] = (stm32_timer_capture_channel_t){
+        .flags = STM32_TIMER_CAPTURE_CHAN_FLAG_FALLING | STM32_TIMER_CAPTURE_CHAN_FLAG_ENABLE,
+            .cb = zedmon_alert0_capture_irq,
+    };
+
+    stm32_timer_capture_setup(&zedmon_alert_tc, 3, 48);
 }
 
 void target_init(void)
@@ -81,6 +99,7 @@
     stm32_debug_init();
     i2c_init();
     stm32_usbc_init();
+
     target_usb_setup();
 
     printf("zedmon online.  board_id=%1x\n", board_id);