|
| 1 | +#include <stdio.h> |
| 2 | +#include "pico/stdlib.h" |
| 3 | +#include "hardware/i2c.h" |
| 4 | +#include "hardware/uart.h" |
| 5 | +#include "pico/status_led.h" |
| 6 | +#include "pico/binary_info.h" |
| 7 | + |
| 8 | +#define UART_PASSTHROUGH uart1 |
| 9 | +#define UART_PASSTHROUGH_BAUD 115200 |
| 10 | +#define UART_PASSTHROUGH_RX 5 |
| 11 | + |
| 12 | +#define I2C_ADDRESS 0x40 |
| 13 | + |
| 14 | +// INA237 register addresses |
| 15 | +#define REG_CONFIG 0x00 |
| 16 | +#define REG_ADCCFG 0x01 |
| 17 | +#define REG_SHUNTCAL 0x02 |
| 18 | +#define REG_VSHUNT 0x04 |
| 19 | +#define REG_VBUS 0x05 |
| 20 | +#define REG_DIETEMP 0x06 |
| 21 | +#define REG_CURRENT 0x07 |
| 22 | +#define REG_POWER 0x08 |
| 23 | +#define REG_DIAG 0x0B |
| 24 | + |
| 25 | +// Calibration for 15 mΩ shunt resistor (Adafruit INA237 breakout default), 250 mA maximum expected current |
| 26 | +#define SHUNT_RES_OHMS 0.015f |
| 27 | +#define MAX_CURRENT_A 0.25f |
| 28 | +#define CURRENT_LSB_A (MAX_CURRENT_A / 32768.0f) |
| 29 | +// SHUNT_CAL = 819.2e6 × CURRENT_LSB × RSHUNT × 4 (ADC range 1, scale = 4) |
| 30 | +#define SHUNT_CAL_VALUE ((uint16_t)(819.2e6f * CURRENT_LSB_A * SHUNT_RES_OHMS * 4)) |
| 31 | + |
| 32 | +// ADCCFG: triggered temp+bus+shunt (mode=0x7); 1052 µs conversion time for bus, shunt & temp (=0x5); 16 sample average (=0x2) |
| 33 | +// bits [15:12]=0xF, bits [11:9]=0x5, bits [8:6]=0x5, bits [5:3]=0x5, bits [2:0]=0x2 |
| 34 | +#define ADCCFG_VALUE 0x7B6Au |
| 35 | + |
| 36 | +static void write_reg(uint8_t reg, uint16_t value) { |
| 37 | + uint8_t buf[3] = { reg, (uint8_t)(value >> 8), (uint8_t)(value & 0xFF) }; |
| 38 | + int ret = i2c_write_blocking(i2c_default, I2C_ADDRESS, buf, 3, false); |
| 39 | + assert(ret == 3); |
| 40 | +} |
| 41 | + |
| 42 | +// Read 16-bit register (the usual) |
| 43 | +static uint16_t read_reg(uint8_t reg) { |
| 44 | + int ret = i2c_write_blocking(i2c_default, I2C_ADDRESS, ®, 1, true); |
| 45 | + assert(ret == 1); |
| 46 | + if (ret != 1) return 0; |
| 47 | + uint8_t buf[2]; |
| 48 | + ret = i2c_read_blocking(i2c_default, I2C_ADDRESS, buf, 2, false); |
| 49 | + assert(ret == 2); |
| 50 | + if (ret != 2) return 0; |
| 51 | + return ((uint16_t)buf[0] << 8) | buf[1]; |
| 52 | +} |
| 53 | + |
| 54 | +// Read 24-bit register (the power register) |
| 55 | +static uint32_t read_reg3(uint8_t reg) { |
| 56 | + int ret = i2c_write_blocking(i2c_default, I2C_ADDRESS, ®, 1, true); |
| 57 | + assert(ret == 1); |
| 58 | + if (ret != 1) return 0; |
| 59 | + uint8_t buf[3]; |
| 60 | + ret = i2c_read_blocking(i2c_default, I2C_ADDRESS, buf, 3, false); |
| 61 | + assert(ret == 3); |
| 62 | + if (ret != 3) return 0; |
| 63 | + return ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | buf[2]; |
| 64 | +} |
| 65 | + |
| 66 | +static void print_uart_passthrough(void) { |
| 67 | + // Interrupts will go off until the uart is read, so disable them |
| 68 | + uart_set_irqs_enabled(UART_PASSTHROUGH, false, false); |
| 69 | + while (uart_is_readable(UART_PASSTHROUGH)) |
| 70 | + putchar(uart_getc(UART_PASSTHROUGH)); |
| 71 | + uart_set_irqs_enabled(UART_PASSTHROUGH, true, false); |
| 72 | +} |
| 73 | + |
| 74 | +int main() { |
| 75 | + stdio_init_all(); |
| 76 | +#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) |
| 77 | + #warning i2c / ina237_i2c example requires a board with I2C pins |
| 78 | + panic("Default I2C pins were not defined"); |
| 79 | +#endif |
| 80 | + bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); |
| 81 | + bi_decl(bi_1pin_with_func(UART_PASSTHROUGH_RX, GPIO_FUNC_UART)); |
| 82 | + bi_decl(bi_program_description("INA237 I2C example for the Raspberry Pi Pico")); |
| 83 | + |
| 84 | + printf("INA237 example\n"); |
| 85 | + |
| 86 | + uart_init(UART_PASSTHROUGH, UART_PASSTHROUGH_BAUD); |
| 87 | + gpio_set_function(UART_PASSTHROUGH_RX, GPIO_FUNC_UART); |
| 88 | + |
| 89 | + uint irq_num = UART_IRQ_NUM(UART_PASSTHROUGH); |
| 90 | + irq_set_exclusive_handler(irq_num, print_uart_passthrough); |
| 91 | + irq_set_enabled(irq_num, true); |
| 92 | + uart_set_irqs_enabled(UART_PASSTHROUGH, true, false); |
| 93 | + |
| 94 | + i2c_init(i2c_default, 100 * 1000); |
| 95 | + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); |
| 96 | + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); |
| 97 | + gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); |
| 98 | + gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); |
| 99 | + |
| 100 | + // Set ADC range 1: ±40.96 mV full scale (bit 4 of CONFIG), giving 1.25 µV/LSB shunt resolution |
| 101 | + write_reg(REG_CONFIG, 0x0010u); |
| 102 | + // Write shunt calibration so that CURRENT and POWER registers are scaled correctly |
| 103 | + write_reg(REG_SHUNTCAL, SHUNT_CAL_VALUE); |
| 104 | + |
| 105 | + hard_assert(status_led_init()); |
| 106 | + while (true) { |
| 107 | + status_led_set_state(true); |
| 108 | + |
| 109 | + // Trigger single conversion of bus voltage, shunt voltage and temperature |
| 110 | + // This ensures all the values will correspond to the same measurement |
| 111 | + write_reg(REG_ADCCFG, ADCCFG_VALUE); |
| 112 | + |
| 113 | + // Wait for conversion ready |
| 114 | + while (!(read_reg(REG_DIAG) & (1 << 1))) tight_loop_contents(); |
| 115 | + |
| 116 | + // Bus voltage: 3.125 mV/LSB, unsigned |
| 117 | + float v = read_reg(REG_VBUS) * 0.003125f; |
| 118 | + |
| 119 | + // Shunt voltage: 1.25 µV/LSB (ADC range 1, ±40.96 mV full scale), signed |
| 120 | + float vshunt_mv = (int16_t)read_reg(REG_VSHUNT) * 1.25f / 1000.0f; |
| 121 | + |
| 122 | + // Current: CURRENT_LSB A/LSB, signed |
| 123 | + float ma = (int16_t)read_reg(REG_CURRENT) * CURRENT_LSB_A * 1000.0f; |
| 124 | + |
| 125 | + // Power: 20 × CURRENT_LSB W/LSB, unsigned |
| 126 | + float mw = read_reg3(REG_POWER) * 0.2f * CURRENT_LSB_A * 1000.0f; |
| 127 | + |
| 128 | + float mw_calc = v * ma; |
| 129 | + |
| 130 | + // Die temperature: bits [15:4] are the 12-bit signed value, 125 m°C/LSB |
| 131 | + float temp_c = ((int16_t)read_reg(REG_DIETEMP) >> 4) * 0.125f; |
| 132 | + |
| 133 | + printf("bus: %.3f V shunt: %.3f mV current: %.2f mA power: %.2f mW (calc: %.2f mW) temp: %.2f C\n", |
| 134 | + v, vshunt_mv, ma, mw, mw_calc, temp_c); |
| 135 | + |
| 136 | + status_led_set_state(false); |
| 137 | + |
| 138 | + // Wait 1s before taking next measurement |
| 139 | + sleep_ms(1000); |
| 140 | + } |
| 141 | + return 0; |
| 142 | +} |
0 commit comments