概述
文章目录
- Linux
- 一.简介
- 二.前序
- 三.MCP3421
- 1.官方资料下载
- 2.芯片讲解
- 四.电压转电量思路
- 五.程序
- 1.C程序
- 2.测试结果
- 六.总结
Linux
一.简介
最近在做一个嵌入式的设备,该设备是移动电源供电,所以就设计到了电池电量的检测和显示剩余电量百分比,我想这个功能在嵌入式产品中是比较常见的。这里我们用到的电压采集的芯片为MCP3421,这是一个iic驱动的芯片支持最高18位的采集精度,虽然做电量显示不需要这么高的精度,但是这样的进度不得不羡慕一下,连续两周的奋战,终于从坑里爬出来了。不想让大家在像我这样爬的,一两天能完成的事不需要煎熬一两周。
二.前序
其实不不打算说的,但是在搜查阅资料的时候,真的是太坑了。关于MCP3421芯片的驱动程序网上也有,所以我就参考着来写了,结果各种试水,各种惨,有IIC通讯时SDA和SCL电平写错的,有配置数据写错的(读操作和写操作),最好都怀疑他们上传代码的时候有没有编译运行和测试。话又说回来人家上传什么,是人家的权利,总结一下教训,大家以官方资料为准,进行程序的编写
出错无非就是IIC通讯的时候没通讯上,那我们就看输出的两个管脚SDA和SCL输出的波形对不对,我刚开始也没通讯上,最后用逻辑分析仪抓的波形,修改程序才写好的,感觉好的工具事半功倍,废话不多说了,直接给大家上干货
三.MCP3421
1.官方资料下载
mcp3421官方资料(中文+英文版)
密码:4pzc
2.芯片讲解
2.1芯片连接原理图
2.2 设置配置寄存器
配置8位寄存器的值
相信大家看到上面的图片就能明白
- 默认的配置为 连续转换+12位采样率+x1的增益(增益就是将你输入的信号放大) 1001 0000 所以就是 0x90
- 一般采用 连续转换+18位采样率+x1的增益 1001 1100 就是 0x9c
- 单次转换+18位采样率+x1的增益 1000 1100 就是 0x8c
根据自己的需求配置就可以了
2.3 读写的配置
这个很好理解,我们在对芯片进行读数据或者写配置的时候,首先要写入参数,让芯片进入不同的模式
也是8位一个字节包含 器件代码(4位)+地址位(3位)+R/W 位 MCP3421器件代码为 1101,地址位出厂没有要求就是 000,而读数取据模式 R/W位位 1,写模式为0
- 读模式 1101 0001 0xd1
- 写模式 1101 0000 0xd0
2.4采集电压转换
我们采集到的是,18位的数据需要通过公式计算才能转换成实际测量的电压值
转换公式如下
输入电压 = (输出代)* (LSB/PGA)
PGA为增益,增益有 1,2,4,8支持四中,通常我们选择为1
LSB为分辨率
注意公式里面运算都是以V为单位,输出电压的范围为-2.048-0和0-2.048V,需要我们测量转换再次转换一下就好了
//转化为实际电压
data = 输出电压 /0.163; //0.163为实际输入1v时的测量电压值
四.电压转电量思路
-
我们通过MCP3421芯片最终才回来的是实际的电压,如何转换为剩余的电量那,我在测试的时候该芯片可以采0-12v的电压,但是电池输出为12v到7v左右设置就不能正常工作了所以我想到下面方法
-
我们需要测量出满电时的电池输出电压battery_H,和设备不能工作的时候的临界电压值battery_L
-
用下面的公式来计算,通常采回来的电压是float类型,需要转换为int类型显示剩余电量的百分比
剩余电量百分比 = (int)(((采集电压 - battery_L)*100) / (battery_H - battery_L))
五.程序
1.C程序
代码比较长,码云上代码下载地址
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //open
#include <unistd.h> //read
#include <stdlib.h>
#define u8 unsigned char
#define u16 unsigned short int
#define u32 unsigned int
//gpio 电量采集
#define scl 919
#define sda 916
//这里通过linux 打开文件的方式控制gpio所以比较耗时间
#define I2C_SCL_L set_gpioValue(scl,0)
#define I2C_SCL_H set_gpioValue(scl,1)
#define I2C_SDA_L set_gpioValue(sda,0)
#define I2C_SDA_H set_gpioValue(sda,1)
#define I2C_SCL_OUT set_gpioDirection(scl, "out")
#define I2C_SDA_OUT set_gpioDirection(sda, "out")
#define I2C_SDA_IN set_gpioDirection(sda, "in")
#define I2C_SDA_GET get_gpioValue(sda)
//MCP342 出厂时如果未指定地址则为 1101 000x 最后一位x 如果是写配置则为1 读则为0
#define MCP3421_ADDREAD 0xd1
#define MCP3421_ADDWRITE 0Xd0
//写入配置参数 如0x8c,0x9c; 8:单次转换 9:连续转换;c:18位 3.75 sps ,PGA=1;
//mcp3421初始化不配置 默认为0x90 连续转换 12位,PGA =1
#define MCP3421_OneShot_18BIT_X1 0x8c
#define MCP3421_MoreShot_18BIT_X1 0x9c
//电池 电量显示
#define battery_H 12 //电池的满电电压
#define battery_L 8 //电池的下限电压(需要实际测试)
//create gpio file
void creat_gpio(int n)
{
FILE * fp =fopen("/sys/class/gpio/export","w");
if (fp == NULL)
perror("export open filed");
else
fprintf(fp,"%d",n);
fclose(fp);
}
//设置gpio的方向 "in" or "out"
void set_gpioDirection(int n,char *direction)
{
char path[100] = {0};
sprintf(path,"/sys/class/gpio/gpio%d/direction",n);
FILE * fp =fopen(path,"w");
if (fp == NULL)
perror("direction open filed");
else
fprintf(fp,"%s",direction);
fclose(fp);
}
//设置gpio的电平
void set_gpioValue(int n, int value)
{
char path[64];
snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", n);
FILE *fp = fopen(path, "w");
if (fp == NULL)
perror("direction open filed");
else
fprintf(fp, "%d", value);
fclose(fp);
}
//获取gpio的值
int get_gpioValue(int n)
{
char path[64];
char value_str[3];
int fd;
snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", n);
fd = open(path, O_RDONLY);
if (fd < 0) {
perror("Failed to open gpio value for reading!");
return -1;
}
if (read(fd, value_str, 3) < 0) {
perror("Failed to read value!");
return -1;
}
close(fd);
return (atoi(value_str));
}
// IIC传输延时函数
void I2C_Delay(int t)
{
int i, j;
for(i=0; i<t; i++)
{
for(j=0; j<12; j++)
{}
}
}
//初始化scl和sda为高电平
void I2C_Init(void)
{
creat_gpio(scl);
creat_gpio(sda);
set_gpioDirection(scl, "out"); //开发板调为输出模式默认为低电平
set_gpioDirection(sda, "out");
set_gpioValue(scl,1);
set_gpioValue(sda,1);
usleep(100); //保持高电平稳定
}
/*******************************************************************************
* 函数名 : I2cStart()
* 函数功能 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
* 输入 : 无
* 输出 : 无
* 备注 : 起始之后SDA和SCL都为0
*******************************************************************************/
void I2C_Start()
{
I2C_SDA_OUT; //设置输出模式(会将sda拉低)
I2C_Delay(1);
I2C_SDA_H;
I2C_SCL_H;
I2C_SDA_L;
I2C_SCL_L;
I2C_Delay(1);
}
/*******************************************************************************
* 函数名 : I2cStop()
* 函数功能 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿
* 输入 : 无
* 输出 : 无
* 备注 : 结束之后保持SDA和SCL都为1;表示总线空闲
*******************************************************************************/
void I2C_Stop()
{
I2C_SDA_OUT; //设置输出模式(会将sda拉低)
I2C_Delay(1);
I2C_SDA_L;
I2C_SCL_H;
I2C_SDA_H;
I2C_Delay(1);
}
/*******************************************************************************
* 函数名 : IIC_Wait_Ack()
* 函数功能 : 检查应答信号:在第9个时钟信号为高电平期间主机释放sda,从机拉低sda
* 输入 : 从机
* 输出 : 返回 0有应答,返回1 无应答
* 备注 :
*******************************************************************************/
unsigned char I2C_Wait_Ack()
{
unsigned int i = 0;
I2C_SDA_IN; //设置sda为输入模式
I2C_SCL_H;
while(I2C_SDA_GET)//等待应答,也就是等待从设备把SDA拉低
{
i++;
if( i > 200) //如果超过时间没有应答发送失败,或者为非应答,表示接收结束
{
I2C_SCL_L;
I2C_SCL_H;
printf("no ackrn"); //没有应答
return 1;
}
}
I2C_SCL_L;
return 0;
}
//应答
void I2C_Ack()
{
I2C_SDA_OUT; //会将sda拉低
I2C_Delay(1);
I2C_SCL_L;
I2C_SDA_L;
I2C_SCL_H;
I2C_Delay(1); //从机检测sda的低电平
I2C_SCL_L; //第9个时钟周期结束
}
//非应答
void I2C_NAck()
{
I2C_SDA_OUT; //会将sda拉低
I2C_Delay(1);
I2C_SCL_L;
I2C_SDA_H; //至高sda 表示不应答
I2C_SCL_H;
I2C_Delay(1); //从机检测sda的高电平
I2C_SCL_L; //第9个时钟周期结束
}
/*******************************************************************************
* 函数名 : I2cSendByte(unsigned char dat)
* 函数功能 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
* 输入 : num
* 输出 : 0或1。发送成功返回1,发送失败返回0
* 备注 : 发送完一个字节SCL=0,SDA=1
*******************************************************************************/
unsigned char I2C_SendByte(unsigned char dat)
{
I2C_SDA_OUT; //会将sda拉低
I2C_Delay(1);
unsigned char a=0,b=0;
for(a=0;a<8;a++)//要发送8位,从最高位开始
{
I2C_SCL_L;
if (dat&0x80)
{
I2C_SDA_H;
}
else
{
I2C_SDA_L;
}
dat<<=1;
I2C_SCL_H;
}
I2C_SCL_L;
}
/*******************************************************************************
* 函数名 : I2cReadByte()
* 函数功能 : 使用I2c读取一个字节
* 输入 : 无
* 输出 : dat
* 备注 : 接收完一个字节SCL=0,SDA=1.
*******************************************************************************/
unsigned char I2C_ReadByte()
{
I2C_SDA_IN;
unsigned char a=0,dat=0;
for(a=0;a<8;a++)//接收8个字节
{
dat<<=1;
I2C_SCL_L;
I2C_SCL_H;
if( I2C_SDA_GET )
{
dat |= 0x01;
}
else
{
dat &= 0xfe;
}
I2C_SCL_L;
}
return dat;
}
//MCP3421写配置
void Write_MCP3421(unsigned char WR_Data)
{
I2C_Start();
//写入地址 及 最后位为0,是写操作配置 最后位为1,是读操作配置
I2C_SendByte(0xd0); // 1101 a2 a1 a0 0 发送给第一个字节数据 MCP3421地址字节+R/W命令
// 1101 0000 0xd0 写模式
I2C_Wait_Ack();
I2C_SendByte(WR_Data); //RDY O/C C1 C0 S1 S0 G1 G0
I2C_Wait_Ack();
I2C_Stop();
}
//MCP3421读数据
float READ_MCP3421()
{
unsigned long int elech;
unsigned long int elecm;
unsigned long int elecl;
unsigned long int config;
unsigned long int AD_B_Result;
float AD_F_Result=0.0;
float LSB;
int PGA;
I2C_Start();
I2C_SendByte(0xd1); //0xd1=0b11010001, 最后一位1表示单片机接收数据
I2C_Wait_Ack(); //MCP3421发出应答ACK信号
//读取第二个字节数据 Upper Data Byte
elech=(long int)I2C_ReadByte(); //NULL NULL NULL NULL NULL NULL D17 D16
I2C_Ack(); //主器件发出应答信号
//读取第三个字节数据 Lower Data Byte
elecm=(long int)I2C_ReadByte(); //D15 D14 D13 D12 D11 D10 D9 D8
I2C_Ack(); 主器件发出应答信号
elecl=(long int)I2C_ReadByte(); //D7 D6 D5 D4 D3 D2 D1 D0
I2C_Ack();
config = (long int)(I2C_ReadByte()); //RDY C1 C0 O/C S1 S0 G1 G0
I2C_NAck();// 停止接收
I2C_Stop();
//开始进行数据转化
AD_B_Result=(elech<<16)|(elecm<<8)|(elecl); //将三个数据进行整合,整合成一个24位的数据
AD_B_Result=AD_B_Result&0x01ffffff; //由于数据elech的前6个字节没有用,所以将其清零
LSB = (float)(2.0 * 2.048) / 262144; //公式 (2*Vref(2.048))/2^n n为分辨率 12,14,16,18
//18为计算为15.625uv
PGA = 1; //增益为1
AD_F_Result = (float)(LSB * AD_B_Result)/PGA; //输出范围 -2.048v-0v-2.048v
return AD_F_Result;
}
float get_adc()
{
int i = 0;
float sum = 0.0;
float data = 0.0;
//为了稳定获取10次求平均数
for(i=0; i<10; i++)
{
sum += READ_MCP3421();
}
//转化为实际电压
data = (sum / 10) /0.163; //0.163为实际输入1v时的测量值
return data;
}
int main()
{
float data = 0;
int show = 0; //剩余电量百分比
I2C_Init();
//写入配置参数 如0x8c,0x9c; 8:单次转换 9:连续转换;c:18位 3.75 sps ,PGA=1;
Write_MCP3421(0X9C);
while(1)
{
//获取到的电压值
data = get_adc();
printf("data=%.3fvrn", data);
show = (int)(((data-battery_L)*100)/(battery_H - battery_L));
printf("battery = %d%%rn", show);
sleep(2);
}
}
2.测试结果
六.总结
大多数的ad采集芯片驱动方式都差不多,如果是IIC驱动的只是配置的参数不同,相信大家会一个其他的也就会了,创作不易,希望对大家有用
最后
以上就是风中帽子为你收集整理的高精度ADC采集电量显示(MCP3421)Linux的全部内容,希望文章能够帮你解决高精度ADC采集电量显示(MCP3421)Linux所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复