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

2024-08-29 20:28

本文主要是介绍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;

#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.1864PPM = ((RS/12.0)-0.1864)/(-0.0003);printf("C = %f\n",PPM);sleep(1);}
}

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

#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;
}//采用读启动的方式启动adcstatic 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_deviceret = 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 classp_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");

这篇关于Linux下的使用字符设备驱动框架编写ADC驱动 ——MQ-4传感器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1118809

相关文章

一文详解如何使用Java获取PDF页面信息

《一文详解如何使用Java获取PDF页面信息》了解PDF页面属性是我们在处理文档、内容提取、打印设置或页面重组等任务时不可或缺的一环,下面我们就来看看如何使用Java语言获取这些信息吧... 目录引言一、安装和引入PDF处理库引入依赖二、获取 PDF 页数三、获取页面尺寸(宽高)四、获取页面旋转角度五、判断

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定

golang程序打包成脚本部署到Linux系统方式

《golang程序打包成脚本部署到Linux系统方式》Golang程序通过本地编译(设置GOOS为linux生成无后缀二进制文件),上传至Linux服务器后赋权执行,使用nohup命令实现后台运行,完... 目录本地编译golang程序上传Golang二进制文件到linux服务器总结本地编译Golang程序

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录