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中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

Redis 基本数据类型和使用详解

《Redis基本数据类型和使用详解》String是Redis最基本的数据类型,一个键对应一个值,它的功能十分强大,可以存储字符串、整数、浮点数等多种数据格式,本文给大家介绍Redis基本数据类型和... 目录一、Redis 入门介绍二、Redis 的五大基本数据类型2.1 String 类型2.2 Hash

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Redis中Hash从使用过程到原理说明

《Redis中Hash从使用过程到原理说明》RedisHash结构用于存储字段-值对,适合对象数据,支持HSET、HGET等命令,采用ziplist或hashtable编码,通过渐进式rehash优化... 目录一、开篇:Hash就像超市的货架二、Hash的基本使用1. 常用命令示例2. Java操作示例三