简介:面向基于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.symvers和build软链接正常生成,然后才编译自己的ko。
实际操作时建议按下面这套流程走:
- 在板子上执行
uname -r,把内核版本号记下来,例如3.4.39-s5p6818。 - 从厂家提供的光盘或网盘里拿到对应的内核源码压缩包,解压到Ubuntu,建议路径用
/home/你的用户名/kernel/linux-3.4.39之类,不要放桌面或带中文的路径。 - 安装工具链,GEC6818一般用
arm-linux-gnueabi-gcc(4.8.3版本左右)。安装完执行arm-linux-gnueabi-gcc -v确认版本。 - 进入内核源码目录,执行
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_request、class_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.c、test_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=arm和CROSS_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_disable加local_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 VCC | 3.3V引脚 | 不能接5V,数据线会超压 |
| DHT11 GND | GND | 共地必须接 |
| DHT11 DATA | GPIOC12(如GPIO_C12丝印) | 模块自带4.7K上拉则无需外接 |
| MQ-2 VCC | 5V引脚 | MQ-2加热丝需要5V供电 |
| MQ-2 GND | GND | 与板卡共地 |
| MQ-2 DO | GPIOB5(如GPIO_B5丝印) | 根据自己DM修改宏定义 |
| MQ-2 AO | 悬空(若用DO模式) | 如需ADC则接ADC通道 |
接线之前一定先把板子断电,不要在运行状态下插拔杜邦线。我曾经在板子运行的时候插MQ-2,结果5V串到GPIO上,把那一路GPIO烧了,后面只能换引脚,非常折腾。
5.2 把ko文件传到开发板并加载
编译完成后,当前目录下会生成dht11.ko和mq2.ko,用U盘、网络或者adb推送到板子任意目录(我习惯放在/home/gec/ko下)。然后执行:
insmod dht11.ko insmod mq2.ko如果一切正常,lsmod能看到这两个模块,ls /dev/dht11和ls /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,两个方式:
- 最简单的方法:在
/etc/init.d/下的启动脚本里加两行insmod命令。 - 标准化做法:把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上报到上位机或云平台,这一套毕设的完整度会很高。驱动是没那么多玄学的,反复看官方头文件,反复用逻辑分析仪对比时序,问题基本都能定位。我做这套东西时最大的体会是:先把环境对齐做扎实,后面的路就顺畅了。
本文还有配套的精品资源,点击获取