day72 传感器分类、关键参数、工作原理与Linux驱动开发(GPIO/I²C/Platform/Misc框架)
一、传感器分类与关键参数
(一)按通信协议分类
| 通信协议 | 对应传感器 | 功能 |
|---|---|---|
| GPIO(单总线) | dht11、dht22、ds18b20 | dht11/dht22:温湿度检测;ds18b20:温度检测 |
| GPIO | hcsr-04 | 超声波测距 |
| I²C | LM75、BH1750、MPU6050、MAX30100/30102 | LM75:温度;BH1750:光照强度;MPU6050:六轴姿态;MAX30100/30102:心率、血氧 |
| SPI | ADXL345 | 三轴加速度检测 |
| ADC | MQ系列(MQ-2、MQ-135、MQ-7) | MQ-2:烟雾浓度;MQ-135、MQ-7:气体浓度 |
| UART | GPS传感器、GY-35 | GPS:定位(经度、纬度、海拔);GY-35:红外测距 |
✅ 面试重点:必须清楚所用传感器的接口类型、通信协议(如I²C地址、SPI模式)、引脚连接方式。
(二)核心传感器关键参数表
| 传感器 | 功能 | 连接方式 | 量程(测量范围) | 精度 | 分辨率 | 工作电压 |
|---|---|---|---|---|---|---|
| dht11 | 温湿度 | GPIO(单总线) | 温度:0--50℃;湿度:20--90%RH | 温度:±2℃;湿度:±5%RH | 1 | 3.3V--5.5V |
| dht22 | 温湿度 | GPIO(单总线) | 温度:-40--80℃;湿度:0--99.9%RH | 温度:±0.5℃;湿度:±2%RH | 0.1℃ / 0.1%RH | 3.3V--5.5V |
| ds18b20 | 温度 | GPIO(单总线) | -55--125℃ | 0--85℃:±0.5℃(典型);全范围:±2℃ | 9位:0.5℃;10位:0.25℃;11位:0.125℃;12位:0.0625℃ | 3V--5V |
| hcsr-04 | 超声波测距 | GPIO | 2cm--450cm | ±0.3cm | 1 | 5V |
| MAX30102 | 心率、血氧 | I²C | 心率:30--240 BPM;血氧:70--100% | ±2% | - | 1.8V--5.5V |
⚠️ 选型建议:根据项目场景(如工业设备高温环境)选择合适量程的传感器。
二、典型传感器工作原理与时序
(一)dht11温湿度传感器
- 电路图:VCC接电源,通过4.7K电阻R1连接DATA引脚,GND接地,NC为空脚。
- 数据格式:共40bit(5字节),顺序为湿度整数+湿度小数+温度整数+温度小数+校验和,高位先行。
- 工作时序 :
- 主机发送起始信号:DHT11引脚默认高电平,主机拉低至少18ms,再拉高20--40μs。
- 从机回复应答:拉低80μs,再拉高80μs。
- 传输bit'0':从机拉低50μs,再拉高26--28μs。
- 传输bit'1':从机拉低50μs,再拉高70μs。
- 结束信号:主机将引脚从低电平拉为高电平。
(二)hcsr-04超声波测距传感器
-
引脚定义:VCC(5V)、GND(地)、Trig(控制端/发射端)、Echo(接收端)。
-
工作原理/时序 :
-
Trig引脚默认低电平,拉高 ≥10μs 后拉低 → 传感器内部发送8个40kHz超声波脉冲。
-
Echo引脚默认低电平,检测到高电平时记录开始时间,检测到低电平时记录结束时间。
-
距离计算公式:
距离(cm) = (高电平时间 μs × 340 m/s) / (2 × 10000) = 高电平时间(μs) / 58
-
-
IMX6ULL硬件连接 :
- VCC → P4.46(5V)
- GND → P4.47(地)
- Trig → SNVS_TAMPER4(P4.1,即GPIO5_IO04)
- Echo → SNVS_TAMPER5(P4.6,即GPIO5_IO05)
🔧 注意:必须使用5V供电,3.3V可能导致工作异常。
(三)MAX30102心率血氧传感器
- 引脚说明
| 引脚 | 功能 |
|---|---|
| VIN | 电源输入(1.8V--5.5V) |
| SCL | I²C时钟线 |
| SDA | I²C数据线 |
| INT | 中断引脚 |
| IRD | 红外LED_DRIVER |
| RD | 红色LED_DRIVER |
| GND | 地 |
-
核心特性:
- 集成红外LED(940nm)、红光LED(660nm)、光电探测器;
- 基于PPG(Photoplethysmography,光电容积图)原理;
- 16位ADC;
- I²C接口(默认地址0x57);
- 可配置LED电流、采样率、FIFO阈值。
-
PPG采集原理:
- 光线照射人体组织,动脉血随心脏搏动膨胀收缩,导致吸收光强变化;
- 光电探测器捕捉光强变化并转化为电信号;
- 红光与红外光配合用于SpO₂计算和心率检测。
-
信号处理流程:
- 光发射:控制红/红外LED发射特定波长光;
- 光信号采集:光电探测器 → AFE放大/滤波 → ADC转换;
- 数字信号处理:
- 带通滤波(0.5--5Hz)去噪;
- AC/DC分量分离;
- 峰值检测 → 计算心率;
- AC/DC比值 → 查表得血氧值。
三、Linux驱动开发框架
(一)Linux驱动分类
-
字符型设备驱动:
- 以1字节为单位读写,无缓存;
- 适用于鼠标、键盘、传感器等;
- 需实现
cdev结构体(设备号、名称)和file_operations(open/read等)。
-
块设备驱动:
- 以固定块(如512B)为单位,带缓存;
- 适用于硬盘、Flash。
-
网络设备驱动:
- 基于TCP/IP协议栈。
(二)字符型设备驱动框架
-
手动创建设备节点:
bashmknod /dev/hcsr04 c 250 0 # c=字符设备,250=主设备号,0=次设备号 -
自动创建设备节点:
- 使用
class_create()创建类; - 使用
device_create()创建设备节点。
- 使用
-
init函数(insmod触发):
- 注册字符设备:
alloc_chrdev_region()(动态)或register_chrdev_region()(静态); - 初始化
cdev:cdev_init()+cdev_add(); - 自动创建设备节点;
- 硬件初始化(如
request_irq)。
- 注册字符设备:
-
exit函数(rmmod触发):
- 注销字符设备:
unregister_chrdev_region(); - 删除cdev:
cdev_del(); - 销毁类/设备:
class_destroy()+device_destroy(); - 释放资源:
free_irq()。
- 注销字符设备:
(三)杂项设备驱动(Misc Device)
-
特点:
- 主设备号固定为10;
- 自动生成
/dev/xxx节点; - 使用
miscdevice结构体(含次设备号、设备名)和file_operations。
-
init函数:
cmisc_register(&misc_dev); // 自动创建 /dev/misc_max30102 request_irq(...); // 申请中断 -
exit函数:
cmisc_deregister(&misc_dev); free_irq(...);
(四)Platform驱动(设备与驱动分离)
1. 设备树配置(以hcsr-04为例)
(1) 引脚复用配置
dts
pinctrl_hcsr04: hcsr04grp {
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER4__GPIO5_IO04 0x10b0 /* Trigger */
MX6UL_PAD_SNVS_TAMPER5__GPIO5_IO05 0x10b0 /* Echo */
>;
};
(2) 设备节点
dts
hcsr04 {
compatible = "imx6ull,hcsr04";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hcsr04>;
gpios = <&gpio5 4 GPIO_ACTIVE_LOW>, /* Trigger */
<&gpio5 5 GPIO_ACTIVE_LOW>; /* Echo */
status = "okay";
};
(3) 编译并更新设备树
bash
make dtbs
cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /tftpboot/
2. 驱动实现
- init函数 :
platform_driver_register() - exit函数 :
platform_driver_unregister() - probe函数 (设备匹配成功后执行):
- 解析设备树GPIO:
of_get_named_gpio() - 申请GPIO:
devm_gpio_request_one() - 设置方向:
gpio_direction_output()/gpio_direction_input() - 注册杂项设备:
misc_register()
- 解析设备树GPIO:
✅ 行业趋势:现代Linux驱动开发普遍采用 Platform + Device Tree 架构。
(五)I²C子系统驱动(以MAX30102为例)
1. I²C子系统架构分层
| 层级 | 功能 |
|---|---|
| 物理硬件层 | 具体I²C设备(如MAX30102) |
| 适配器层 | 开发板I²C控制器,厂商实现,提供 i2c_adapter 和 i2c_algorithm |
| 核心层 | 提供设备/驱动注册、匹配方法,数据传输API(i2c_master_send等) |
| 设备驱动层 | 手动实现 i2c_driver 和 i2c_client |
| 用户层 | 调用 open、read 等函数操作设备 |
2. 驱动实现要点
(1) 使用宏简化注册
c
module_i2c_driver(max30102_driver); // 自动调用 i2c_add_driver/i2c_del_driver
(2) i2c_driver结构体
c
static const struct of_device_id max30102_of_match[] = {
{ .compatible = "imx6ull,max30102" },
{ }
};
static struct i2c_driver max30102_driver = {
.probe = max30102_probe,
.remove = max30102_remove,
.driver = {
.name = "max30102",
.of_match_table = max30102_of_match,
},
};
(3) I²C寄存器读写封装
c
// 写寄存器:向指定寄存器写入1字节
static int max30102_write_reg(struct i2c_client *client, u8 reg, u8 val) {
u8 buf[2] = {reg, val}; // buf[0]=寄存器地址, buf[1]=写入值
struct i2c_msg msg = {
.addr = client->addr, // I2C设备地址(0x57)
.flags = 0, // 写操作
.len = 2, // 发送2字节
.buf = buf, // 数据缓冲区
};
return i2c_transfer(client->adapter, &msg, 1); // 返回传输消息数
}
// 理想结果:返回1表示成功传输1条消息
// 功能:通过I²C总线向MAX30102的指定寄存器写入配置值
c
// 读寄存器:先写地址,再读数据(两段式)
static int max30102_read_reg(struct i2c_client *client, u8 reg, u8 *val) {
struct i2c_msg msgs[2] = {
{ .addr = client->addr, .flags = 0, .len = 1, .buf = ® }, // 写地址
{ .addr = client->addr, .flags = I2C_M_RD, .len = 1, .buf = val } // 读数据
};
return i2c_transfer(client->adapter, msgs, 2);
}
// 理想结果:返回2表示成功完成两段传输
// 功能:从MAX30102指定寄存器读取1字节数据到val指针
(4) probe函数关键步骤
- 注册杂项设备:
misc_register() - 解析中断:
irq_of_parse_and_map() - 申请中断:
request_threaded_irq()(下降沿触发) - 初始化等待队列(用于阻塞read)
- 配置传感器寄存器(设置工作模式、LED电流、采样率等)
(5) read函数逻辑
- 进程在等待队列中阻塞;
- FIFO满时触发INT中断;
- 中断服务函数唤醒进程;
- 驱动读取FIFO原始数据(红光/红外ADC值);
copy_to_user()返回给应用层。
四、应用程序开发
(一)hcsr-04应用程序(main.c)
功能 :打开 /dev/misc_hcsr04,读取Echo高电平时间,计算并打印距离。
c
int fd = open("/dev/misc_hcsr04", O_RDWR); // 打开设备节点
read(fd, &time_us, sizeof(time_us)); // 读取高电平持续时间(微秒)
distence = (340.0 / 10000.0 * time_us) / 2; // 距离 = (声速 × 时间) / 2
printf("time_us = %d, distence = %.2lf cm\n", time_us, distence); // 打印结果
usleep(250000); // 延时250ms
close(fd); // 关闭设备
理想运行结果 :
time_us = 580, distence = 10.00 cm
功能说明:应用层通过标准文件操作接口与驱动交互,获取测距原始数据并换算为厘米。
(二)MAX30102应用程序(max30102_app.c)
功能 :打开 /dev/misc_max30102,读取FIFO数据,调用算法计算心率、血氧。
关键步骤:
- 读取500个样本初始化缓冲区,确定信号范围(max/min);
- 调用
maxim_heart_rate_and_oxygen_saturation()计算心率和血氧; - 循环更新缓冲区(舍去前100组,添加新100组),重新计算并打印。
(三)MAX30102算法(max30102_algorithm.c/.h)
核心函数 :maxim_heart_rate_and_oxygen_saturation
- 去噪滤波:对IR信号去除DC分量,进行4点移动平均、差分、汉明窗滤波;
- 峰值检测:计算相邻峰值间隔,心率 = 6000 / 平均间隔(单位:BPM);
- 血氧计算 :计算红光/红外信号AC/DC比值,通过
uch_spo2_table查表得SpO₂。
关键数据结构:
aun_ir_buffer[]:红外光原始数据缓冲区;aun_red_buffer[]:红光原始数据缓冲区;uch_spo2_table[]:预校准的血氧查找表。
五、设备树配置(MAX30102完整示例)
- 禁用冲突设备(避免GPIO1_IO15被占用):
dts
&sai2 {
status = "disabled"; // 禁用SAI2,释放GPIO1_IO15
};
- 中断引脚配置:
dts
pinctrl_max30102: max30102grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO15__GPIO1_IO15 0x10b0 /* INT引脚 */
>;
};
- I²C设备节点:
dts
&i2c1 {
clock-frequency = <100000>; // 100kHz
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
max30102@57 {
compatible = "imx6ull,max30102";
reg = <0x57>; // I²C地址
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_max30102>;
interrupt-parent = <&gpio1>;
interrupts = <15 IRQ_TYPE_EDGE_FALLING>; // GPIO1_IO15,下降沿触发
status = "okay";
};
};
硬件连接(i.MX6ULL):
- VCC → P4.48(3.3V)
- GND → P4.47(地)
- SCL → P4.43(I2C1_SCL)
- SDA → P4.42(I2C1_SDA)
- INT → P4.30(GPIO1_IO15)
六、开发环境与编译
交叉编译工具链配置(内核版本:4.1.15)
-
拷贝工具链:
bashcp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz ~/rabbit/ -
解压:
bashcd ~/rabbit tar -xvf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz -
配置环境变量(~/.bashrc):
bashexport PATH=/home/用户名/rabbit/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin:$PATH source ~/.bashrc -
验证:
basharm-linux-gnueabihf-gcc -v # 应显示 4.9.4
⚠️ 重要:内核与驱动必须使用同一版本编译器,否则
insmod会报错。
应用程序Makefile示例
makefile
CC := arm-linux-gnueabihf-gcc
MODULE_NAME := max30102
all:
${CC} $(wildcard *.c) -o ${MODULE_NAME}_app -lm # -lm链接数学库
cp ${MODULE_NAME}_app ~/nfs
.PHONY: clean
clean:
rm -f ${MODULE_NAME}_app
distclean:
rm -f ${MODULE_NAME}_app
rm -f ~/nfs/${MODULE_NAME}_app
七、面试高频问题准备
- "你用的传感器是什么接口?如何连接?"
- "该传感器的量程、精度、分辨率是多少?"
- "请描述其工作时序。"
- "Linux下如何编写其驱动?用了什么框架?"
- "设备树中如何描述该设备?"
💡 建议:调试时务必记录参数、时序、代码逻辑,形成个人技术笔记。
✅ 总结:本日学习涵盖传感器分类选型、工作原理、Linux驱动四大框架(字符设备/Misc/Platform/I²C子系统)、设备树配置、应用层交互及交叉编译环境搭建,为嵌入式传感器项目开发奠定完整基础。