Linux下的使用字符设备驱动框架编写ADC驱动 ——MQ-4传感器

ADC的原理

ADC 的作用:模拟信号转换为数字信号

模拟信号一般是指连续变化的电压信号,其数值在一定范围内变化。

而数字信号是由一系列离散的数字表示, 只能取有限的值,通常以二进制形式表示。

ADC通常由一个采样保持电路、一个比较器和一个计数器组成。

采样保持电路将输入的模拟电压保持在一个稳定的值,

比较器将这个稳定的值与一个参考电压进行比较,

计数器记录比较器的输出信号的次数。

ADC的分辨率是指ADC可以分辨的最小电压变化,通常用位数来表示。(8 10 12 16)

ADC的工作原理是将模拟信号分割 成一系列离散的取样,并将每个取样值转换为相应的数字表示。这个过程 涉及到两个主要步骤:采样和量化。

细分为 采样-->保持-->量化-->编码

采样:ADC将连续变化的模拟信号在一定时间间隔内进行取样。取样频率决定了每秒采集的样本数,通常 以赫兹(Hz)表示。采样过程通过保持并测量模拟信号在每个采样时间点的电压值来实现。

量化:采样得到的连续模拟信号经过量化转换为数字形式。量化是将每个采样值映射到一个离散的数字值的过程。这通常通过比较采样值与参考电压之间的差异(逐次逼近法),并将其转换为数字表示。

逐次逼近法:ADC量化的过程 是相对于一个基准值的,这个基准值称之为基准电压。一般采用逐次逼近法的ADC会先拿采用电压Vadc跟基准电压Vref(3.3v)的1/2进行比较,如果Vadc>Vref,则结果为1,否则结果为0。之后继续拿Vadc 和Vref的1/4或Vref的3/4继续比较。这个过程有点像二分法,每次比较都会使量化的结果逼近真实值。

很明显,比较的次数 决定了测量的精度,这个精度被称之为ADC的分辨率。比如一个比较了8次的ADC外设,它就称为8位ADC,其结果是0~255(2的8次方)之间的一个数值,设该数值为n,那么实际电压就是Vref * (n/255)。如果把比较次数增加到10次,结果就是0~1023(2的10次方)之间的一个数。

例如,一个10位ADC可以分辨的最小电压变化为1/1024,这意味着ADC可以分辨的最小电变化为输入电压的1/1024。

V = (AD / 2^n) * Vref

其中,V表示实际电压值,AD表示AD转换的数字值,n表示AD转换的位数,Vref表示参考(基准)电压。

MQ-4传感器 可燃气体浓度检测传感器

参考博文:http://t.csdnimg.cn/TORnr

必须使用5v电压,否则会造成电压过低测不准

MQ-4传感器内置了一种特殊的材料,叫做敏感材料,它能与待测气体发生化学反应。

当检测到有害气体时,气体分子会被吸附在传感器的敏感层表面,并与敏感层中的化学物质发生反应。这种化学反应会改变敏感层的电阻值,从而使得整个传感器的电阻值发生变化。

U = IR

MQ- 4气体传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡(SnO2)。

当传感器所处环境中存在可燃气体时,传感器的电导率随空气中可燃气体浓度的增加而增大。使用简单的电路即可将电导率的变化转换为与该气体浓度相对应的输出信号。

MQ-4气体传感器对甲烷的灵敏度高,对丙烷、丁烷也有较好的灵敏度。

敏感材料会随着使用时间的增长而老化,使得传感器的精度逐渐降低。因此,定期更换传感器是必要的。

输入电压:DC5V 功耗(电流):150mA

DO输出:TTL数字量0和1(0.1和5V)

AO输出:0.1-0.3V(相对无污染),最高浓度电压4V左右

特别提醒:传感器通电后,需要预热20S左右,测量的数据才稳定,传感器发热属于正常现象,因为内部有电热丝,如果烫手就不正常了。

无天然气的环境下,实测AOUT端的电压为0.5V,当检测到天然气时,电压每升高0.1V,实际被测气体浓度增加200ppm

ppm = (Voltage - 0.5) / 0.1 * 200;

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
	int fd = open("/dev/adc",O_RDWR);

	if(fd < 0)
	{
		puts("error\n");
		return -1;
	}

	unsigned int short n;
	float C;
	float RS,PPM;
	while(1)
	{
		read(fd,&n,2);

	
		printf("%d\n",n);
		C = ((float)n/1023)*3.3;
		RS = (5.0/C-1)*1.0;   //RS = (Vc/VRL-1)*RL
		//y = -0.003x + 0.1864     RS/R0 = -0.003X+0.1864

		PPM = ((RS/12.0)-0.1864)/(-0.0003);

		printf("C = %f\n",PPM);
		
		sleep(1);
	}
}

S3C2440 adc驱动程序 采用通道AIN1采集

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/irqreturn.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <mach/irqs.h>
#define ADCCON   (0x58000000)
#define ADCDAT0  (0x5800000C)
#define CLKCON   (0x4c00000C)
static unsigned int *REG_ADCCON;
static unsigned int *REG_ADCDAT0;
static unsigned int *REG_CLKCON;


static int adc_driver_open(struct inode *pNode,struct file *fp)
{
	return 0;
}

static int adc_driver_close(struct inode *pNode,struct file *fp)
{
	return 0;
}

//采用读启动的方式启动adc

static ssize_t adc_driver_read(struct file *fp,char __user*userBuffer,size_t len,loff_t *offset)
{
	unsigned short ret;
	*REG_ADCCON|=(0x01 << 0);  //启动一次ADC转换
	//while(!(*REG_ADCCON&(1<<15)));
	ret = *REG_ADCDAT0&0x3ff;//将转换的结果放入 adcdat0 的低10位中
	copy_to_user(userBuffer,&ret,2);//把结果返回用户层
	return 2;
}

static ssize_t adc_driver_write(struct file *fp,const char __user *userBuffer,size_t len,loff_t*offset)
{	
	return 0;
}

static struct file_operations fops =
{
	.owner = THIS_MODULE,
	.open = adc_driver_open,
	.release = adc_driver_close,
	.read = adc_driver_read,
	.write = adc_driver_write,
};
//自动获取设备号需要定义的变量
static dev_t dev_num;//设备号
struct cdev adc_dev;//  cdev 是一个描述字符设备的结构体

//自动添加设备所需要定义的变量
static struct class *p_class;
static struct device *p_device;

static int __init adc_driver_init(void)
{
	int ret;
	//申请设备号  此设备号的起始值为 0    申请1个设备   设备起个名字叫做 adc_device
	ret = alloc_chrdev_region(&dev_num,0,1,"adc_device");
	if(ret)
	{
		printk("alloc_chrdev_region is error\n");
		goto alloc_chrdev_region_err;
	}

	printk("major = %u,minior = %u\n",MAJOR(dev_num),MINOR(dev_num));

	//将设备初始化
	cdev_init(&adc_dev,&fops);

	//向内核添加驱动程序  相当于  register_chrdev函数 的注册
	ret = cdev_add(&adc_dev,dev_num,1);
	if(ret)
	{
		printk("cdev_add is error\n");
		goto cdev_add_err;
	}

	//创建一个设备类  adc class
	p_class = class_create(THIS_MODULE,"adc class");
	if(IS_ERR(p_class))
	{
		printk("class_create is error!");
		goto class_create_err;
	}

	//创建一个设备类 属于 之前adc class的类别   这两步可以实现设备节点的自动添加
	//不需要使用mknod /dev/adc 手动添加设备节点
	p_device = device_create(p_class,NULL,dev_num,NULL,"adc");
	if(p_device == NULL)
	{
		printk("device_create is error\n");
		goto device_create_err;
	}



    //把需要配置引脚的物理地址映射到虚拟地址
	REG_ADCCON = ioremap(ADCCON,4);  //io引脚的控制寄存器
	REG_ADCDAT0 = ioremap(ADCDAT0,4);  //io引脚的数据寄存器
	REG_CLKCON = ioremap(CLKCON ,4);
	*REG_CLKCON |=(1<< 15);

	//配置寄存器
	*REG_ADCCON |= (0X01 << 14)|(24<< 6);
	*REG_ADCCON &=~(0X07<<3);
	*REG_ADCCON |= (0X01<<3);//把adc的采样通道设置为AIN1
	*REG_ADCCON &=~(0X01<<2);
	*REG_ADCCON &=~(0X01 << 1);


	printk("adc_driver_init\n");
	return 0;


device_create_err:
	class_destroy(p_class);
class_create_err:
	cdev_del(&adc_dev);
cdev_add_err:
	unregister_chrdev_region(dev_num,1);
alloc_chrdev_region_err:
	return ret;
}

static void __exit adc_driver_exit(void)
{
	iounmap(REG_ADCCON);
	iounmap(REG_ADCDAT0);
	iounmap(REG_CLKCON);
	device_destroy(p_class,dev_num);
	class_destroy(p_class);
	cdev_del(&adc_dev);

	unregister_chrdev_region(dev_num,1);
	printk("adc_driver_exit ok\n");
}


module_init(adc_driver_init);

module_exit(adc_driver_exit);

MODULE_LICENSE("GPL");
相关推荐
sinat_384241093 小时前
使用 npm 安装 Electron 作为开发依赖
服务器
朝九晚五ฺ3 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
自由的dream4 小时前
Linux的桌面
linux
xiaozhiwise4 小时前
Makefile 之 自动化变量
linux
Kkooe5 小时前
GitLab|数据迁移
运维·服务器·git
久醉不在酒5 小时前
MySQL数据库运维及集群搭建
运维·数据库·mysql
意疏6 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
虚拟网络工程师6 小时前
【网络系统管理】Centos7——配置主从mariadb服务器案例(下半部分)
运维·服务器·网络·数据库·mariadb
BLEACH-heiqiyihu6 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器