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

news2024/9/27 17:28:06

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.1864

		PPM = ((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;
}

//采用读启动的方式启动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");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2089343.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例2

C(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例2 文章目录 C(Qt)-GIS开发-QGraphicsView显示瓦片地图简单示例21、概述2、实现效果3、主要代码4、源码地址 更多精彩内容&#x1f449;个人内容分类汇总 &#x1f448;&#x1f449;GIS开发 &#x1f448; 1、概述 支持多线程…

Android 事件分发:为什么有时候会出现事件冲突?事件的顺序是如何的?出现事件冲突如何解决呢?比如为什么左右可以滑动,而上下却不行?

目录&#xff1a; 一、为什么要学习事件呢&#xff1f; 1.在开发复杂的应用时&#xff0c;经常需要处理复杂的用户交互逻辑。学习事件分发机制可以帮助你更好地控制事件的传递和处理流程&#xff0c;从而解决一些复杂的交互问题&#xff0c;如滑动冲突、点击穿透等。 2.面试需…

NLP笔记:BLEU

1 介绍 bleu是一种文本评估算法&#xff0c;它是用来评估机器翻译跟专业人工翻译之间的对应关系核心思想就是机器翻译越接近专业人工翻译&#xff0c;质量就越好&#xff0c;经过bleu算法得出的分数可以作为机器翻译质量的一个指标 2 BLEU原理 2.1 N-gram BLEU采用了N-gram…

NLP(三):词向量

自然语言处理&#xff0c;处理的是自然的需要&#xff0c;通过分词后得到我们想要的词&#xff0c;但是不可能直接把这种自然语言传递给计算机来理解。这时候就有一个概念叫词向量&#xff0c;用来表示词的特征向量或表征。 一&#xff0c;词向量的表示 词向量的表示主要有两…

C——四种排序方法

这一篇文章我将要详细讲解四种排序方法 1.冒泡排序 冒泡排序是我们首先接触的排序方法&#xff0c;他通过两次循环完成。 /*冒泡排序*/&#xff08;升序&#xff09; void maopao(int *a,int n) {int i;for(i 0; i < n-1; i){for (int j 0; j < n - 1 - i; j){if (a[…

mars3D使用 POI 查询、限定范围

mars3D使用 一、mars3D中使用 geocoder 进行 POI 查询二、限定范围1.初始化时渲染2.重新渲染 总结 一、mars3D中使用 geocoder 进行 POI 查询 在json文件或者自己的mapOptions中配置token "token":{"tianditu":"e5c3984ced09bc1f55e8e1107fdc5a6b&q…

论文速览【LLM-agent】—— 【ReAct】Synergizing Reasoning and Acting in Language Models

文章链接&#xff1a;ReAct: Synergizing Reasoning and Acting in Language Models发表&#xff1a;ICLR 2023领域&#xff1a;LLM agent 摘要&#xff1a;尽管大型语言模型&#xff08;LLMs&#xff09;在语言理解和交互式决策任务中展示了令人印象深刻的能力&#xff0c;但它…

正弦波振荡器工作原理及频率稳定性条件

晶发电子专注17年晶振生产,晶振产品包括石英晶体谐振器、振荡器、贴片晶振、32.768Khz时钟晶振、有源晶振、无源晶振等&#xff0c;产品性能稳定,品质过硬,价格好,交期快.国产晶振品牌您值得信赖的晶振供应商。 正弦波振荡器是一种能够自动将直流电转换为特定频率和振幅的正弦交…

解析云原生架构中两大核心原则

1.云原生架构是什么 云原生架构是一种设计和构建应用程序的现代方法&#xff0c;以微服务、容器化、持续集成和持续部署&#xff08;CI/CD&#xff09;等技术为基础&#xff0c;使应用能够在云环境中动态运行。云原生架构强调解耦合、弹性和自动化&#xff0c;开发团队在独立的…

【高级编程】万字整理集合框架 迭代器 泛型(含方法案例)

文章目录 集合框架集合接口集合类ArrayListLinkedListHashSet 迭代器 IteratorMap 接口泛型Collections 工具类 集合框架 如果并不知道程序运行时会需要多少对象&#xff0c;或者需要更复杂方式存储对象——可以使用Java集合框架 Java集合框架提供了一套性能优良、使用方便的…

@EqualsAndHashCode注解使用

一&#xff0c;EqualsAndHashCode注解来自于Lombok EqualsAndHashCode 是 Lombok 库提供的一个注解&#xff0c;用于自动生成 equals 和 hashCode 方法。这两个方法在 Java 中非常重要&#xff0c;特别是在集合框架中使用时&#xff0c;它们确保了对象的正确比较和哈希值的一致…

YOLOv8改进 | 注意力篇 | YOLOv8引入LSK注意力机制

1. LSK介绍 1.1 摘要: 最近关于遥感目标检测的研究主要集中在改进定向边界框的表示上,但忽略了遥感场景中呈现的独特先验知识。 这种先验知识可能很有用,因为在没有参考足够远距离上下文的情况下,可能会错误地检测微小的遥感物体,并且不同类型物体所需的远距离上下文可能…

HarmonyOS开发实战( Beta5版)优化实践/合理使用缓存提升性能

简介 随着应用功能的日益丰富与复杂化&#xff0c;数据加载效率成为了衡量应用性能的重要指标。不合理的加载策略往往导致用户面临长时间的等待&#xff0c;这不仅损害了用户体验&#xff0c;还可能引发用户流失。因此&#xff0c;合理运用缓存技术变得尤为重要。 系统提供了P…

uniapp组件用法

一. 什么是组件,有什么好处? 在uni-app中&#xff0c;组件是构成应用的基本单位&#xff0c;它们是用来定义用户界面的一部分&#xff0c;并且通常包含了视图和逻辑。组件的设计使得开发者能够以声明式的方式构建应用界面&#xff0c;并且通过组件化的开发方式来提高代码的复…

损失函数、成本函数cost 、最大似然估计

一、损失函数 什么是损失函数&#xff1f; 【深度学习】一文读懂机器学习常用损失函数&#xff08;Loss Function&#xff09;-腾讯云开发者社区-腾讯云 损失函数&#xff08;loss function&#xff09;是用来估量模型的预测值f(x)与真实值Y的不一致程度&#xff0c;它是一个…

Python 新手必看:如何用 unittest 写出高质量代码?

文末赠免费精品编程资料~~ 在 Python中 &#xff0c;unittest 模块是进行单元测试的强大工具。无论你是初学者还是有经验的开发者&#xff0c;单元测试都是确保代码质量的重要一环。而 unittest 模块就是让这一过程变得简单、快捷的利器。 什么是单元测试&#xff1f; 在进入…

浩瀚麦克风怎么样?西圣、罗德、神牛领夹麦克风全网巅峰PK测评

​一台优质专业的无线领夹麦克风能够清晰、稳定地收录声音&#xff0c;提升音频录制质量。而劣质的无线领夹麦克风则可能出现声音不清晰、信号不稳定、续航短等各种问题。作为一名资深的数码测评师&#xff0c;我已经测评过了好几十款无线领夹麦克风&#xff0c;今天将从麦克风…

Python应用指南:获取高德地铁站点数据(单城市版)

书接上文&#xff0c;上篇文章是一次性下载全国所以城市的地铁站点数据&#xff0c;但是可视化的过程需要手动把换乘站给一个个复制出来分配到其他各个经过的线路&#xff0c;还需要核对站点顺序不能出错&#xff0c;如果只需要单个城市的数据呢&#xff1f;另外能不能直接生成…

【复杂系统系列(初级)】自动调节动态平衡模型——生物体的稳态机制

【通俗理解】自动调节动态平衡模型——生物体的稳态机制 关键词提炼 #自动调节 #动态平衡 #生物体稳态 #反馈机制 #体温调节 #微分方程模型 第一节&#xff1a;自动调节动态平衡模型的类比与核心概念 1.1 自动调节动态平衡模型的类比 自动调节动态平衡模型可以被视为生物体…

grpc-spring 通信(监控视频传输)

先看效果 这是微软相机&#xff0c;22ms延迟 &#xff08;不走网络存粹寄存器和内存的通信&#xff09;这是程序抓取摄像头然后传给client&#xff0c;client的java窗口展示的&#xff0c;延时也是22ms&#xff08;对了localhost好像也不走网络吧&#xff09; 几个点 1.openc…