blob: 98f397d1747979ac8a154619edd7dfcdfa72be4e [file] [log] [blame]
#include "ina231.h"
#include <app/zedmon/usb.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>
enum {
INA231_REG_CONFIG = 0,
INA231_REG_SHUNT_VOLTAGE = 1,
INA231_REG_BUS_VOLTAGE = 2,
INA231_REG_POWER = 3,
INA231_REG_CURRENT = 4,
INA231_REG_CALIBRATION = 5,
INA231_REG_MASK_ENABLE = 6,
INA231_REG_MASK_ALERT_LIMIT = 7,
};
enum {
INA231_CONFIG_MODE_POWER_DOWN = 0 << 0,
INA231_CONFIG_MODE_TRIG_SHUNT = 1 << 0,
INA231_CONFIG_MODE_TRIG_BUS = 2 << 0,
INA231_CONFIG_MODE_TRIG_SHUNT_BUS = 3 << 0,
INA231_CONFIG_MODE_POWER_DOWN2 = 4 << 0,
INA231_CONFIG_MODE_CONT_SHUNT = 5 << 0,
INA231_CONFIG_MODE_CONT_BUS = 6 << 0,
INA231_CONFIG_MODE_CONT_SHUNT_BUS = 7 << 0,
INA231_CONFIG_V_SHUNT_CT_140_US = 0 << 3,
INA231_CONFIG_V_SHUNT_CT_204_US = 1 << 3,
INA231_CONFIG_V_SHUNT_CT_332_US = 2 << 3,
INA231_CONFIG_V_SHUNT_CT_588_US = 3 << 3,
INA231_CONFIG_V_SHUNT_CT_1100_US = 4 << 3,
INA231_CONFIG_V_SHUNT_CT_2116_US = 5 << 3,
INA231_CONFIG_V_SHUNT_CT_4156_US = 6 << 3,
INA231_CONFIG_V_SHUNT_CT_8244_US = 7 << 3,
INA231_CONFIG_V_BUS_CT_140_US = 0 << 6,
INA231_CONFIG_V_BUS_CT_204_US = 1 << 6,
INA231_CONFIG_V_BUS_CT_332_US = 2 << 6,
INA231_CONFIG_V_BUS_CT_588_US = 3 << 6,
INA231_CONFIG_V_BUS_CT_1100_US = 4 << 6,
INA231_CONFIG_V_BUS_CT_2116_US = 5 << 6,
INA231_CONFIG_V_BUS_CT_4156_US = 6 << 6,
INA231_CONFIG_V_BUS_CT_8244_US = 7 << 6,
INA231_CONFIG_AVG_1 = 0 << 9,
INA231_CONFIG_AVG_4 = 1 << 9,
INA231_CONFIG_AVG_16 = 2 << 9,
INA231_CONFIG_AVG_64 = 3 << 9,
INA231_CONFIG_AVG_128 = 4 << 9,
INA231_CONFIG_AVG_256 = 5 << 9,
INA231_CONFIG_AVG_512 = 6 << 9,
INA231_CONFIG_AVG_1024 = 7 << 9,
INA231_CONFIG_RST = 1 << 16,
};
enum {
INA231_ME_LEN = 1 << 0, // Alert Latch Enable.
INA231_ME_APOL = 1 << 1, // Alert Polarity.
INA231_ME_OVF = 1 << 2, // Math Overflow.
INA231_ME_CRVF = 1 << 3, // Conversion Ready Flag.
INA231_ME_AFF = 1 << 4, // Alert Function Flag.
INA231_ME_CNVR = 1 << 10, // Assert Alert on Conversion Ready.
INA231_ME_POL = 1 << 11, // Assert Alert on Power Over Limit.
INA231_ME_BUL = 1 << 12, // Assert Alert on Bus Under Voltage Limit.
INA231_ME_BOL = 1 << 13, // Assert Alert on Bus Over Voltage Limit.
INA231_ME_SUL = 1 << 14, // Assert Alert on Shunt Under Voltage Limit.
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 int INA231_BUS = 2;
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 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);
if (ret != NO_ERROR) {
return ret;
}
return NO_ERROR;
}
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);
if (ret != NO_ERROR) {
return ret;
}
*val = data[0] << 8 | data[1];
return NO_ERROR;
}
lk_bigtime_t current_time_hires_irq(void);
bool stm32_exti6_irq(void) {
lk_bigtime_t now = current_time_hires_irq();
ina231_alert_event_t event = {
.channel = 0,
.time = now,
};
// 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;
}
bool stm32_exti7_irq(void) {
// TODO(konkers): Implement.
return false;
}
static bool ina231_alert0_irq(uint64_t val) {
ina231_alert_event_t event = {
.channel = 0,
.time = val,
};
// 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;
}
void ina231_init(void) {
cbuf_initialize(&ina231_alert_events, 32);
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_irq,
};
stm32_timer_capture_setup(&ina231_tc, 3, 48);
status_t ret = ina231_write(INA231_REG_MASK_ENABLE, INA231_ME_CNVR);
if (ret != NO_ERROR) {
printf("Error configuring ina231: %d\n", ret);
}
ret = ina231_write(INA231_REG_CONFIG,
INA231_CONFIG_MODE_CONT_SHUNT_BUS
| INA231_CONFIG_V_SHUNT_CT_332_US
| INA231_CONFIG_V_BUS_CT_332_US
| INA231_CONFIG_AVG_1);
if (ret != NO_ERROR) {
printf("Error configuring ina231: %d\n", ret);
}
ina231_start_time = current_time_hires();
}
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);
}
}
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);
if (ret != NO_ERROR) {
printf("error: %d\n", ret);
return 1;
}
printf("%02x: %04x\n", reg, data);
return 0;
}
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);