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