本帖最后由 bzhou830 于 2023-12-12 16:23 编辑
本帖最后由 bzhou830 于 2023-12-12 16:21 编辑
上一篇https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=43771&_dsign=5acd58c5中我们分析了硬件的可行性。然后使用lvgl在小安派s1上做了一个简单的ui界面。这篇我们来研究下INA226这个IC的使用。
1.INA226模块
INA226是一款集成了高精度电流和电压测量功能的数字电流/功率监测芯片。它可以通过I2C接口与微控制器或其他设备通信,用于实时监测和测量电流、电压和功率。从INA226的data sheet中可以看到其典型测量电路如下图,vbus引脚接被测量电路的电源+端,Vin+/Vin-串联在被测试回路中。地址线A0/A1直接接地,因为我们这里的IIC上只使用了一片INA226。
data sheet中还给出了layout的参考方案,这个方案和我们使用的模块布局是比较一致的。
INA226通过I2C接口与主控设备通信,所以我们就需要使用小安派的IO和INA226的IIC时钟线和数据线连接起来。IIC通信部分直接参考数据手册中的时序图即可,在INA226_Init()函数中,写入Configuration Register的数据为0x4527(16次平均,1.1ms,1.1ms,连续测量分流电压和总线电压)想修改的话参考数据手册寄存器定义表格。而写入Calibration Register的数据需要根据实际电路中的采样电阻阻值以及电流分辨率来设置。因为我们使用的INA226模块中使用的采样电阻为R100(0.1欧)。根据数据手册中的公式1和公式2。
因为Shunt Voltage Register的值最大为 0x7FFF,LSB=2.5uV, FSR = 81.92mV
。又因为分流电阻阻值为 0.1
欧,所以最大电流为 819.2mA
,Maximum Expected Current的值不能超过 819.2mA
。(避免在使用中出现的超量程情况)
假设 Current_LSB = 0.02mA
,则 Maximum Expected Current = 655.36mA
,满足上述条件。则:
CAL = 0.00512/(0.02*0.1)*1000 = 2560= 0x0A00
。
所以写入Calibration Register中的数据为0x0A00。
2. 小安派驱动INA226
前面讲到INA226通过IIC接口和小安派通信。这里为了深入的了解下IIC时序,我们直接采用模拟IIC的方式来对INA226进行驱动,并且使用逻辑分析仪来调试、捕获波形数据来进行深入研究。驱动INA226的代码如下:
bsp_ina226.h
#ifndef __BSP_INA226__
#define __BSP_INA226__
#define READ_ADDR 0x81 //A0=GND,A1=GND // R=1, W=0
#define WRITE_ADDR 0x80
#define Config_Reg 0x00
#define Shunt_V_Reg 0x01
#define Bus_V_Reg 0x02
#define Power_Reg 0x03
#define Current_Reg 0x04
#define Calib_Reg 0x05
#define Mask_En_Reg 0x06
#define Alert_Reg 0x07
#define Man_ID_Reg 0xFE //0x5449
#define ID_Reg 0xFF //0x2260
uint16_t INA226_Read2Byte(uint8_t reg_addr);
uint8_t INA226_Write2Byte(uint8_t reg_addr,uint16_t reg_data);
void INA226_Init(void);
#endif
bsp_ina226.c
#include "bflb_gpio.h"
#include "bsp_ina226.h"
struct bflb_device_s *gpio;
#define IIC_SDA_PIN GPIO_PIN_0
#define IIC_SCL_PIN GPIO_PIN_1
#define READ_SDA bflb_gpio_read(gpio, IIC_SDA_PIN)
#define delay_us(val) bflb_mtimer_delay_us(val);
#define delay_ms(val) bflb_mtimer_delay_ms(val);
void SDA_OUT()
{
bflb_gpio_deinit(gpio, IIC_SDA_PIN);
bflb_gpio_init(gpio, IIC_SDA_PIN, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
}
void SDA_IN()
{
bflb_gpio_deinit(gpio, IIC_SDA_PIN);
bflb_gpio_init(gpio, IIC_SDA_PIN, GPIO_INPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
}
void IIC_SetSDA()
{
bflb_gpio_set(gpio, IIC_SDA_PIN); // 将pin0设置为高电平
}
void IIC_ResetSDA()
{
bflb_gpio_reset(gpio, IIC_SDA_PIN); // 将pin0设置为低电平
}
void IIC_setSDAVal(uint8_t val)
{
if (val)
{
bflb_gpio_set(gpio, IIC_SDA_PIN); // 将pin0设置为高电平
}
else
{
bflb_gpio_reset(gpio, IIC_SDA_PIN); // 将pin0设置为低电平
}
}
void IIC_SetSCL()
{
bflb_gpio_set(gpio, IIC_SCL_PIN); // 将pin1设置为高电平
}
void IIC_ResetSCL()
{
bflb_gpio_reset(gpio, IIC_SCL_PIN); // 将pin1设置为高电平
}
// 初始化IIC
void IIC_Init(void)
{
gpio = bflb_device_get_by_name("gpio");
bflb_gpio_init(gpio, IIC_SDA_PIN, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
bflb_gpio_init(gpio, IIC_SCL_PIN, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
}
// 产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); // sda线输出
IIC_SetSDA();
IIC_SetSCL();
delay_us(4);
IIC_ResetSDA(); // START:when CLK is high, DATA change form high to low
delay_us(4);
IIC_ResetSCL(); // 钳住I2C总线,准备发送或接收数据
}
// 产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT(); // sda线输出
IIC_ResetSCL();
IIC_ResetSDA(); // STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SetSCL();
IIC_SetSDA(); // 发送I2C总线结束信号
delay_us(4);
}
// 等待应答信号到来
// 返回值:1,接收应答失败
// 0,接收应答成功
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime = 0;
SDA_IN(); // SDA设置为输入
IIC_SetSDA();
delay_us(1);
IIC_SetSCL();
delay_us(1);
while (READ_SDA)
{
ucErrTime++;
if (ucErrTime > 250)
{
IIC_Stop();
return 1;
}
}
IIC_ResetSCL(); // 时钟输出0
return 0;
}
// 产生ACK应答
void IIC_Ack(void)
{
IIC_ResetSCL();
SDA_OUT();
IIC_ResetSDA();
delay_us(2);
IIC_SetSCL();
delay_us(2);
IIC_ResetSCL();
}
// 不产生ACK应答
void IIC_NAck(void)
{
IIC_ResetSCL();
SDA_OUT();
IIC_SetSDA();
delay_us(2);
IIC_SetSCL();
delay_us(2);
IIC_ResetSCL();
}
// IIC发送一个字节
// 返回从机有无应答
// 1,有应答
// 0,无应答
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_ResetSCL(); // 拉低时钟开始数据传输
for (t = 0; t < 8; t++)
{
// IIC_SDA=(txd&0x80)>>7;
IIC_setSDAVal((txd & 0x80) >> 7);
txd <<= 1;
delay_us(2); // 对TEA5767这三个延时都是必须的
IIC_SetSCL();
delay_us(2);
IIC_ResetSCL();
delay_us(2);
}
}
// 读1个字节,ack=1时,发送ACK,ack=0,发送nACK
uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i, receive = 0;
SDA_IN(); // SDA设置为输入
for (i = 0; i < 8; i++)
{
IIC_ResetSCL();
delay_us(2);
IIC_SetSCL();
receive <<= 1;
if (READ_SDA)
receive++;
delay_us(1);
}
if (!ack)
IIC_NAck(); // 发送nACK
else
IIC_Ack(); // 发送ACK
return receive;
}
uint16_t INA226_Read2Byte(uint8_t reg_addr)
{
uint16_t reg_data = 0;
uint16_t temp = 0;
IIC_Start();
IIC_Send_Byte(WRITE_ADDR);
if (IIC_Wait_Ack())
return 0;
IIC_Send_Byte(reg_addr);
if (IIC_Wait_Ack())
return 0;
IIC_Start();
IIC_Send_Byte(READ_ADDR);
if (IIC_Wait_Ack())
return 0;
reg_data = IIC_Read_Byte(1);
reg_data = (reg_data << 8) & 0xFF00;
temp = IIC_Read_Byte(0);
IIC_Stop();
reg_data |= temp;
return reg_data;
}
uint8_t INA226_Write2Byte(uint8_t reg_addr, uint16_t reg_data)
{
uint8_t data_high = (uint8_t)((reg_data & 0xFF00) >> 8);
uint8_t data_low = (uint8_t)reg_data & 0x00FF;
IIC_Start();
IIC_Send_Byte(WRITE_ADDR);
if (IIC_Wait_Ack())
return 0;
IIC_Send_Byte(reg_addr);
if (IIC_Wait_Ack())
return 0;
IIC_Send_Byte(data_high);
if (IIC_Wait_Ack())
return 0;
IIC_Send_Byte(data_low);
if (IIC_Wait_Ack())
return 0;
IIC_Stop();
delay_ms(2);
return 1;
}
void INA226_Init(void)
{
IIC_Init();
INA226_Write2Byte(Config_Reg, 0x4527); // 0100_010_100_100_111 //16次平均,1.1ms,1.1ms,连续测量分流电压和总线电压
INA226_Write2Byte(Calib_Reg, 0x0A00);
}
烧录代码用逻辑分析仪来看看数据波形,可以从波形上看到INA226和小安派通信正常。
!这里再放出来一个通信不正常的图,原因就是输入输出同时设置出现电气干扰的情况下数据错误的情况.
从串口输出的测量值这时候已经出现:
至此,我们已经成功的驱动了INA226, 后面讲测量的数据显示在显示器上,并对外形美化美化就完成了。
点个支持再走吧,下期见!