一、简单介绍
芯片的datasheet地址:
INA3221 三通道、高侧测量、分流和总线电压监视器,具有兼容 I2C 和 SMBUS 的接口 datasheet (Rev. B)
笔者所使用的INA3221是淘宝买的模块
原理图
模块的三个通道的电压都是一样,都是POWER。这个芯片采用的是高侧测量:每个通道有两个引脚,一个连接负载去给负载供电,另一个回来,回到GND
经过笔者测量和观察,采样电阻的阻值应该是100mΩ
二、模块与接线
INA3221使用IIC通信协议进行读写,笔者使用的是STM32G030F6P6单片机来操作,读者按实际情况类推即可,cubeMX配置,基本都大差不差。
POWER连接外部电源的正极
GND连接外部电源的负极
将模块的GND与单片机的GND连接起来,注意这里地接在一起,如果单片机连着电脑,请务必小心操作,不要接反也不要短路
三、cubemx配置
设置时钟
用内部的RC震荡就可以了,也可以使用外部晶振,但如果用外部晶振的话,这颗晶振必须是有源的,之前看一些G030的板子焊了一个无源晶振,有点匪夷所思。
设置IIC
打算把电压和电流显示在0.96寸的oled上,因此就开了两个IIC,当然了,只开一个也可以,把INA3221和OLED都接在一个IIC总线上就行了,但为了方便,笔者开了两个。
这里的频率可以开到1Mhz,如果是103C8T6,应该是到不了的😋
数据量很小,DMA就不开了
设置GPIO
由于模块上自带了LED灯,所以就没必要开输入IO口给模块,可以开一个监控单片机程序运行的LED
至此,cubeMX配置完毕
四、keil配置
五、驱动编写
打开手册关于编程的章节
支持快速IIC传输,且高位在前
不管是读还是写,一开始要发一个寄存器指针过去,定位一下寄存器
寄存器读写函数
static void INA3221_ReadReg(INA3221_regType *reg)
{
HAL_I2C_Mem_Read(INA3321_I2C, INA3221_I2C_ADDRESS, reg->address, 1, ®->data, 2, 0xFFFF);
DataReverse(reg->data, ®->data);
}
static void INA3221_WriteReg(INA3221_regType *reg)
{
DataReverse(reg->data, ®->data);
HAL_I2C_Mem_Write(INA3321_I2C, INA3221_I2C_ADDRESS, reg->address, 1, ®->data, 2, 0xFFFF);
}
寄存器表
先简单读一下芯片的ID,看看是否能正常通信
读芯片的id号,值是0x2032,但波形是3220,因此要翻转一下高低字节
代码如下
static void DataReverse(uint16_t raw, uint16_t* cook)
{
*cook = ((uint8_t)(raw) << 8) | (raw >> 8);
}
读channel的bus电压值
一个位代表8mV,但寄存器里面的左移三位又刚好弥补了这一点,因此直接读到的就是电压值。
代码如下
static void INA3221_Sample_Volt()
{
INA3221_ReadReg(&volt1);
INA3221_ReadReg(&volt2);
INA3221_ReadReg(&volt3);
}
读channel的shunt电压值
一个位代表40uV,满量程是163.8mV,因此这个用的100mΩ的模块最大采集电流为1638mA
shunt可以是负数,代表反向电流,但模块设计成IN-接PWR了,笔者就只实验了正向电流
代码如下
static void INA3221_Calculate_Current(uint32_t* current)
{
current[0] = shunt1.data >> 3;
/* 40uV per LSB */
current[0] *= 4;
current[0] = current[0] * 10 / SHUNT_RESISTOR;
current[1] = shunt2.data >> 3;
/* 40uV per LSB */
current[1] *= 4;
current[1] = current[1] * 10 / SHUNT_RESISTOR;
current[2] = shunt3.data >> 3;
/* 40uV per LSB */
current[2] *= 4;
current[2] = current[2] * 10 / SHUNT_RESISTOR;
}
主函数编写
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_I2C2_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
INA3221_Init();
OLED_Init();
OLED_Clear();
OLED_ShowString(0,0,"C1",16);
OLED_ShowString(0,2,"C2",16);
OLED_ShowString(0,4,"C3",16);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
INA3221_GetVolt();
INA3221_GetCurrent();
sprintf(info[0], "%.2fV ", voltage[0]/1000.0f);
sprintf(info[1], "%.2fV ", voltage[1]/1000.0f);
sprintf(info[2], "%.2fV ", voltage[2]/1000.0f);
OLED_ShowString(20,0,info[0],16);
OLED_ShowString(20,2,info[1],16);
OLED_ShowString(20,4,info[2],16);
sprintf(info[3], "%.3fA", current[0]/1000.0f);
sprintf(info[4], "%.3fA", current[1]/1000.0f);
sprintf(info[5], "%.3fA", current[2]/1000.0f);
OLED_ShowString(80,0,info[3],16);
OLED_ShowString(80,2,info[4],16);
OLED_ShowString(80,4,info[5],16);
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
六、效果展示
七、驱动附录
ina3221.h
#ifndef INA3221_H
#define INA3221_H
#include"i2c.h"
#include "stdint.h"
#include "main.h"
typedef struct
{
uint8_t address;
uint16_t data;
}INA3221_regType;
typedef enum
{
CH1,
CH2,
CH3,
}INA3221_SHUNTChannelType;
/**
* prepare for mask enable register
*/
typedef struct
{
uint8_t CF1;
uint8_t CF2;
uint8_t CF3;
uint8_t WF1;
uint8_t WF2;
uint8_t WF3;
}INA3221_FlagType;
extern INA3221_regType dieID;
extern INA3221_regType mask_enable;
extern INA3221_regType volt1;
extern INA3221_regType shunt1;
extern INA3221_regType volt2;
extern INA3221_regType shunt2;
extern INA3221_regType volt3;
extern INA3221_regType shunt3;
extern uint32_t current[3];
extern uint16_t voltage[3];
extern INA3221_FlagType INA3221_flag;
void INA3221_Config();
void INA3221_Init();
void INA3221_Reset();
void INA3221_ReadDieID();
void INA3221_ReadmanufactID();
void INA3221_GetVolt();
void INA3221_GetCurrent();
#endif
ina3221.c
#include "INA3221.h"
#define INA3321_I2C &hi2c1
#define INA3221_I2C_ADDRESS 0x80
/*shunt resistor mohm*/
#define SHUNT_RESISTOR 100
#define POWER_VALID_UPPER 10000
#define POWER_VALID_LOWER 9000
/**
* register address table
*/
typedef enum
{
SHUNT_CH1 = 0x01,
VOLT_CH1,
SHUNT_CH2,
VOLT_CH2,
SHUNT_CH3,
VOLT_CH3,
CRITICAL_CH1,
WARNING_CH1,
CRITICAL_CH2,
WARNING_CH2,
CRITICAL_CH3,
WARNING_CH3,
SHUNT_VOLT_SUM,
SHUNT_VOLT_SUM_LIMIT,
MASK_ENABLE,
POWER_VALID_HIGH = 0x10,
POWER_VALID_LOW,
}INA3221_RegAddressType;
/**
* average samples
*/
typedef enum
{
AVG_1,
AVG_4,
AVG_16,
AVG_64,
AVG_128,
AVG_256,
AVG_512,
AVG_1024,
}INA3221_AVGType;
/**
* average samples
*/
typedef enum
{
CONV_TIME_140US,
CONV_TIME_204US,
CONV_TIME_332US,
CONV_TIME_588US,
CONV_TIME_1_1MS,
CONV_TIME_2_116MS,
CONV_TIME_4_156MS,
CONV_TIME_8_244MS,
}INA3221_CTType;
/**
* average samples
*/
typedef enum
{
POWER_DOWN,
SHUNT_SINGLE,
BUS_SINGLE,
SHUNT_BUS_SINGLE,
POWER_DN,
SHUNT_CONTINUOUS,
BUS_CONTINUOUS,
SHUNT_BUS_CONTINUOUS,
}INA3221_ModeType;
INA3221_regType cfg = {.address = 0};
INA3221_regType volt1 = {.address = VOLT_CH1};
INA3221_regType shunt1 = {.address = SHUNT_CH1};
INA3221_regType volt2 = {.address = VOLT_CH2};
INA3221_regType shunt2 = {.address = SHUNT_CH2};
INA3221_regType volt3 = {.address = VOLT_CH3};
INA3221_regType shunt3 = {.address = SHUNT_CH3};
INA3221_regType critical_ch1 = {.address = CRITICAL_CH1};
INA3221_regType critical_ch2 = {.address = CRITICAL_CH2};
INA3221_regType critical_ch3 = {.address = CRITICAL_CH3};
INA3221_regType warning_ch1 = {.address = WARNING_CH1};
INA3221_regType warning_ch2 = {.address = WARNING_CH2};
INA3221_regType warning_ch3 = {.address = WARNING_CH3};
INA3221_regType mask_enable = {.address = MASK_ENABLE};
INA3221_regType power_valid_upper = {.address = POWER_VALID_HIGH};
INA3221_regType power_valid_lower = {.address = POWER_VALID_LOW};
INA3221_regType manufactID = {.address = 0xFE};
INA3221_regType dieID = {.address = 0xFF};
/* store power voltage */
uint32_t current[3];
/* store power current */
uint16_t voltage[3];
INA3221_FlagType INA3221_flag;
/**
* exchange data high and low byte for word variable
*/
static void DataReverse(uint16_t raw, uint16_t* cook);
/**
* read register value
*/
static void INA3221_ReadReg(INA3221_regType *reg);
/**
* write register value
*/
static void INA3221_WriteReg(INA3221_regType *reg);
/**
* cacluate voltages from volt register value
*/
static void INA3221_Calculate_Volt(uint16_t* volt);
/**
* cacluate currents from shunt register value
*/
static void INA3221_Calculate_Current(uint32_t* current);
/**
* set limite value for current alert
*/
static void INA3221_SetLimit(INA3221_regType *reg, uint16_t volt);
/**
* just read volt registers
*/
static void INA3221_Sample_Volt();
/**
* just read shunt registers
*/
static void INA3221_Sample_Shunt();
static void DataReverse(uint16_t raw, uint16_t* cook)
{
*cook = ((uint8_t)(raw) << 8) | (raw >> 8);
}
static void INA3221_ReadReg(INA3221_regType *reg)
{
HAL_I2C_Mem_Read(INA3321_I2C, INA3221_I2C_ADDRESS, reg->address, 1, ®->data, 2, 0xFFFF);
DataReverse(reg->data, ®->data);
}
static void INA3221_WriteReg(INA3221_regType *reg)
{
DataReverse(reg->data, ®->data);
HAL_I2C_Mem_Write(INA3321_I2C, INA3221_I2C_ADDRESS, reg->address, 1, ®->data, 2, 0xFFFF);
}
static void INA3221_Calculate_Volt(uint16_t* volt)
{
*volt = volt1.data;
*(volt + 1) = volt2.data;
*(volt + 2) = volt3.data;
}
static void INA3221_Calculate_Current(uint32_t* current)
{
current[0] = shunt1.data >> 3;
/* 40uV per LSB */
current[0] *= 4;
current[0] = current[0] * 10 / SHUNT_RESISTOR;
current[1] = shunt2.data >> 3;
/* 40uV per LSB */
current[1] *= 4;
current[1] = current[1] * 10 / SHUNT_RESISTOR;
current[2] = shunt3.data >> 3;
/* 40uV per LSB */
current[2] *= 4;
current[2] = current[2] * 10 / SHUNT_RESISTOR;
}
static void INA3221_SetLimit(INA3221_regType *reg, uint16_t volt)
{
reg->data = volt;
INA3221_WriteReg(reg);
}
static void INA3221_Sample_Volt()
{
INA3221_ReadReg(&volt1);
INA3221_ReadReg(&volt2);
INA3221_ReadReg(&volt3);
}
static void INA3221_Sample_Shunt()
{
INA3221_ReadReg(&shunt1);
INA3221_ReadReg(&shunt2);
INA3221_ReadReg(&shunt3);
}
void INA3221_ReadDieID()
{
INA3221_ReadReg(&dieID);
}
void INA3221_ReadmanufactID()
{
INA3221_ReadReg(&manufactID);
}
void INA3221_GetVolt()
{
INA3221_Sample_Volt();
INA3221_Calculate_Volt(voltage);
}
void INA3221_GetCurrent()
{
INA3221_Sample_Shunt();
INA3221_Calculate_Current(current);
}
void INA3221_Config()
{
/* read default register value from chip*/
INA3221_ReadReg(&cfg);
/* store it in config variable */
cfg.data |= (cfg.data & !0x0E00) | (AVG_4 << 9);
/*bus*/
cfg.data |= (cfg.data & !0x01C0) | (CONV_TIME_2_116MS << 6);
/*shunt*/
cfg.data |= (cfg.data & !0x38) | (CONV_TIME_2_116MS << 3);
/* wirte to register value */
INA3221_WriteReg(&cfg);
INA3221_ReadReg(&cfg);
}
void INA3221_Set_Critical(INA3221_SHUNTChannelType channel, uint16_t current)
{
switch (channel)
{
case CH1:
{
/* 40uV per LSB */
INA3221_SetLimit(&critical_ch1, (current * SHUNT_RESISTOR / 40) << 3);
break;
}
case CH2:
{
INA3221_SetLimit(&critical_ch2, (current * SHUNT_RESISTOR / 40) << 3);
break;
}
case CH3:
{
INA3221_SetLimit(&critical_ch3, (current * SHUNT_RESISTOR / 40) << 3);
break;
}
default:
break;
}
}
void INA3221_Set_Warning(INA3221_SHUNTChannelType channel, uint16_t current)
{
switch (channel)
{
case CH1:
{
INA3221_SetLimit(&warning_ch1, (current * SHUNT_RESISTOR / 40) << 3);
break;
}
case CH2:
{
INA3221_SetLimit(&warning_ch2, (current * SHUNT_RESISTOR / 40) << 3);
break;
}
case CH3:
{
INA3221_SetLimit(&warning_ch3, (current * SHUNT_RESISTOR / 40) << 3);
break;
}
default:
break;
}
}
void INA3221_Reset()
{
cfg.data = 0x8000;
INA3221_WriteReg(&cfg);
}
void INA3221_Init()
{
INA3221_ReadDieID();
/* reset all registers */
INA3221_Reset();
INA3221_Config();
}
八、补充功能
INA3221还带可编程报警和警告输出
一、关键提示
会去比较每个通道的shunt电压值和相应的预设的值,用来判断是否发生过流
二、警告提示
会去比较每个通道的平均shunt电压值和相应的预设的值,用来判断是否发生过流
三、电源有效提示
芯片默认的PV上限是10V,下限是9V,都是可以改写的
意思是在外部高压输入的时候,如果三个通道的电压均高于10V,那么电源OK
如果电压降低,低于9V,电源不OK,模块的灯就会亮起来,因为这几个提示引脚都是开漏输出的
在外部高压输入的时候,如果有任意通道电压<10V,电源不OK,模块的灯就会亮起来,因为这几个提示引脚都是开漏输出的
如果电压升高,高于10V就可以了
有点像施密特触发器
可以把PV上拉到VPU去,这样电压就抬起来了,也可以在PV引脚和地直接串电阻来降压
模块的输入端子里有VPU,根据实际情况使用即可