| #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); |
| |