一、介绍

  INA226 是德州仪器(Texas Instruments, TI)推出的一款高精度分流电压和总线电压监测芯片,可通过 I²C 接口与主控 MCU 通信。该器件可以实时测量分流电阻上的压降(用于计算电流)以及系统电源总线的电压,并通过内部乘法器计算出功率值。其输出数据可以是电压、电流或功率的直接数字值,无需额外的外部运算。

  主包最近需要使用该模块帮客户实现一个功能,就是使用INA226模块实时监测系统的总电压、电流和功率,但是发现网上使用STM32F103C8T6单片机控制INA226读取电压电流的资料比较少,刚好前几天浅浅研究了一下这个模块,下面记录一下该模块的使用方法,由于主包是第一次写该类型的博客,如果写的不好还望体谅,后续将持续更新各个模块的使用方法和代码,本博客用到的工程demo无偿开源,需要的可以查看下主页获取方式。

二、传感器原理图

在这里插入图片描述
以下是INA226传感器各个模块的引脚说明:

引脚功能说明
IN+电流流入(接外部电源正极)
IN+电流流出(接负载一侧的正极)
VS3v3
SCL时钟线
SDA数据线
A0IIC地址设置引脚
A1IIC地址设置引脚
Alert报警输出引脚(这里不接)
VBUS总线电压输入引脚

三、实物连接说明

在这里插入图片描述
  我这里是将IN+连接到外部电池电源的输入端(5V_IN),IN-连接到负载端的供电端(+5V),SCL连接的是单片机的PB0引脚,SDA连接的是单片机的PB1引脚

四、代码

main.c文件

  这里主包是在Timer.c中实现了一个简单的定时器轮询任务框架,大家要是觉得复杂的话可以直接把if(time_flag.time_flag_500ms == 1) 中的代码内容放在while里加个延时就行,效果是一样的,oled的驱动代码就不给出了,随便网上找一个就行。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "main.h"
#include "usart.h"
#include "bsp_iic.h"
#include "bsp_ina226.h"

int main(void)
{
	Timer_Init();         //周期10ms
	OLED_Init();          //显示屏初始化
	OLED_Clear();
	IIC_Init(); 			//初始化I2C
	mx_ina226_init();		//初始化INA226	
	while (1)
	{	
		if(time_flag.time_flag_500ms == 1)    //500ms执行一次
		{
			//电压、电流、功率
			get_power();
			uint16_t V_int = (uint16_t)(ina226_data.voltageVal / 1000);
			uint16_t V_dec = (uint16_t)((ina226_data.voltageVal / 1000 - V_int) * 1000);
			uint16_t I_int = (uint16_t)(ina226_data.Shunt_Current / 1000);
			uint16_t I_dec = (uint16_t)((ina226_data.Shunt_Current / 1000 - I_int) * 1000);
			uint16_t W_int = (uint16_t)ina226_data.Power_Val;
			uint16_t W_dec = (uint16_t)((ina226_data.Power_Val - W_int) * 1000);
			vbus_mV = V_int + (float)V_dec / 1000;
			current_mA = I_int + (float)I_dec / 10000;
			power_mW = W_int + (float)W_dec / 10000;
			time_flag.time_flag_500ms  = 0;
		}
	}
}

bsp_iic.c文件

#include "bsp_iic.h"
#include "delay.h"

/******************************************************************************
      文件说明:iic的驱动文件
      SCL引脚->PB1
		SDA引脚->PB0
      作者:坤博单片机设计
			该代码仅供学习使用
******************************************************************************/

void IIC_Init(void)
{					     
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );	//使能GPIOB时钟
	   
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SCL|GPIO_Pin_SDA; // 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_SetBits(GPIOB,GPIO_Pin_SCL|GPIO_Pin_SDA); 
}

void SDA_Mode(int Mode){

	if(Mode==1){
		
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );	//使能GPIOB时钟
	   
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SDA; 				
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;   		// 上拉输入/(开漏输出)
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	}else if(Mode==0){
		
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );	//使能GPIOA时钟
	   
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SDA; 				
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   	// 推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	}	
}

void IIC_Start(void)
{
	SDA_Mode(0);     //sda线输出
	SDA_Set();	  	  
	SCL_Set();
	delay_us(4);
 	SDA_Clr();//START:when CLK is high,DATA change form high to low 
	delay_us(4);
	SCL_Clr();//钳住I2C总线,准备发送或接收数据 
}

void IIC_Stop(void)
{
	SDA_Mode(0);//sda线输出
	SCL_Clr();
	SDA_Clr();//STOP:when CLK is high DATA change form low to high
 	delay_us(4);
	SCL_Set(); 
	SDA_Set();//发送I2C总线结束信号
	delay_us(4);							   	
}

u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	SDA_Mode(1);      //SDA设置为输入  
	SDA_Set();delay_us(1);	   
	SCL_Set();delay_us(1);	 
	while(SDA_READ())
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	SCL_Clr();//时钟输出0 	   
	return 0;  
}

void IIC_Ack(void)
{
	SCL_Clr();
	SDA_Mode(0);
	SDA_Clr();
	delay_us(2);
	SCL_Set();
	delay_us(2);
	SCL_Clr();
}
    
void IIC_NAck(void)
{
	SCL_Clr();
	SDA_Mode(0);
	SDA_Set();
	delay_us(2);
	SCL_Set();
	delay_us(2);
	SCL_Clr();
}
 
void IIC_Send_Byte(u8 txd)
{                        
   u8 t;   
	SDA_Mode(0); 	    
	SCL_Clr();//拉低时钟开始数据传输
	for(t=0;t<8;t++)
	{              
	//IIC_SDA=(txd&0x80)>>7;
		if((txd&0x80)>>7)
			SDA_Set();
		else
			SDA_Clr();
			txd<<=1; 	  
			delay_us(2);   //对TEA5767这三个延时都是必须的
			SCL_Set();
			delay_us(2); 
			SCL_Clr();	
			delay_us(2);
    }	 
}

u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	SDA_Mode(1);//SDA设置为输入
	for(i=0;i<8;i++ )
	{
		SCL_Clr(); 
		delay_us(2);
		SCL_Set();
    receive<<=1;
    if(SDA_READ())receive++;   
			delay_us(1); 
	}					 
    if (!ack)
        IIC_NAck();//发送nACK
    else
        IIC_Ack(); //发送ACK   
    return receive;
}

bsp_iic.h文件

#ifndef _BSP_IIC_H
#define _BSP_IIC_H
#include "stm32f10x.h"                  // Device header

#define GPIO_Pin_SCL    GPIO_Pin_1
#define GPIO_Pin_SDA    GPIO_Pin_0

#define SCL_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_SCL)//SCL=PB1
#define SCL_Set() GPIO_SetBits(GPIOB,GPIO_Pin_SCL)
#define SDA_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_SDA)//SDA=PB0
#define SDA_Set() GPIO_SetBits(GPIOB,GPIO_Pin_SDA)
#define SDA_READ()  GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_SDA) // 读取 SDA 引脚电平


void IIC_Init(void);
void SDA_Mode(int Mode);		 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号

void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);	  
#endif

bsp_ina226.c文件

#include "bsp_ina226.h"

INA226 ina226_data;

/******************************************************************************
      函数说明:INA226传感器初始化
      入口数据:无
      返回值:  无
      初始化流程:
        1. 发送软件复位命令(0x8000)
        2. 配置工作模式(MODE_INA226)
        3. 设置校准值(CAL_REG)
        4. 读取芯片ID验证通信
      配置参数:
        - 电流检测范围:±4096 LSB
        - 电压检测精度:1.25mV/bit
******************************************************************************/
void mx_ina226_init(void)
{
    INA226_SendData(INA226_ADDR1,CFG_REG,0x8000);	//重新启动
    INA226_SendData(INA226_ADDR1,CFG_REG,MODE_INA226);
    INA226_SendData(INA226_ADDR1,CAL_REG,CAL);		//设置分辨率
    INA226_Get_ID(INA226_ADDR1);					//获取ina226的id
}

/******************************************************************************
      函数说明:向INA226寄存器写入数据
      入口数据:addr - 设备地址(7位)
                reg - 寄存器地址
                data - 16位待写入数据
      返回值:  无
      数据格式:
        1. 先发送高字节再发送低字节
        2. 每次写入需等待ACK应答
      协议层操作:
        1. 发送起始信号
        2. 发送设备地址+写标志
        3. 发送寄存器地址
        4. 发送16位数据
        5. 发送停止信号
******************************************************************************/
void INA226_SendData(uint8_t addr,uint8_t reg,uint16_t data)
{
  	u8 temp=0;
	IIC_Start();

	IIC_Send_Byte(addr);
	IIC_Wait_Ack();

	IIC_Send_Byte(reg);
	IIC_Wait_Ack();
	
	temp = (u8)(data>>8);
	IIC_Send_Byte(temp);
	IIC_Wait_Ack();

	temp = (u8)(data&0x00FF);
	IIC_Send_Byte(temp);
	IIC_Wait_Ack();
	
	IIC_Stop();
}

/******************************************************************************
      函数说明:设置寄存器访问指针
      入口数据:addr - 设备地址(7位)
                reg - 寄存器地址
      返回值:  无
      操作流程:
        1. 发送起始信号
        2. 发送设备地址+写标志
        3. 发送目标寄存器地址
        4. 发送停止信号
      应用场景:
        在连续读取寄存器前必须调用该函数
******************************************************************************/
void INA226_SetRegPointer(uint8_t addr,uint8_t reg)
{
	IIC_Start();

	IIC_Send_Byte(addr);
	IIC_Wait_Ack();

	IIC_Send_Byte(reg);
	IIC_Wait_Ack();

	IIC_Stop();
}

/******************************************************************************
      函数说明:从INA226读取16位数据
      入口数据:addr - 设备地址(7位)
      返回值:  16位寄存器数据
      读取流程:
        1. 发送起始信号+地址+读标志
        2. 先读取高位字节
        3. 再读取低位字节
      应答控制:
        读高位时发送ACK,读低位时发送NACK
******************************************************************************/
uint16_t INA226_ReadData(uint8_t addr)
{
	u16 temp=0;
	IIC_Start();

	IIC_Send_Byte(addr+1);
	IIC_Wait_Ack();
	
	temp = IIC_Read_Byte(1);
	temp<<=8;	
	temp |= IIC_Read_Byte(0);
	
	IIC_Stop();
	return temp;
}

/******************************************************************************
      函数说明:获取INA226芯片ID
      入口数据:addr - 设备地址(7位)
      返回值:  无(结果存储在全局变量中)
      操作步骤:
        1. 设置寄存器指针到ID寄存器
        2. 读取32位ID数据
      数据存储:
        结果保存在ina226_data.ina226_id中
******************************************************************************/
void INA226_Get_ID(uint8_t addr)
{
    uint32_t temp=0;
    INA226_SetRegPointer(addr,INA226_GET_ADDR);
    temp = INA226_ReadData(addr);
    ina226_data.ina226_id = temp;
}

/******************************************************************************
      函数说明:获取校准寄存器值
      入口数据:addr - 设备地址(7位)
      返回值:  16位校准值
      应用场景:
        用于验证校准参数设置是否生效
******************************************************************************/
uint16_t INA226_GET_CAL_REG(uint8_t addr)
{
    uint32_t temp=0;
    INA226_SetRegPointer(addr,CAL_REG);
    temp = INA226_ReadData(addr);
    return (uint16_t)temp;
}

/******************************************************************************
      函数说明:获取分流电压值(2.5uV/bit)
      入口数据:addr - 设备地址(7位)
      返回值:  16位原始数据(有符号)
      数据说明:
        1 LSB = 2.5uV(0.0025mV)
        范围:±81.9175mV
      注意事项:
        返回值可能为负数需注意符号扩展
******************************************************************************/
int16_t INA226_GetShuntVoltage(uint8_t addr)
{
    int16_t temp = 0;
    INA226_SetRegPointer(addr,SV_REG);
    temp = INA226_ReadData(addr);
    return (int16_t)temp;
}

/******************************************************************************
      函数说明:获取总线电压值(1.25mV/bit)
      入口数据:addr - 设备地址(7位)
      返回值:  16位原始数据(无符号)
      数据说明:
        1 LSB = 1.25mV
        范围:0-40.96V
******************************************************************************/
uint16_t INA226_GetVoltage(uint8_t addr)
{
    uint32_t temp = 0;
    INA226_SetRegPointer(addr,BV_REG);
    temp = INA226_ReadData(addr);
    return (uint16_t)temp;
}

/******************************************************************************
      函数说明:获取功率测量值(25mW/bit)
      入口数据:addr - 设备地址(7位)
      返回值:  16位原始数据(无符号)
      数据说明:
        1 LSB = 25mW
        范围:0-10.24W
******************************************************************************/
uint16_t INA226_Get_Power(uint8_t addr)
{
    int16_t temp=0;
    INA226_SetRegPointer(addr,PWR_REG);
    temp = INA226_ReadData(addr);
    return (uint16_t)temp;
}

/******************************************************************************
      函数说明:获取分流电流值(1mA/bit)
      入口数据:addr - 设备地址(7位)
      返回值:  16位原始数据(有符号)
      数据说明:
        1 LSB = 1mA
        范围:±32.768A
******************************************************************************/
int16_t INA226_GetShunt_Current(uint8_t addr)
{
    int16_t temp=0;
    INA226_SetRegPointer(addr,CUR_REG);
    temp = INA226_ReadData(addr);
//	if(temp&0x8000)	temp = ~(temp - 1);
    return temp;
}

/******************************************************************************
      函数说明:获取分流电压(带单位转换)
      入口数据:Voltage - 指向存储结果的浮点型指针(单位:uV)
      返回值:  无
      数据转换:
        原始值 × 2.5uV/bit
      注意事项:
        该函数包含硬件相关的校准系数
******************************************************************************/
void Get_Shunt_voltage(float *Voltage)//uV
{
    *Voltage = (INA226_GetShuntVoltage(INA226_ADDR1)*INA226_VAL_LSB);//如需矫正电流分流参数请将这里改为2.5
}

/******************************************************************************
      函数说明:获取总线电压(带单位转换)
      入口数据:Voltage - 指向存储结果的浮点型指针(单位:mV)
      返回值:  无
      数据转换:
        原始值 × 1.25mV/bit
******************************************************************************/
void GetVoltage(float *Voltage)//mV
{
    *Voltage = INA226_GetVoltage(INA226_ADDR1)*Voltage_LSB;
}

/******************************************************************************
      函数说明:获取功率值(带单位转换)
      入口数据:Power - 指向存储结果的浮点型指针(单位:mW)
      返回值:  无
      数据转换:
        原始值 × 25mW/bit
******************************************************************************/
void Get_Power(float *Power)//mW
{
    *Power = (INA226_Get_Power(INA226_ADDR1)*POWER_LSB);
}

/******************************************************************************
      函数说明:获取分流电流(带单位转换)
      入口数据:Current - 指向存储结果的浮点型指针(单位:mA)
      返回值:  无
      数据转换:
        原始值 × 1mA/bit
******************************************************************************/
void Get_Shunt_Current(float *Current)//mA
{
    *Current = (INA226_GetShunt_Current(INA226_ADDR1)* CURRENT_LSB);
}

/******************************************************************************
      函数说明:综合功率计算
      入口数据:无(结果存储在全局变量中)
      返回值:  无
      计算公式:
        功率(W) = 电压(V) × 电流(A)
        其中:电压(V) = mV值 × 0.001
              电流(A) = mA值 × 0.001
      数据来源:
        - 电压值来自INA226_GetVoltage()
        - 电流值来自INA226_GetShunt_Current()
******************************************************************************/
void get_power()//W
{
    GetVoltage(&ina226_data.voltageVal);			//mV
    Get_Shunt_voltage(&ina226_data.Shunt_voltage);	//uV
    Get_Shunt_Current(&ina226_data.Shunt_Current);	//mA
    Get_Power(&ina226_data.Power);					//mW
    ina226_data.Power_Val = ina226_data.voltageVal*0.001f * ina226_data.Shunt_Current*0.001f; //mV*mA
}

bsp_ina226.c文件

#ifndef __INA226_H
#define __INA226_H

#include "bsp_iic.h"


#define MODE_INA226 0X4327

#define 	CFG_REG	 		0X00		//
#define 	SV_REG 			0X01		//分流电压
#define 	BV_REG 			0X02		//总线电压
#define 	PWR_REG 		0X03		//电源功率
#define 	CUR_REG 		0X04		//电流
#define 	CAL_REG 		0X05		//校准,设定满量程范围以及电流和功率测数的 
#define 	ONFF_REG 		0X06		//屏蔽 使能 警报配置和转换准备就绪
#define 	AL_REG 			0X07		//包含与所选警报功能相比较的限定值
#define 	INA226_GET_ADDR 0XFF		//包含唯一的芯片标识号
#define   	INA226_ADDR1	0x80    //ina226地址
#define     INA226_GETALADDR	0X14

//定义配置数据
#define 	INA226_VAL_LSB	2.5f	//分流电压 LSB 2.5uV
#define     Voltage_LSB		1.25f   //总线电压 LSB 1.25mV
#define     CURRENT_LSB 	1.0f 	//电流LSB 1mA
#define     POWER_LSB       25*CURRENT_LSB
#define     CAL             200 //1020     //0.00512*1000/(Current_LSB*R_SHUNT) = 512  //电流偏大改小

typedef struct
{
    float voltageVal;			//mV
    float Shunt_voltage;		//uV
    float Shunt_Current;		//mA
    float Power_Val;			//功率
    float Power;				//功率mW
    uint32_t   ina226_id;
} INA226;

void INA226_SetRegPointer(uint8_t addr,uint8_t reg);
void INA226_SendData(uint8_t addr,uint8_t reg,uint16_t data);
uint16_t INA226_ReadData(uint8_t addr);

void     INA226_Get_ID(uint8_t addr);			//获取 id
uint16_t INA226_GET_CAL_REG(uint8_t addr);		//获取校准值
uint16_t INA226_GetVoltage( uint8_t addr);		//获取总线电压装载值
int16_t INA226_GetShunt_Current(uint8_t addr);	//获取分流电流装载值
int16_t INA226_GetShuntVoltage(uint8_t addr);	//分流电压装载值
uint16_t INA226_Get_Power(uint8_t addr);		//获取功率装载值,不使用

void mx_ina226_init(void);
void GetVoltage(float *Voltage);
void Get_Shunt_voltage(float *Current);
void Get_Shunt_Current(float *Current);
void get_power(void);						////获取功率= 总线电压 * 电流

uint8_t	INA226_AlertAddr(void);
void Get_Power(float *Power);

extern INA226 ina226_data;
#endif


四、实验现象

  将代码烧录进去后,给单片机供电,oled屏幕上可以显示当前电路的总电压、电流和功率,文章内容仅供参考,实际接线和代码修改根据个人情况而定,完整工程demo查看主页

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐