1. 任务目标
使用开发板实现对INA219的负载测量5V,12V电压电流并显示在OLED
2. INA219 介绍
INA219 是一款具备 I2C 或 SMBUS 兼容接口的分流器和功率监测计。该器件监测分流器电压降和总线电源电压,转换次数和滤波选项可通过编程设定。可编程校准值与内部乘法器相结合,支持直接读取电流值(单位:安培)。通过附加乘法寄存器可计算功率(单位:瓦)。I2C 或 SMBUS 兼容接口具有 16 个可编程地址。INA219 可在 0V 至 26V 范围内感测总线中的分压。该器件由 3V 至 5.5V 单电源供电,芯片的最大功耗为 1mA。该设备可用于服务器、通信设备、笔记本、电源管理、充电器、电焊、电源和测试设备等。

3. 连接关系
1. 主控通过I2C接口连接到 INA219 和 OLED,共用一个I2C控制器,管脚也共用;
2. 电源的 VCC 接到INA219的IN+,经过 INA219 内部电路从 IN- 流出,这一端接到负载(电机)的一端;
3. 电源的 GND 和 INA219 的 GND 以及负载的 GND 连接;

4. INA219 驱动
这里直接采用 libdriver_ina219 仓库中的代码,结构如下:
1. 驱动核心代码是 src/ 目录下的源文件
2. 适配层在 interface/ 目录下,移植到新的平台只需要适配 driver_ina219_interface_template.c 中的6个函数即可

4.1 适配层
把 libdriver_ina219 的代码拷贝到工程中,它的适配层都在源文件 stm32f411_driver_ina219_interface.c 中。

4.1.1 函数 ina219_interface_iic_init()
由 STM32CubeMX 配置工程,使能 I2C1,PB8 作为 SCL,PB9 作为 SDA,并且插上 Seeed Base Shield 扩展板,多个 I2C 从器件可以共用一组 I2C 管脚,只需要注意使用不同的 I2C 从器件地址即可。
由于 MX_I2C1_Init() 已经初始化了 I2C 管脚,这里留空。
#include <stdarg.h>
#include "driver_ina219_interface.h"
#include "stm32f4xx_hal.h"
#include "i2c.h"
#define hi2cx hi2c1
/**
* @brief interface iic bus init
* @return status code
* - 0 success
* - 1 iic init failed
* @note none
*/
uint8_t ina219_interface_iic_init(void)
{
return 0;
}4.1.2 函数 ina219_interface_iic_deinit()
/**
* @brief interface iic bus deinit
* @return status code
* - 0 success
* - 1 iic deinit failed
* @note none
*/
uint8_t ina219_interface_iic_deinit(void)
{
HAL_I2C_DeInit(&hi2cx);
return 0;
}4.1.3 函数 ina219_interface_iic_read()
直接调用 HAL_I2C_Mem_Read() 实现读取功能。
/**
* @brief interface iic bus read
* @param[in] addr iic device write address
* @param[in] reg iic register address
* @param[out] *buf pointer to a data buffer
* @param[in] len length of the data buffer
* @return status code
* - 0 success
* - 1 read failed
* @note none
*/
uint8_t ina219_interface_iic_read(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len)
{
HAL_StatusTypeDef ret;
ret = HAL_I2C_Mem_Read(&hi2cx, addr, reg, 1, buf, len, HAL_MAX_DELAY);
if (HAL_OK != ret) {
return 1;
}
return 0;
}4.1.4 函数 ina219_interface_iic_write()
直接调用 HAL_I2C_Mem_Write() 实现写寄存器功能。
/**
* @brief interface iic bus write
* @param[in] addr iic device write address
* @param[in] reg iic register address
* @param[in] *buf pointer to a data buffer
* @param[in] len length of the data buffer
* @return status code
* - 0 success
* - 1 write failed
* @note none
*/
uint8_t ina219_interface_iic_write(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len)
{
HAL_StatusTypeDef ret;
ret = HAL_I2C_Mem_Write(&hi2cx, addr, reg, 1, buf, len, HAL_MAX_DELAY);
if (HAL_OK != ret) {
return 1;
}
return 0;
}4.1.5 函数 ina219_interface_delay_ms()
直接调用 HAL_Delay() 实现延时功能。
/**
* @brief interface delay ms
* @param[in] ms time
* @note none
*/
void ina219_interface_delay_ms(uint32_t ms)
{
HAL_Delay(ms);
}4.1.6 函数 ina219_interface_debug_print()
由于把标注输入输出重定向到串口,即 printf() 可以直接打印字符串,所以这里屏蔽了 UART 硬件操作。
/**
* @brief interface print format data
* @param[in] fmt format data
* @note none
*/
void ina219_interface_debug_print(const char *const fmt, ...)
{
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}4.2 驱动的封装
在文件 driver_ina219_basic.c 文件中封装 libdriver_ina219 ,实现用户自定义的3个函数,实现 INA219 设备的初始化、反初始化、数据读取。
4.2.1 函数 ina219_basic_init()
在此文件中定义了一个全局变量 ina219_handle_t gs_handle,保存了INA219设备的I2C 从器件地址,适配层API实现,以及设备的状态变量。
• 此函数线把适配层函数挂接到 gs_handle 上
• 调用 ina219_set_addr_pin() 设置I2C地址
• 调用 ina219_set_resistance() 设置电流传感器的分流电阻值
• 调用 ina219_init() 初始化I2C硬件接口,读 INA219_REG_CONF 寄存器,并进行软件复位
• 调用 ina219_set_bus_voltage_range() 设置 INA219 电流传感器的总线电压测量范围
• 调用 ina219_set_bus_voltage_adc_mode() 设置 INA219 电流传感器的总线电压ADC采样模式,即ADC采样精度和转换时间
• 调用 ina219_set_shunt_voltage_adc_mode() 设置 INA219 电流传感器的分流电阻电压ADC采样模式
• 调用 ina219_set_mode() 设置 INA219 电流传感器的工作模式,例如连续测量分流电压和总线电压
• 调用 ina219_set_pga() 设置 INA219 电流传感器的可编程增益放大器PGA参数,例如 ±320mV 量程
• 调用 ina219_calculate_clibration() 和 ina219_set_calibration() 对 INA219 电流传感器进行校准值计算和配置
#include "driver_ina219_basic.h"
static ina219_handle_t gs_handle; /**< ina219 handle */
/**
* @brief basic example init
* @param[in] addr_pin iic address pin
* @param[in] r reference resistor value
* @return status code
* - 0 success
* - 1 init failed
* @note none
*/
uint8_t ina219_basic_init(ina219_address_t addr_pin, double r)
{
uint8_t res;
uint16_t calibration;
/* link interface function */
DRIVER_INA219_LINK_INIT(&gs_handle, ina219_handle_t);
DRIVER_INA219_LINK_IIC_INIT(&gs_handle, ina219_interface_iic_init);
DRIVER_INA219_LINK_IIC_DEINIT(&gs_handle, ina219_interface_iic_deinit);
DRIVER_INA219_LINK_IIC_READ(&gs_handle, ina219_interface_iic_read);
DRIVER_INA219_LINK_IIC_WRITE(&gs_handle, ina219_interface_iic_write);
DRIVER_INA219_LINK_DELAY_MS(&gs_handle, ina219_interface_delay_ms);
DRIVER_INA219_LINK_DEBUG_PRINT(&gs_handle, ina219_interface_debug_print);
/* set addr pin */
res = ina219_set_addr_pin(&gs_handle, addr_pin);
if (res != 0)
{
ina219_interface_debug_print("ina219: set addr pin failed.\n");
return 1;
}
/* set the r */
res = ina219_set_resistance(&gs_handle, r);
if (res != 0)
{
ina219_interface_debug_print("ina219: set resistance failed.\n");
return 1;
}
/* init */
res = ina219_init(&gs_handle);
if (res != 0)
{
ina219_interface_debug_print("ina219: init failed.\n");
return 1;
}
/* set bus voltage range */
res = ina219_set_bus_voltage_range(&gs_handle, INA219_BASIC_DEFAULT_BUS_VOLTAGE_RANGE);
if (res != 0)
{
ina219_interface_debug_print("ina219: set bus voltage range failed.\n");
(void)ina219_deinit(&gs_handle);
return 1;
}
/* set bus voltage adc mode */
res = ina219_set_bus_voltage_adc_mode(&gs_handle, INA219_BASIC_DEFAULT_BUS_VOLTAGE_ADC_MODE);
if (res != 0)
{
ina219_interface_debug_print("ina219: set bus voltage adc mode failed.\n");
(void)ina219_deinit(&gs_handle);
return 1;
}
/* set shunt voltage adc mode */
res = ina219_set_shunt_voltage_adc_mode(&gs_handle, INA219_BASIC_DEFAULT_SHUNT_VOLTAGE_ADC_MODE);
if (res != 0)
{
ina219_interface_debug_print("ina219: set shunt voltage adc mode failed.\n");
(void)ina219_deinit(&gs_handle);
return 1;
}
/* set shunt bus voltage continuous */
res = ina219_set_mode(&gs_handle, INA219_MODE_SHUNT_BUS_VOLTAGE_CONTINUOUS);
if (res != 0)
{
ina219_interface_debug_print("ina219: set mode failed.\n");
(void)ina219_deinit(&gs_handle);
return 1;
}
/* set pga */
res = ina219_set_pga(&gs_handle, INA219_BASIC_DEFAULT_PGA);
if (res != 0)
{
ina219_interface_debug_print("ina219: set pga failed.\n");
(void)ina219_deinit(&gs_handle);
return 1;
}
/* calculate calibration */
res = ina219_calculate_calibration(&gs_handle, (uint16_t *)&calibration);
if (res != 0)
{
ina219_interface_debug_print("ina219: calculate calibration failed.\n");
(void)ina219_deinit(&gs_handle);
return 1;
}
/* set calibration */
res = ina219_set_calibration(&gs_handle, calibration);
if (res != 0)
{
ina219_interface_debug_print("ina219: set calibration failed.\n");
(void)ina219_deinit(&gs_handle);
return 1;
}
return 0;
}4.2.2 函数 ina219_basic_deinit()
调用 ina219_deinit() 函数关闭传感器(通过全局变量 gs_handle)
/**
* @brief basic example deinit
* @return status code
* - 0 success
* - 1 deinit failed
* @note none
*/
uint8_t ina219_basic_deinit(void)
{
uint8_t res;
res = ina219_deinit(&gs_handle);
if (res != 0)
{
return 1;
}
return 0;
}4.2.3 函数 ina219_basic_read()
这段代码用于读取 INA219 电流、功率检测芯片数据的函数,通过调用三个子函数分别读取总线电压mV 、电流 mA 和功率 mW 的数据。
/**
* @brief basic example read
* @param[out] *mV pointer to a mV buffer
* @param[out] *mA pointer to a mA buffer
* @param[out] *mW pointer to a mW buffer
* @return status code
* - 0 success
* - 1 read failed
* @note none
*/
uint8_t ina219_basic_read(float *mV, float *mA, float *mW)
{
uint8_t res;
int16_t s_raw;
uint16_t u_raw;
/* read bus voltage */
res = ina219_read_bus_voltage(&gs_handle, (uint16_t *)&u_raw, mV);
if (res != 0)
{
return 1;
}
/* read current */
res = ina219_read_current(&gs_handle, (int16_t *)&s_raw, mA);
if (res != 0)
{
return 1;
}
/* read power */
res = ina219_read_power(&gs_handle, (uint16_t *)&u_raw, mW);
if (res != 0)
{
return 1;
}
return 0;
}4.3 应用
• 这里用电流源给电机供电,并把 INA219 串接到电路中,Nucleo-F411RE 开发板通过I2C接口与 INA219 通信,读取电压、电流、功率值。然后通过OLED屏幕显示读取的三个数值出来,同时串口打印出来。
• 为了读取数据的问题,连续读取10个组数据,去掉最大值、最小值,然后对8组数据求平均值。
• 注意,INA219 的两个拨码开关都拨到左侧,配置其I2C器件地址为 0x40.
4.3.1 流程

4.3.2 函数 main()
函数main() 即流程图的实现。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t ret_val = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_TIM1_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
printf("\r\n");
printf("\t\t EEPW_2025_DIY1_Task3_INA219 \r\n");
printf("\t\t Build: %s %s \r\n", __DATE__, __TIME__);
printf("\r\n");
HAL_TIM_Base_Start_IT(&htim1);
OLED_Init();
OLED_CLS();
screen_00_welcome_english();
HAL_Delay(1000 * 3);
screen_01_welcome_chinese();
HAL_Delay(1000 * 3);
screen_02_diy_static_menu();
HAL_Delay(1000 * 5);
// 注意拨码开关都拨到 GND,此时 INA219 默认是 I2C 地址 0x40
ret_val = ina219_basic_init(INA219_ADDRESS_0, 0.01); // 3629mV, 5.48V, 17.33mW
if (0 != ret_val) {
printf("ina219_basic_init() failed");
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
ret_val = ina219_read_and_filter(&mV, &mA, &mW);
if (0 != ret_val) {
printf("ina219_read_and_filter() failed");
} else {
// printf("%.2f mV \t %.2f mA \t %.2f mW\r\n", mV, mA, mW);
printf("mV_mA_mW: %.2f, %.2f, %.2f \r\n", mV, mA, mW);
// 把电压、电流、功率数值显示在 OLED 屏幕上
screen_03_diy_dynamic_info(mV, mA, mW);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(500);
}
/* USER CODE END 3 */
}4.3.3 函数 screen_02_diy_static_menu()
此函数绘制了功率监测的主界面。
注意此函数中第一次显示的电压、电流和功率值是占位数值,不是从INA219读取的真实数值。
#define CURRENT_ROW 2
#define VOLTAGE_ROW 4
#define POWER___ROW 6
#define CURRENT_COL 40
#define VOLTAGE_COL 40
#define POWER___COL 40
#define CURRENT_LEN 60
#define VOLTAGE_LEN 60
#define POWER___LEN 60
void screen_02_diy_static_menu(void)
{
uint16_t x = 0, y = 0;
OLED_CLS();
// DIY 功率监测与控制
OLED_ShowStr(x, y, "DIY", ASCII_FONT_8X16);
x += 3*8;
OLED_ShowCN(x, y, 10); // 功
x += 16;
OLED_ShowCN(x, y, 11); // 率
x += 16;
OLED_ShowCN(x, y, 12); // 监
x += 16;
OLED_ShowCN(x, y, 13); // 测
#if 0
x += 16;
OLED_ShowCN(x, y, 21); // 与
x += 16;
OLED_ShowCN(x, y, 14); // 系
x += 16;
OLED_ShowCN(x, y, 15); // 统
#endif
x += 16;
OLED_ShowCN(x, y, 16); // 控
x += 16;
OLED_ShowCN(x, y, 17); // 制
// 电流 mA
y = CURRENT_ROW;
x = 0;
OLED_ShowCN(x, y, 18); // 电
x += 16;
OLED_ShowCN(x, y, 20); // 流
x = CURRENT_COL + CURRENT_LEN;
OLED_ShowStr(x, y, "mA", ASCII_FONT_8X16);
#ifdef DEBUG_DISPLAY_VALUE
// 电流 123
OLED_ShowStr(CURRENT_COL, y, "123", ASCII_FONT_8X16);
#endif
// 电压 V
y = VOLTAGE_ROW;
x = 0;
OLED_ShowCN(x, y, 18); // 电
x += 16;
OLED_ShowCN(x, y, 19); // 压
x = VOLTAGE_COL + VOLTAGE_LEN;
OLED_ShowStr(x, y, "mV", ASCII_FONT_8X16);
#ifdef DEBUG_DISPLAY_VALUE
// 电压 5.43
OLED_ShowStr(VOLTAGE_COL, y, "5.43", ASCII_FONT_8X16);
#endif
// 功率 mW
y = POWER___ROW;
x = 0;
OLED_ShowCN(x, y, 10); // 功
x += 16;
OLED_ShowCN(x, y, 11); // 率
x = POWER___COL + POWER___LEN;
OLED_ShowStr(x, y, "mW", ASCII_FONT_8X16);
#ifdef DEBUG_DISPLAY_VALUE
// 功率 123mA * 5.43V = 643.5mW
OLED_ShowStr(POWER___COL, y, "643.5", ASCII_FONT_8X16);
#endif
}4.3.4 函数 ina219_read_filter()
此函数连续读取10次数值,然后去掉最大值、最小值,对8组数值求平均。
static uint8_t ina219_read_and_filter(float* vol, float* cur, float* power)
{
uint8_t ret = 0;
float mVol = 0.0f, mCur = 0.0f, mPower = 0.0f;
float sumVol = 0.0f, sumCur = 0.0f, sumPower = 0.0f;
float maxVol = 0.0f, maxCur = 0.0f, maxPower = 0.0f;
float minVol = 0.0f, minCur = 0.0f, minPower = 0.0f;
for (int i = 0; i < 10; i++) {
ret += ina219_basic_read(&mVol, &mCur, &mPower);
sumVol += mVol;
sumCur += mCur;
sumPower += mPower;
if (mVol > maxVol) {
maxVol = mVol;
}
if (mCur > maxCur) {
maxCur = mCur;
}
if (mPower > maxPower) {
maxPower = mPower;
}
if (mVol < minVol) {
minVol = mVol;
}
if (mCur < minCur) {
minCur = mCur;
}
if (mPower < minPower) {
minPower = mPower;
}
HAL_Delay(5);
}
if (ret != 0) {
return 1;
}
// remove max and min
sumVol -= (maxVol + minVol);
sumCur -= (maxCur + minCur);
sumPower -= (maxPower + minPower);
*vol = sumVol / 8.0f;
*cur = sumCur / 8.0f;
*power = sumPower / 8.0f;
return 0;
}4.3.5 函数 screen_03_diy_dynamic_info()
此函数把从INA219读取的电压、电流和功率数值,更新在OLED屏幕上,注意只更新屏幕上指定一个数值区域,并没有全屏刷新。
/**
* @brief 在屏幕指定的三个位置分别显示电压、电流、功率
* 所有数据均只显示小数点后两位
*
* @param vol 电压,mV
* @param cur 电流,mA
* @param power 功率,mW
*/
void screen_03_diy_dynamic_info(float vol, float cur, float power)
{
char str_vol[10] = { 0 };
char str_cur[10] = { 0 };
char str_power[10] = { 0 };
sprintf(str_vol, "%.2f", vol);
sprintf(str_cur, "%.2f", cur);
sprintf(str_power, "%.2f", power);
OLED_ShowStr(VOLTAGE_COL, VOLTAGE_ROW, (uint8_t *)str_vol, ASCII_FONT_8X16);
OLED_ShowStr(CURRENT_COL, CURRENT_ROW, (uint8_t *)str_cur, ASCII_FONT_8X16);
OLED_ShowStr(POWER___COL, POWER___ROW, (uint8_t *)str_power, ASCII_FONT_8X16);
}https://www.bilibili.com/video/BV1m9KPzWEpj/?vd_source=8f2bbf56b70c541bec2ea0b9f102ebee

我要赚赏金
