我是靠谱客的博主 风中帽子,最近开发中收集的这篇文章主要介绍高精度ADC采集电量显示(MCP3421)Linux,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • 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时的测量电压值

四.电压转电量思路

  1. 我们通过MCP3421芯片最终才回来的是实际的电压,如何转换为剩余的电量那,我在测试的时候该芯片可以采0-12v的电压,但是电池输出为12v到7v左右设置就不能正常工作了所以我想到下面方法

  2. 我们需要测量出满电时的电池输出电压battery_H,和设备不能工作的时候的临界电压值battery_L

  3. 用下面的公式来计算,通常采回来的电压是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所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(62)

评论列表共有 0 条评论

立即
投稿
返回
顶部