大家好我是 杰哥编程
最近在做项目时需要stm32f103监测系统用电功率电压电流,硬件使用到INA226模块,软件采用HAL库+CubeMX方式配置32的I2C通信,特来分享一下。其中参考了2篇别的博客 根据手上资料进行汇总整理。
本次项目使用1个stm32f103c8t6单片机、一个5VDC-DC升压稳压电源模块板、一个18650锂电池、一个INA226(IIC接口)电压模块
I2C通讯协议(Inter-Integrated Circuit)
I2C最少只需要两根线,一条双向串行数据线(SDA),一条串行时钟线(SCL)
。数据线即用来表示数据,时钟线用于数据收发同步。
I2C的数据传输速率位于串口和SPI之间,大部分I2C设备支持100KHz和400KHz模式。实际具有三种传输模式
:标准模式传输速率为100kbit/s,快速模式为400kbit/s,高速模下可达3.4Mbit/s,但目前大多I2C设备尚不支持高速模式。
使用I2C传输数据会有一些额外消耗:每发送8bits数据,就需要额外1bit的元数据(ACK或NACK)。
I2C支持双向数据交换,由于仅有一根数据线,故通信是半双工
的。硬件复杂度也位于串口和SPI之间,而软件实现可以相当简单。
和异步串口类似,但可以支持多个slave设备。一个I2C理论上最多可挂载127个设备,但除去保留地址,最多可挂载112个设备。同时支持多个通讯主机及多个通讯从机
。允许有多个master并且每个master都可以与所有的slaves通信(master之间不可通过I2C通信,并且每个master只能轮流使用I2C总线)。
多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
电气特性
:总线通过上拉电阻接到电源。当I2C设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
SDA没事的时候是高电平 SCL是时钟上下上下的跳
起始信号:在SCL为高电平的时候,SDA由高电平向低电平跳变。
结束信号:SCL为高电平的时候,SDA由低电平向高电平跳变。
应答信号?
I2C的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。
作为数据接收端时,当设备(无论主从机)接收到I2C传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。
数据位
发送有效?
在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。
进行数据传送时,在SCL呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。只有在SCL为低电平期间,才允许SDA上的电平改变状态。逻辑0的电平为低电压,而逻辑1则为高电平。
没找到图 自己画 哭!!!!55555
红线为c时钟线高电平时能实际有效采集到数据位 d数据线为高则采集数据位为1反之为0,
粉框为c时钟线低电平时,d数据线允许切换高低电平(也可以不切换),以供下一次时钟高电平时采集数据位
简单来说我要开始水了!!!
就将通俗易懂的 能快速理解的讲完 进入下一篇章
(以下只讲基础的简单的细致的后面会复制粘贴出来 愿意深入了解的可以逐字观看)
1 i2c传输帧首字节有个control byte 用来寻址决定数据传输方向 不管
2既然是多从机的传输协议就要有指定传输目标的功能 也就是地址 地址一般为7位 然后再加一位读写位 有10位的地址 也不难 分成两帧
3指定从机通信后 要实际数据交互 分为两种写时序和读时序
4写时序 写到哪 写到某个8bit的储存单元地址 写什么 某些个8位数据 能连续写的叫页写
5读时序 读哪 读某8bit的单元地址 主机发个control byte虚晃一下 为啥?我也不知道 就这么规定 你就照办 从机应答后发主机想读的数据 想连续读 你主机得给人家从机一个ack亲亲 不然以为你高冷 不需要连续读呢
跳过
看后面的stm32控制INA226篇章INA226是具有I2C™或SMBUS兼容接口的电流分流器和功率监控器。该设备同时监视并联电压降和总线电源电压。可编程的校准值,转换时间和平均值与内部乘法器结合使用,可以直接读取以安培为单位的电流和以瓦特为单位的功率。
INA226感应共模总线电压上的电流,该电压可在0 V至36 V之间变化,与电源电压无关。该器件采用2.7V至5.5V单电源供电,典型功耗为330 µA。该器件的额定工作温度范围为–40°C至125 \ xC2°C,并且在I 2 C兼容接口上具有多达16个可编程地址。
接线方式为:INPUT接电源正极,GND接电源负极,OUTPUT接用电器件(我没接)的电源正极输入口
首先,为了使用I2C读取模块数据,需要先通过传感器数据手册来确定从机地址和所需寄存器地址。
tnnd 以前没深入了解过i2c 所以这边踩坑了
这是个7位的地址 stm32用i2c的时候地址填写的是1个byte,
然后我们了解到i2c指定从机通信的时候7位地址后面还有1个读写位
所以0x40左移一位等于0x80 (0x40<<1 == 0x80)
所以stm32用i2c通信的时候,从机地址要写0x80
#define INA226_ADDRESS 0x80 /*INA226的地址*/
0x0
用于配置芯片的采样速度,采样模式,采样滤波周期等,默认值是0x4127
0x5
校准寄存器 看手册↓ 直接搜索找到计算方式
理解不了的可以看看下面示例代码中arduino是如何控制INA226模块的↓↓↓↓↓↓
// Calibrate INA226. Rshunt = 0.01 ohm, Max excepted current = 4A
//我买的ina226模块采样电阻型号r010是0.01欧姆 假如我们的电路预期最大电流是4安培
//计算公式:
//Current_LSB = 预期最大电流4A / 2^15 = 0.0001220703125
//CAL = 0.00512/(Current_LSBR)=0.00512/0.0001220703125=42=0x2A
//当然如果你想让单片机自己算直接移植下面的函数到你的工程里就行
bool INA226::calibrate(float rShuntValue, float iMaxExpected)
{
uint16_t calibrationValue;
rShunt = rShuntValue;
float iMaxPossible, minimumLSB;
iMaxPossible = vShuntMax / rShunt;
minimumLSB = iMaxExpected / 32767;
currentLSB = (uint16_t)(minimumLSB * 100000000);
currentLSB /= 100000000;
currentLSB /= 0.0001;
currentLSB = ceil(currentLSB);
currentLSB *= 0.0001;
powerLSB = currentLSB * 25;
calibrationValue = (uint16_t)((0.00512) / (currentLSB * rShunt));
writeRegister16(INA226_REG_CALIBRATION, calibrationValue);
return true;
}
arduino简直是神器 集成了大量的模块的驱动逻辑编写 遇到常见or相似模块 直接去arduino拿来用
0x2 0x3 0x4
测电压 功率 电流
这个直接i2c读取寄存器就可以了
这里采用的是常用的STM32F103C8T6芯片,选择外部高频时钟,SWD调试.
生成代码GENERATE CODE
arduino用cpp语法实现了完整的逻辑
arduino的INA226链接:https://www.jarzebski.pl/arduino/czujniki-i-sensory/cyfrowy-czujnik-pradu-mocy-ina226.html
可以参考arduino也可以看我的
h头文件
#ifndef INC_IIC_INA226_H_
#define INC_IIC_INA226_H_
void INA226_init(void);
//仅使用了获取电压电流功率3个功能
float INA226_GetBusV(void);
float INA226_GetCurrent(void);
float INA226_GetPower(void);
#endif /* INC_IIC_INA226_H_ */
c驱动源文件
#include "i2c.h"
uint8_t INA226_SetConfig(uint16_t ConfigWord);
uint8_t INA226_SetCalibrationReg(uint16_t ConfigWord);
uint16_t INA226_GetBusVReg(void);
uint16_t INA226_GetPowerReg(void);
uint16_t INA226_GetCurrentReg(void);
#define INA226_COM_PORT hi2c1 /*通讯使用的IIC接口*/
#define INA226_ADDRESS 0x80 /*INA226的地址*/
#define INA226_I2C_TIMEOUT 10 /*IIC通讯超时*/
#define INA226_CALIB_VAL 1024
#define INA226_CURRENTLSB 0.5F // mA/bit
#define INA226_CURRENTLSB_INV 1/INA226_CURRENTLSB // bit/mA
#define INA226_POWERLSB_INV 1/(INA226_CURRENTLSB*25) // bit/mW
#define INA226_CONFIG 0x00 // Configuration Register (R/W)初始值4127
#define INA226_SHUNTV 0x01 // Shunt Voltage (R)初始值0,分流电压测量值
#define INA226_BUSV 0x02 // Bus Voltage (R)初始值0,总线电压测量值
#define INA226_POWER 0x03 // Power (R)初始值0,输出功率测量值
#define INA226_CURRENT 0x04 // Current (R)初始值0,分流电阻电流计算值
#define INA226_CALIB 0x05 // Calibration (R/W),设置全量程和电流LSB
#define INA226_MASK 0x06 // Mask/Enable (R/W),报警设置和转换准备标志
#define INA226_ALERTL 0x07 // Alert Limit (R/W),报警阈值
#define INA226_MANUF_ID 0xFE // Manufacturer ID (R),0x5449
#define INA226_DIE_ID 0xFF // Die ID (R),0x2260
#define INA226_MODE_POWER_DOWN (0<<0) // Power-Down
#define INA226_MODE_TRIG_SHUNT_VOLTAGE (1<<0) // Shunt Voltage, Triggered
#define INA226_MODE_TRIG_BUS_VOLTAGE (2<<0) // Bus Voltage, Triggered
#define INA226_MODE_TRIG_SHUNT_AND_BUS (3<<0) // Shunt and Bus, Triggered
#define INA226_MODE_POWER_DOWN2 (4<<0) // Power-Down
#define INA226_MODE_CONT_SHUNT_VOLTAGE (5<<0) // Shunt Voltage, Continuous
#define INA226_MODE_CONT_BUS_VOLTAGE (6<<0) // Bus Voltage, Continuous
#define INA226_MODE_CONT_SHUNT_AND_BUS (7<<0) // Shunt and Bus, Continuous
// Shunt Voltage Conversion Time
#define INA226_VSH_140uS (0<<3)
#define INA226_VSH_204uS (1<<3)
#define INA226_VSH_332uS (2<<3)
#define INA226_VSH_588uS (3<<3)
#define INA226_VSH_1100uS (4<<3)
#define INA226_VSH_2116uS (5<<3)
#define INA226_VSH_4156uS (6<<3)
#define INA226_VSH_8244uS (7<<3)
// Bus Voltage Conversion Time (VBUS CT Bit Settings[6-8])
#define INA226_VBUS_140uS (0<<6)
#define INA226_VBUS_204uS (1<<6)
#define INA226_VBUS_332uS (2<<6)
#define INA226_VBUS_588uS (3<<6)
#define INA226_VBUS_1100uS (4<<6)
#define INA226_VBUS_2116uS (5<<6)
#define INA226_VBUS_4156uS (6<<6)
#define INA226_VBUS_8244uS (7<<6)
// Averaging Mode (AVG Bit Settings[9-11])
#define INA226_AVG_1 (0<<9)
#define INA226_AVG_4 (1<<9)
#define INA226_AVG_16 (2<<9)
#define INA226_AVG_64 (3<<9)
#define INA226_AVG_128 (4<<9)
#define INA226_AVG_256 (5<<9)
#define INA226_AVG_512 (6<<9)
#define INA226_AVG_1024 (7<<9)
// Reset Bit (RST bit [15])
#define INA226_RESET_ACTIVE (1<<15)
#define INA226_RESET_INACTIVE (0<<15)
// Mask/Enable Register
#define INA226_MER_SOL (1<<15) // Shunt Voltage Over-Voltage
#define INA226_MER_SUL (1<<14) // Shunt Voltage Under-Voltage
#define INA226_MER_BOL (1<<13) // Bus Voltagee Over-Voltage
#define INA226_MER_BUL (1<<12) // Bus Voltage Under-Voltage
#define INA226_MER_POL (1<<11) // Power Over-Limit
#define INA226_MER_CNVR (1<<10) // Conversion Ready
#define INA226_MER_AFF (1<<4) // Alert Function Flag
#define INA226_MER_CVRF (1<<3) // Conversion Ready Flag
#define INA226_MER_OVF (1<<2) // Math Overflow Flag
#define INA226_MER_APOL (1<<1) // Alert Polarity Bit
#define INA226_MER_LEN (1<<0) // Alert Latch Enable
#define INA226_MANUF_ID_DEFAULT 0x5449
#define INA226_DIE_ID_DEFAULT 0x2260
void INA226_init(void)
{
/*
* 设置转换时间8.244ms,求平均值次数16,设置模式为分流和总线连续模式
* 总数据转换时间 = 8.244*16 = 131.9ms
*/
INA226_SetConfig(0x45FF);
/*
* 分流电阻最大电压 = 32768 * 0.0000025V = 0.08192V
* 设置分流电压转电流转换参数:电阻0.01R,分辨率0.2mA
* 公式1
* Current_LSB = 预期最大电流 / 2^15
* Current_LSB = 5 / 32768 = 0.000152A ,选0.2ma
* 公式2
* CAL = 0.00512/(Current_LSB*R)
* CAL = 0.00512/(0.0002*0.01)=2560 = 0x0a00
*/
INA226_SetCalibrationReg(0x0a00);
}
/*
**************************************************
* 说明:读取BUS电压,并转换为浮点数据
**************************************************
*/
float INA226_GetBusV(void)
{
uint16_t regData;
float fVoltage;
regData = INA226_GetBusVReg();
fVoltage = regData * 0.00125f;/*电压的LSB = 1.25mV*/
return fVoltage;
}
/*
**************************************************
* 说明:读取电流,并转换为浮点数据
**************************************************
*/
float INA226_GetCurrent()
{
uint16_t regData;
float fCurrent;
regData = INA226_GetCurrentReg();
if(regData >= 0x8000) regData = 0;
fCurrent = regData * 0.0002f;/*电流的LSB = 0.2mA,由用户配置*/
return fCurrent;
}
/*
**************************************************
* 说明:读取功率,并转换为浮点数据
**************************************************
*/
float INA226_GetPower()
{
uint16_t regData;
float fPower;
regData = INA226_GetPowerReg();
fPower = regData * 0.005f;/*功率的LSB = 电流的LSB*25*/
return fPower;
}
uint8_t INA226_SetConfig(uint16_t ConfigWord)
{
uint8_t SentTable[3];
SentTable[0] = INA226_CONFIG;
SentTable[1] = (ConfigWord & 0xFF00) >> 8;
SentTable[2] = (ConfigWord & 0x00FF);
return HAL_I2C_Master_Transmit(&INA226_COM_PORT, INA226_ADDRESS, SentTable, 3, INA226_I2C_TIMEOUT);
}
// uint16_t INA226_GetConfig()
// {
// uint8_t SentTable[1] = {INA226_CONFIG};
// uint8_t ReceivedTable[2];
// HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
// if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
// else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
// }
// uint16_t INA226_GetShuntV()
// {
// uint8_t SentTable[1] = {INA226_SHUNTV};
// uint8_t ReceivedTable[2];
// HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
// if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
// else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
// }
uint16_t INA226_GetBusVReg()
{
uint8_t SentTable[1] = {INA226_BUSV};
uint8_t ReceivedTable[2];
HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
}
uint8_t INA226_SetCalibrationReg(uint16_t ConfigWord)
{
uint8_t SentTable[3];
SentTable[0] = INA226_CALIB;
SentTable[1] = (ConfigWord & 0xFF00) >> 8;
SentTable[2] = (ConfigWord & 0x00FF);
return HAL_I2C_Master_Transmit(&INA226_COM_PORT, INA226_ADDRESS, SentTable, 3, INA226_I2C_TIMEOUT);
}
// uint16_t INA226_GetCalibrationReg()
// {
// uint8_t SentTable[1] = {INA226_CALIB};
// uint8_t ReceivedTable[2];
// HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
// if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
// else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
// }
uint16_t INA226_GetPowerReg()
{
uint8_t SentTable[1] = {INA226_POWER};
uint8_t ReceivedTable[2];
HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
}
uint16_t INA226_GetCurrentReg()
{
uint8_t SentTable[1] = {INA226_CURRENT};
uint8_t ReceivedTable[2];
HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
}
// uint16_t INA226_GetManufID()
// {
// uint8_t SentTable[1] = {INA226_MANUF_ID};
// uint8_t ReceivedTable[2];
// HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
// if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
// else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
// }
// uint16_t INA226_GetDieID()
// {
// uint8_t SentTable[1] = {INA226_DIE_ID};
// uint8_t ReceivedTable[2];
// HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
// if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
// else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
// }
// uint8_t INA226_SetMaskEnable(uint16_t ConfigWord)
// {
// uint8_t SentTable[3];
// SentTable[0] = INA226_MASK;
// SentTable[1] = (ConfigWord & 0xFF00) >> 8;
// SentTable[2] = (ConfigWord & 0x00FF);
// return HAL_I2C_Master_Transmit(&INA226_COM_PORT, INA226_ADDRESS, SentTable, 3, INA226_I2C_TIMEOUT);
// }
// uint16_t INA226_GetMaskEnable()
// {
// uint8_t SentTable[1] = {INA226_MASK};
// uint8_t ReceivedTable[2];
// HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
// if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
// else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
// }
// uint8_t INA226_SetAlertLimit(uint16_t ConfigWord)
// {
// uint8_t SentTable[3];
// SentTable[0] = INA226_ALERTL;
// SentTable[1] = (ConfigWord & 0xFF00) >> 8;
// SentTable[2] = (ConfigWord & 0x00FF);
// return HAL_I2C_Master_Transmit(&INA226_COM_PORT, INA226_ADDRESS, SentTable, 3, INA226_I2C_TIMEOUT);
// }
// uint16_t INA226_GetAlertLimit()
// {
// uint8_t SentTable[1] = {INA226_ALERTL};
// uint8_t ReceivedTable[2];
// HAL_I2C_Master_Transmit(&INA226_COM_PORT,INA226_ADDRESS, SentTable, 1, INA226_I2C_TIMEOUT);
// if (HAL_I2C_Master_Receive(&INA226_COM_PORT,INA226_ADDRESS, ReceivedTable, 2, INA226_I2C_TIMEOUT) != HAL_OK) return 0xFF;
// else return ((uint16_t)ReceivedTable[0]<<8 | ReceivedTable[1]);
// }
main.c伪代码
int main(void)
{
while (1)
{
batteryVolt = INA226_GetBusV();//锂电池电压
//单节18650电池
if(batteryVolt>4.15) //电压值对比
{BatCap = 0.99;}//容量
else if(batteryVolt<3.4)
{BatCap =0;}
else
{BatCap = (batteryVolt-3.4)/(4.15-3.4);}//正常情况下计算比例
if(BatCap<0.45)
{}//没电啦
printf("B:%3.1fv Q:%02d%% ",batteryVolt,(int)(BatCap*100)); //打印
}
}