Skip to content

Commit 2f2fc35

Browse files
authored
Add ina237 and ina219 examples (#770)
* Add ina219 and ina237 examples * Add uart passthrough to ina237 example * Fixup ina260 readme wording
1 parent 9211cb6 commit 2f2fc35

6 files changed

Lines changed: 283 additions & 1 deletion

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ App|Description
152152
---|---
153153
[bus_scan](i2c/bus_scan) | Scan the I2C bus for devices and display results.
154154
[bmp280_i2c](i2c/bmp280_i2c) | Read and convert temperature and pressure data from a BMP280 sensor, attached to an I2C bus.
155-
[ina260_i2c](i2c/ina260_i2c) | Monitor power usage of another Pico device ina260 sensor, via I2C.
155+
[ina219_i2c](i2c/ina219_i2c) | Monitor power usage of another Pico device using an ina219 sensor, via I2C.
156+
[ina237_i2c](i2c/ina237_i2c) | Monitor power usage of another Pico device using an ina237 sensor, via I2C.
157+
[ina260_i2c](i2c/ina260_i2c) | Monitor power usage of another Pico device using an ina260 sensor, via I2C.
156158
[lcd_1602_i2c](i2c/lcd_1602_i2c) | Display some text on a generic 16x2 character LCD display, via I2C.
157159
[lis3dh_i2c](i2c/lis3dh_i2c) | Read acceleration and temperature value from a LIS3DH sensor via I2C
158160
[mcp9808_i2c](i2c/mcp9808_i2c) | Read temperature from a MCP9808 sensor, set limits and raise alerts when limits are surpassed.

i2c/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ if (TARGET hardware_i2c)
1313
add_subdirectory_exclude_platforms(ht16k33_i2c)
1414
add_subdirectory_exclude_platforms(slave_mem_i2c)
1515
add_subdirectory_exclude_platforms(ina260_i2c)
16+
add_subdirectory_exclude_platforms(ina219_i2c)
17+
add_subdirectory_exclude_platforms(ina237_i2c)
1618
else()
1719
message("Skipping I2C examples as hardware_i2c is unavailable on this platform")
1820
endif()

i2c/ina219_i2c/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
add_executable(ina219_i2c
2+
ina219_i2c.c
3+
)
4+
target_link_libraries(ina219_i2c
5+
pico_stdlib
6+
pico_status_led
7+
hardware_i2c
8+
)
9+
# Wait at most 3s for stdio_usb to be ready
10+
target_compile_definitions(ina219_i2c PRIVATE
11+
PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS=3000
12+
)
13+
pico_add_extra_outputs(ina219_i2c)
14+
pico_enable_stdio_usb(ina219_i2c 1)
15+
pico_enable_stdio_uart(ina219_i2c 1)

i2c/ina219_i2c/ina219_i2c.c

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#include <stdio.h>
2+
#include "pico/stdlib.h"
3+
#include "hardware/i2c.h"
4+
#include "pico/status_led.h"
5+
#include "pico/binary_info.h"
6+
7+
#define I2C_ADDRESS 0x40
8+
9+
// INA219 register addresses
10+
#define REG_CONFIG 0x00
11+
#define REG_SHUNTVOLT 0x01
12+
#define REG_BUSVOLT 0x02
13+
#define REG_POWER 0x03
14+
#define REG_CURRENT 0x04
15+
#define REG_CALIBRATION 0x05
16+
17+
// CONFIG: 16V bus range, gain ÷1 (±40mV shunt), 12-bit 32-sample averaging, continuous bus+shunt
18+
// bit13=0 (16V), bits[12:11]=00 (÷1), bits[10:7]=0xD (32S), bits[6:3]=0xD (32S), bits[2:0]=7
19+
#define CONFIG_VALUE 0x06EFu
20+
21+
// Calibration for 0.1 Ω shunt resistor (Adafruit INA219 breakout default), 400 mA maximum current
22+
// CAL = 0.04096 / (current_lsb_A × rshunt) = 0.04096 / (0.0001 × 0.1) = 4096
23+
#define CALIBRATION_VALUE 4096u
24+
#define CURRENT_LSB_MA 0.1f // 0.04096 / (4096 × 0.1 Ω) × 1000 mA/LSB
25+
#define POWER_LSB_MW 2.0f // 20 × CURRENT_LSB_MA mW/LSB
26+
27+
// Shunt voltage: signed 16-bit, 10 µV/LSB = 0.01 mV/LSB
28+
// Bus voltage: bits [15:3], 4 mV/LSB; bit 1 = conversion ready, bit 0 = math overflow
29+
#define VSHUNT_LSB_MV 0.01f
30+
#define VBUS_LSB_MV 4.0f
31+
#define BUSVOLT_OVF 0x01u
32+
33+
static void write_reg(uint8_t reg, uint16_t value) {
34+
uint8_t buf[3] = { reg, (uint8_t)(value >> 8), (uint8_t)(value & 0xFF) };
35+
int ret = i2c_write_blocking(i2c_default, I2C_ADDRESS, buf, 3, false);
36+
assert(ret == 3);
37+
}
38+
39+
static uint16_t read_reg(uint8_t reg) {
40+
int ret = i2c_write_blocking(i2c_default, I2C_ADDRESS, &reg, 1, true);
41+
assert(ret == 1);
42+
if (ret != 1) return 0;
43+
uint8_t buf[2];
44+
ret = i2c_read_blocking(i2c_default, I2C_ADDRESS, buf, 2, false);
45+
assert(ret == 2);
46+
if (ret != 2) return 0;
47+
return ((uint16_t)buf[0] << 8) | buf[1];
48+
}
49+
50+
int main() {
51+
stdio_init_all();
52+
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
53+
#warning i2c / ina219_i2c example requires a board with I2C pins
54+
panic("Default I2C pins were not defined");
55+
#endif
56+
bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));
57+
bi_decl(bi_program_description("INA219 I2C example for the Raspberry Pi Pico"));
58+
59+
printf("INA219 example\n");
60+
61+
i2c_init(i2c_default, 100 * 1000);
62+
gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
63+
gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
64+
gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
65+
gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
66+
67+
write_reg(REG_CONFIG, CONFIG_VALUE);
68+
write_reg(REG_CALIBRATION, CALIBRATION_VALUE);
69+
70+
hard_assert(status_led_init());
71+
while (true) {
72+
status_led_set_state(true);
73+
74+
// Re-write calibration each iteration: a sharp load transient can reset the INA219,
75+
// clearing the calibration register and making CURRENT and POWER read zero
76+
write_reg(REG_CALIBRATION, CALIBRATION_VALUE);
77+
78+
// Shunt voltage: 10 µV/LSB, signed
79+
float vshunt_mv = (int16_t)read_reg(REG_SHUNTVOLT) * VSHUNT_LSB_MV;
80+
81+
// Bus voltage: bits [15:3] at 4 mV/LSB; bit 0 is the math overflow flag
82+
uint16_t bus_raw = read_reg(REG_BUSVOLT);
83+
float vbus_v = (bus_raw >> 3) * VBUS_LSB_MV / 1000.0f;
84+
85+
// Current: signed, CURRENT_LSB_MA mA/LSB
86+
float ma = (int16_t)read_reg(REG_CURRENT) * CURRENT_LSB_MA;
87+
88+
// Power: unsigned, POWER_LSB_MW mW/LSB
89+
float mw = read_reg(REG_POWER) * POWER_LSB_MW;
90+
91+
// INA219 measures on the high side: VIN+ is the supply, VIN- is the load
92+
float vin_v = vbus_v + vshunt_mv / 1000.0f;
93+
94+
printf("VIN+: %.3f V VIN-: %.3f V shunt: %.3f mV current: %.2f mA power: %.2f mW\n",
95+
vin_v, vbus_v, vshunt_mv, ma, mw);
96+
97+
if (bus_raw & BUSVOLT_OVF) {
98+
printf("Math overflow - measurement out of range\n");
99+
}
100+
101+
status_led_set_state(false);
102+
sleep_ms(1000);
103+
}
104+
return 0;
105+
}

i2c/ina237_i2c/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
add_executable(ina237_i2c
2+
ina237_i2c.c
3+
)
4+
target_link_libraries(ina237_i2c
5+
pico_stdlib
6+
pico_status_led
7+
hardware_i2c
8+
hardware_uart
9+
)
10+
# Wait at most 3s for stdio_usb to be ready
11+
target_compile_definitions(ina237_i2c PRIVATE
12+
PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS=3000
13+
)
14+
pico_add_extra_outputs(ina237_i2c)
15+
pico_enable_stdio_usb(ina237_i2c 1)
16+
pico_enable_stdio_uart(ina237_i2c 1)

i2c/ina237_i2c/ina237_i2c.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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, &reg, 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, &reg, 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

Comments
 (0)