news 2026/9/8 18:49:41

GEC6818传感器驱动实战:DHT11温湿度与MQ-2烟雾检测ko模块开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GEC6818传感器驱动实战:DHT11温湿度与MQ-2烟雾检测ko模块开发

简介:面向基于GEC6818开发板做毕业设计的电子、嵌入式方向学生,这套驱动资源覆盖温湿度、红外、超声波、步进电机、继电器、光敏、烟雾火焰、ADC等常用外设模块,基本满足智能家居、环境监测类项目的底层驱动需求。压缩包共含116个文件,大小约21.38MB,以.c源码、.ko驱动模块、Makefile编译脚本为主体,另有接线说明txt、原理图PDF及测试程序等,便于直接加载或二次修改后再用arm-linux-gcc交叉编译移植。每类传感器均配有驱动源码与编译产物,省去在虚拟机中反复配置内核环境的步骤,配合文档中的接线说明可快速验证硬件通路。目前已有2607人学习使用,对初次接触GEC6818驱动开发、希望缩短毕设调试周期的读者有较强参考价值。 GEC6818是我用过一段时间的开发板,三星S5P6818八核A53方案,性能在国产板卡里算能打的,做毕设选它很常见。但真正让人头疼的不是板子本身,而是驱动。很多同学第一次在GEC6818上做温湿度、烟雾传感器的时候,卡在同一个地方:代码写好了,交叉编译也过了,但insmod加载就是报错,或者加载成功却读不到数据。这篇文章我把自己调通的DHT11温湿度驱动、MQ-2烟雾驱动ko文件、源文件以及最关键的接线说明整个捋一遍,适合正在做嵌入式Linux毕设、或者刚接触GEC6818字符设备驱动开发的同学直接参考。

1. 开发环境搭建与整体思路确认

1.1 为什么需要先确认内核版本和交叉编译器

GEC6818出厂一般自带Linux 3.4.x或4.4.x的内核(不同批次有差异),而我们要编译ko驱动,最核心的前提是:编译ko所用内核源码的版本,必须和板子上正在运行的内核版本完全一致,包括编译器版本和内核配置选项都要对齐。否则insmod时大概率会遇到invalid module format或者一堆unknown symbol

我踩过的坑是:一开始图省事,直接用板子厂家提供的Ubuntu 16.04虚拟机,里面已经装好了arm-linux-gnueabi-工具链,但内核源码是另一个版本。第一次insmod DHT11驱动直接报错,查了半小时,发现版本号一个带+一个不带。解决办法很简单,下载与板子运行内核同版本的源码,在源码根目录先做一次默认配置,再编译内核自带的模块目录,让Module.symversbuild软链接正常生成,然后才编译自己的ko。

实际操作时建议按下面这套流程走:

  1. 在板子上执行uname -r,把内核版本号记下来,例如3.4.39-s5p6818
  2. 从厂家提供的光盘或网盘里拿到对应的内核源码压缩包,解压到Ubuntu,建议路径用/home/你的用户名/kernel/linux-3.4.39之类,不要放桌面或带中文的路径。
  3. 安装工具链,GEC6818一般用arm-linux-gnueabi-gcc(4.8.3版本左右)。安装完执行arm-linux-gnueabi-gcc -v确认版本。
  4. 进入内核源码目录,执行make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- distclean清理,然后执行make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- s5p6818_defconfig(有的厂家给的是tiny4412_defconfig或者自定义文件,看实际提供情况),最后执行make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules预编译一遍内核自带的模块。

之所以要先编译一遍内核自带模块,是为了生成完整的Module.symvers文件,里面记录着导出符号的crc值。我们自己写的驱动如果依赖内核导出的函数(比如gpio_requestclass_create等),依赖检查靠的就是它。没有这个文件,交叉编译过程本身可能不报错,但加载到板子上就是一连串的unknown symbol

1.2 驱动框架怎么选:直接ko比内核编译更省事

毕设场景下,不建议为了加两个传感器就去重新编译整个内核镜像,然后烧录到板子里。开发效率最高的做法是:把驱动编译成可加载模块(.ko),通过insmod动态加载。

这样做有几个好处:第一,修改驱动代码后只需要重新make,拷贝ko文件到板子,insmod替换,不用反复烧写系统;第二,驱动都在应用层测试,崩溃了直接rmmod再insmod,系统不会死;第三,答辩演示的时候可以现场insmod、lsmod、rmmod,展示往往更直观。

我用的驱动框架是标准的字符设备驱动:分配设备号(动态)、初始化cdev、创建设备类、生成设备节点。上层应用通过open/read接口读传感器数据,方式为把传感器的原始电平或转换后的计算逻辑放到驱动read里实现。

2. 源码结构和文件说明

2.1 源码文件清单

我这套代码整理下来主要包含下面这些文件,可以参考:

  • dht11.c:DHT11驱动源文件,字符设备驱动框架,单总线时序解析。
  • mq2.c:MQ-2烟雾传感器驱动源文件,读取ADC或GPIO高低电平状态。
  • Makefile:与上面两个源文件配套,通过obj-m方式编译。
  • test_dht11.ctest_mq2.c:应用层测试程序,分别对应温湿度和烟雾读取。
  • readme.txt:接线和编译步骤速查。

dht11.c里我把传感器型号对应的名字、GPIO引脚号、延时函数全部用宏定义写在文件头部,比如#define DHT11_PIN S5P6818_GPIO_C(12)。GEC6818的GPIO编号方式和IMX系列、STM32MP1不一样,它用的是S5P6818_GPIO开头的一系列宏,分别对应GPIOA到GPIOK多组引脚。查阅原理图或者底板丝印之后,把实际接到传感器的引脚换算成这样的宏,写到宏定义里即可。这块换算如果搞不明白,建议直接看厂家提供的gpio-cfg.h头文件,里面对每个引脚的bank偏移和number定义得很清楚。

2.2 Makefile组织方式

Makefile看起来很简单,但容易出错。核心内容如下:

obj-m += dht11.o obj-m += mq2.o KDIR := /home/你的用户名/kernel/linux-3.4.39 PWD := $(shell pwd) all: make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules clean: make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- clean

很多同学会问,为什么make -C $(KDIR)进入内核目录之后,还要加M=$(PWD)?因为M=$(PWD)告诉kbuild系统,我要编译的是外部模块,目录是当前路径。少了这个参数,make会去编译整个内核;加上之后,kbuild只处理当前目录下的obj-m目标。

ARCH=armCROSS_COMPILE=arm-linux-gnueabi-这两个参数必须写。不写ARCH的话,kbuild有可能跑到x86架构的配置里;不写CROSS_COMPILE的话,用的就是gcc而不是arm交叉编译工具,生成的可执行文件在PC上能跑,但在arm板上会提示Exec format error。

3. DHT11温湿度驱动解析

3.1 DHT11单总线时序与编程要点

DHT11是单总线数字温湿度传感器,只有一根数据线。它靠一组严格的时序协议工作:主机先拉低总线至少18ms,然后再拉高20~40us,之后释放总线,等待DHT11的响应。响应信号是DHT11拉低80us左右,再拉高80us左右。响应之后,DHT11连续发送40位数据:8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。

每一位数据的判断,是利用高电平持续的时间长短区分的:50us的低电平时钟信号后面,如果高电平持续26~28us左右,表示0;高电平持续70us左右,表示1。上拉电阻和线路电容对时序有一定影响,实测发现Ubuntu里交叉编译时没有太大问题,但在板子上,系统调度可能会产生微秒级误差,导致读出来的数据全是0xFF或者直接reset掉。

为了尽量稳定,我在驱动代码中关闭了内核抢占,并且在关键引脚操作段使用preempt_disablelocal_irq_save来锁住中断,具体实现思想是这样的:

static int dht11_read_raw(int *humidity, int *temperature) { int err = 0; unsigned long flags; int bits[40] = {0}; int i = 0; /* 请求GPIO并设置为输出方向 */ gpio_direction_output(DHT11_PIN, 0); local_irq_save(flags); /* 主机拉低至少18ms */ gpio_set_value(DHT11_PIN, 0); mdelay(20); /* 拉高 20~40us */ gpio_set_value(DHT11_PIN, 1); udelay(30); /* 释放总线,切回输入 */ gpio_set_value(DHT11_PIN, 1); gpio_direction_input(DHT11_PIN); /* 等待DHT11拉低响应信号,超时则失败 */ while (gpio_get_value(DHT11_PIN)) { if (++i > 10000) return -ETIMEDOUT; } /* 跳过80us低电平和80us高电平 */ while (!gpio_get_value(DHT11_PIN)) {} while (gpio_get_value(DHT11_PIN)) {} /* 读取40位数据,每位先等50us低电平,再读高电平持续时间 */ for (i = 0; i < 40; i++) { while (!gpio_get_value(DHT11_PIN)) {} udelay(40); bits[i] = gpio_get_value(DHT11_PIN); while (gpio_get_value(DHT11_PIN)) {} } local_irq_restore(flags); /* 组合成字节,做校验和检查 */ /* ... */ return err; }

这段代码里有一个经验点:读每位数据时,先等到低电平结束,再延时40us左右去判高电平,如果在40us之后依然是高,说明这是一个1;如果已经变低,说明是0。这个采样点在不同板子上可能需要微调,我调试的时候用逻辑分析仪抓过波形,最后确定40us这个值在GEC6818上最稳妥。如果板子主频环境不一样,可以小范围尝试35~50us。

3.2 GPIO编号换算和接线注意

DHT11接线非常简单,一路VCC接3.3V,另一路GND接GND,数据脚接GPIO。需要注意的是:

  • 如果面包板上用5V供电模块,数据脚高电平是5V,而GEC6818的GPIO最高只能承受3.3V,建议加电平转换或者串联一个1K电阻分压再接入引脚。
  • 数据线和VCC之间最好接一个4.7K上拉电阻。DHT11模块一般已经自带,如果是裸传感器,必须自己加。

GPIO号换算方面,在驱动程序里写的是S5P6818_GPIO_C(12)这样的形式,那说明接的是GPIOC组的第12个引脚。具体丝印上可能直接标GPIO_C12或者类似,对应核心板上的物理引脚位置需要查底板原理图。动手前用万用表量一下引脚和GND之间的电压,确认没接反就好。

3.3 上层应用怎么读

上层应用是通过文件节点来读的。insmod之后,如果设备号是动态分配的,再加上class_create并自动生成设备节点,/dev/dht11应该是自动出现的。应用层代码大概长这样:

#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main(void) { int fd; char buf[32] = {0}; int ret; fd = open("/dev/dht11", O_RDWR); if (fd < 0) { perror("open"); return -1; } while (1) { ret = read(fd, buf, sizeof(buf)); if (ret > 0) { printf("data: %s\n", buf); } sleep(2); } close(fd); return 0; }

驱动里的read函数把温湿度格式化成字符串,例如"humidity=58.0% temp=25.0C",拷到buf里,返回给应用层。这样做的好处是应用层不用关心协议解析,只需要打印字符串,毕设答辩时好用。

4. MQ-2烟雾传感器驱动解析

4.1 MQ-2模块的两种输出方式

MQ-2是半导体气敏传感器,对液化气、丙烷、氢气、烟雾都比较敏感。模块上一般有两路输出:一路AO模拟电压输出,另一路DO数字电平输出(TTL)。DO输出的阈值可以通过板载电位器调节,当气体浓度超过阈值时,DO输出电平翻转。

针对这两种输出,驱动写起来是完全不同的思路:

  • DO模式:当作数字输入,用gpio_get_value读取,应用层只需要判断有没有超过阈值。
  • AO模式:需要接到GEC6818的ADC通道,驱动里要用到ADC子系统或平台自带的ADC控制器。

毕设最简方案是优先用DO,代码量小,而且演示时把打火机的气体凑近传感器,LED或者蜂鸣器状态变化很明显,效果比较直观。AO模式的好处是可以量化浓度趋势,但GEC6818的ADC接口资源和设备树配置情况因厂商资料而异,调试成本更高。

4.2 基于GPIO的烟雾检测驱动

如果用DO输出,驱动基本逻辑和按键驱动类似,区别在于不需要防抖,因为传感器输出本身就是缓慢变化的。关键代码:

static int mq2_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { int value; char kbuf[16]; int ret; value = gpio_get_value(MQ2_PIN); if (value == 0) snprintf(kbuf, sizeof(kbuf), "smoke detected"); else snprintf(kbuf, sizeof(kbuf), "normal"); ret = copy_to_user(buf, kbuf, strlen(kbuf) + 1); return ret < 0 ? -EFAULT : strlen(kbuf) + 1; }

这里注意一个点:MQ-2模块上的DO输出,有的传感器浓度高时输出低电平,有的是高电平,取决于模块上比较器的接法。启动后可以先运行应用层程序观察,看到底是高有效还是低有效。我的代码注释里特别标明了默认定义为低电平有效,实际使用时根据自己模块调整这一个判断条件就行。

4.3 ADC方式读取

如果坚持要用AO模式,GEC6818自带ADC控制器,但不同厂家的BSP对ADC的支持程度不同。正常情况下需要通过平台提供的ADC驱动申请一个channel,然后读取原始值。原始值是12bit,取值范围0~4095,对应0~1.8V(部分底板可能是0~3.3V)。气体浓度越高,电压越大。

如果不熟悉内核的IIO子系统,可以直接在应用层访问/sys/bus/iio/devices/iio:device0/in_voltage0_raw这样的节点来读取电压原始值。这种方法有时更快更方便,而且在应用层做换算灵活,缺点是采样率上不去,但对烟雾检测来说足够了。

5. 接线说明、ko文件加载与测试

5.1 接线速查表

这里把自己实测的接线方式整理成表,方便电工和软件不同背景的同学对照:

传感器引脚GEC6818对应位置说明
DHT11 VCC3.3V引脚不能接5V,数据线会超压
DHT11 GNDGND共地必须接
DHT11 DATAGPIOC12(如GPIO_C12丝印)模块自带4.7K上拉则无需外接
MQ-2 VCC5V引脚MQ-2加热丝需要5V供电
MQ-2 GNDGND与板卡共地
MQ-2 DOGPIOB5(如GPIO_B5丝印)根据自己DM修改宏定义
MQ-2 AO悬空(若用DO模式)如需ADC则接ADC通道

接线之前一定先把板子断电,不要在运行状态下插拔杜邦线。我曾经在板子运行的时候插MQ-2,结果5V串到GPIO上,把那一路GPIO烧了,后面只能换引脚,非常折腾。

5.2 把ko文件传到开发板并加载

编译完成后,当前目录下会生成dht11.komq2.ko,用U盘、网络或者adb推送到板子任意目录(我习惯放在/home/gec/ko下)。然后执行:

insmod dht11.ko insmod mq2.ko

如果一切正常,lsmod能看到这两个模块,ls /dev/dht11ls /dev/mq2也能看到对应的设备节点。设备节点没有自动生成时,可以在驱动源码里检查是否调用了device_create,然后手动用mknod /dev/dht11 c 主设备号 0创建。

接着编译并运行测试程序:

arm-linux-gnueabi-gcc -o test_dht11 test_dht11.c ./test_dht11

看到类似humidity=58.0% temp=25.0C的输出,说明驱动从下面到上面已经全部调通。这时候再进行烟雾测试,拿一个打火机出气口远离传感器,放气,观察DO对应的打印信息变化。

5.3 开机自动加载

如果希望板子启动时自动加载ko,两个方式:

  1. 最简单的方法:在/etc/init.d/下的启动脚本里加两行insmod命令。
  2. 标准化做法:把ko拷贝到/lib/modules/3.4.39-s5p6818/目录,执行depmod -a,然后编辑/etc/modules添加模块名。

第一种方法对毕设来说足够了,简单直接,不会影响系统原有启动过程。

6. 常见问题与排查技巧实录

6.1 insmod失败:invalid module format

最常见的问题,没有之一。原因几乎都是内核版本或者编译环境不一致。排查方法:

  • modinfo dht11.ko查看vermagic。
  • 板子上执行cat /proc/version查看运行内核版本和编译工具链。
  • 两者不一致时,回到内核源码目录重新make并重新编译ko。

注意有些厂家的内核源码在上层加了少量补丁,从uname -r看版本号相同还不够,对不上extra version也会报错。建议完全用厂家配套的源码和工具链。

6.2 insmod成功但read不到数据

DHT11读不到数据,大部分是GPIO编号换算错了。可使用命令在板子上手动导出GPIO进行测试。另外,也有可能是延时不能太严苛导致时序问题,尝试调整udelay(40)的值。还有一种典型情况是接线太长,杜邦线超过20cm时,DHT11通信极不稳定,尽量缩短数据线距离。

6.3 MQ-2一直输出正常或一直报警

这个基本是和DO有效电平搞反了。反过来判断即可。同时如果打算用AO模式,检查模块供电是否达到了5V,MQ-2的加热电阻需要一定时间预热,第一次上电大概两三分钟内输出不稳定,等稳定后再测试。

6.4 卸载模块提示Device or resource busy

说明设备文件还在被打开。先关闭正在运行的测试程序,再执行rmmod dht11。如果还是busy,检查是不是有别的进程占用,执行fuser /dev/dht11可以查看占用进程PID。

7. 一点扩展想法

GEC6818的驱动开发套路摸熟之后,后面接其他传感器就顺手很多。比如想加一个OLED屏显示温湿度和烟雾状态,可以复用这里的字符设备框架,把显示驱动也做成ko,应用层统一read数据再write到OLED节点,整个系统的层次一下子就清晰了。也有人在这块板子上继续做Linux网络编程,把温湿度烟雾数据通过socket上报到上位机或云平台,这一套毕设的完整度会很高。驱动是没那么多玄学的,反复看官方头文件,反复用逻辑分析仪对比时序,问题基本都能定位。我做这套东西时最大的体会是:先把环境对齐做扎实,后面的路就顺畅了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 18:46:42

Flink基础之Flink on Yarn原理详解:三种模式与提交流程

摘要 讲透 Flink 跑在 YARN 上的完整原理&#xff1a;YARN 核心概念与 Flink 角色映射、Session/Per-Job/Application 三种运行模式的差异与选型、Application 模式下从上传 JAR 到 TaskManager 启动的完整提交流程、Container 与 Slot 的两层资源模型&#xff0c;并给出容错机…

作者头像 李华
网站建设 2026/9/8 18:46:39

TBOX信息安全系列3需求篇-车企信息安全需求对比

做TBOX项目&#xff0c;你第一个拿到的不是原理图&#xff0c;而是一份客户的网络安全需求规范。很多工程师看到几十页的"应/应该/可能"就头大&#xff0c;不知道从哪下手。这篇用两份真实的OEM需求规范做对标——某自主品牌&#xff08;33页&#xff09;和某合资品牌…

作者头像 李华
网站建设 2026/9/8 18:45:44

WorkBuddy实操指南:AI智能体如何自动化周报汇总与多维表同步

上周五下午&#xff0c;我差点又被钉钉群里的“周报接龙”给淹没了。十几个同事把各自的周报往群里一甩&#xff0c;我整理汇总&#xff0c;照着以往的速度&#xff0c;这一趴怎么也得花上四十分钟。但那天我用了不到十分钟就收拾完了——不是手下多了人&#xff0c;也不是手速…

作者头像 李华
网站建设 2026/9/8 18:45:20

MoneyPrinterTurbo:开源AI短视频自动化生产全攻略

简介&#xff1a;这是一份面向短视频创作与AI应用开发者的视频生成器源码包&#xff0c;基于MoneyPrinterTurbo实现&#xff0c;只需输入主题或关键词&#xff0c;即可自动完成文案、素材、字幕、背景音乐并合成高清短视频。压缩包共80个文件&#xff0c;约111MB&#xff0c;以…

作者头像 李华
网站建设 2026/9/8 18:44:34

昇思大模型推理服务生命周期管理:从状态机到优雅下线实践

刚开始做昇思大模型推理服务的时候&#xff0c;我栽得最狠的一次就是在“升级模型版本”。当时想法很简单&#xff1a;新模型推理代码写好&#xff0c;直接把服务停了&#xff0c;换权重&#xff0c;再启动。结果呢&#xff1f;服务中断了将近十分钟&#xff0c;线上积压的推理…

作者头像 李华