Day73 嵌入式传感器技术全栈开发
·
day73 嵌入式传感器技术全栈开发
本笔记系统性整理了嵌入式开发中常用传感器(GPIO、I2C、SPI、ADC、UART)的分类、工作原理、驱动开发框架及应用层实现,涵盖从硬件连接、设备树配置、内核驱动编写到用户空间程序调用的完整流程。内容基于Linux内核子系统(字符设备、杂项设备、I2C、SPI、IIO),并结合OV5640摄像头、HCSR04超声波、MAX30102心率血氧、MQ系列气体传感器等实例,提供可直接运行的代码及详细注释。
一、传感器分类与核心参数
(一)按连接方式分类
| 连接方式 | 传感器型号 | 功能 |
|---|---|---|
| GPIO(单总线) | dht11 | 温湿度检测 |
| ds18b20 | 温度检测 | |
| hcsr-04 | 超声波测距 | |
| I2C | LM75 | 温度检测 |
| BH1750 | 光照强度检测 | |
| MPU6050 | 六轴姿态检测 | |
| MAX30100 | 心率、血氧浓度检测 | |
| SPI | ADXL345 | 三轴加速度检测 |
| ADC | MQ系列(MQ-2、MQ-135、MQ-7等) | 气体浓度检测(如MQ-2检测烟雾浓度) |
| UART | GPS传感器 | 定位(精度、维度、海拔)检测 |
| GY-35 | 红外测距检测 |
(二)关键传感器指标参数
| 传感器 | 功能 | 连接方式 | 量程(测量范围) | 精度 | 分辨率 | 工作电压 |
|---|---|---|---|---|---|---|
| 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 | 心率、血氧 | I2C | 心率:30-240BPM(次/分钟);血氧:70-100% | ±2% | - | 1.8V-5.5V |
二、传感器工作原理与时序详解
(一)DHT11温湿度传感器
电路图:
R1
VCC 4.7K
1 2 3
DATA
NC
GND P2
DHT11 GND
数据格式:5字节共40bit,高位先行。
- 字节1:湿度整数部分
- 字节2:湿度小数部分
- 字节3:温度整数部分
- 字节4:温度小数部分
- 字节5:校验和(前4字节之和)
时序:
- 主机起始信号:拉低DATA引脚 ≥18ms,再拉高 ≥20-40us。
- 从机应答信号:拉低 ≥80us,再拉高 ≥80us。
- 数据传输:
- bit “0”:拉低50us,再拉高26-28us。
- bit “1”:拉低50us,再拉高70us。
- 结束信号:主机将DATA拉高。
(二)HC-SR04超声波传感器
实物图:标注“4.0000H00”和“HC-SR04”。
引脚:
- VCC:5V电源
- GND:接地
- Trig:触发端(控制)
- Echo:回波端(接收)
工作原理/时序:
- 向Trig发送≥10us的高电平脉冲。
- 传感器内部发射8个40kHz超声波脉冲。
- Echo引脚输出高电平,持续时间为超声波往返时间。
- 计算距离公式:
距离 = (Echo高电平时间 × 340m/s) / 2
IMX6ULL原理图连接:
- VCC → P4 46号引脚
- GND → P4 47号引脚
- Trig → SNVS_TAMPER4 (P4 1号引脚)
- Echo → SNVS_TAMPER5 (P4 6号引脚)
三、Linux驱动框架详解
(一)驱动类型概述
- 字符型设备驱动:
- 以字节为单位读写,无缓存。
- 适用于鼠标、键盘、串口等。
- 核心结构体:
cdev,file_operations。
- 块设备驱动:
- 以固定块大小(如512k)读写,带缓存。
- 适用于硬盘、Flash等存储设备。
- 网络设备驱动:
- 基于TCP/IP协议栈,用于网络通信。
(二)平台驱动(Platform Driver)——以HCSR04为例
设备树配置(dts)
/* iomux节点增加引脚复用 */
trig:
#define MX6UL_PAD_SNVS_TAMPER4_GPIO5_IO04 0x002C 0x02B8 0x0000 0x5 0x0
echo:
#define MX6UL_PAD_SNVS_TAMPER5_GPIO5_IO05 0x0030 0x02BC 0x0000 0x5 0x0
pinctrl_hcsr04: hcsr04_fsl,pins {
MX6UL_PAD_SNVS_TAMPER4_GPIO5_IO04 0x10B0
MX6UL_PAD_SNVS_TAMPER5_GPIO5_IO05 0x10B0
};
/* 添加hcsr04设备节点 */
hcsr04 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "imx6ull,hcsr04";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hcsr04>;
gpio-trig = <&gpio5 4 GPIO_ACTIVE_LOW>;
gpio-echo = <&gpio5 5 GPIO_ACTIVE_LOW>;
status = "okay";
};
驱动入口函数
static int __init hcsr04_init(void) {
int ret = 0;
ret = platform_driver_register(&hcsr04_driver); // 注册平台驱动
if (ret != 0) {
pr_info("platform_driver_register failed!
");
return -1;
}
return 0;
}
驱动出口函数
static void __exit hcsr04_exit(void) {
platform_driver_unregister(&hcsr04_driver); // 注销平台驱动
return;
}
平台驱动结构体
static struct platform_driver hcsr04_driver = {
.probe = hcsr04_probe, // 匹配成功后执行
.remove = hcsr04_remove, // 卸载时执行
.driver = {
.name = "hcsr04", // 驱动名称,用于匹配compatible
},
.id_table = hcsr04_idtable,
.of_match_table = hcsr04_of_match_table, // 设备树匹配表
.owner = THIS_MODULE,
};
设备树匹配表
static struct of_device_id hcsr04_of_match_table[] = {
{.compatible = "imx6ull,hcsr04"}, // 与设备树compatible字段匹配
{},
};
probe函数实现
static int hcsr04_probe(struct platform_device *pdev) {
int ret = 0;
// 解析设备树获取GPIO
gpio_trig = of_get_named_gpio(pdev->dev.of_node, "gpio-trig", 0);
gpio_echo = of_get_named_gpio(pdev->dev.of_node, "gpio-echo", 0);
// 申请GPIO资源
ret = devm_gpio_request(&pdev->dev, gpio_trig, "hcsr04_trig");
if (ret) return ret;
ret = devm_gpio_request(&pdev->dev, gpio_echo, "hcsr04_echo");
if (ret) return ret;
// 注册混杂设备
ret = misc_register(&misc_hcsr04);
if (ret != 0) return ret;
// 构建file_operations
static struct file_operations fops = {
.read = hcsr04_read,
};
return 0;
}
read函数实现
static ssize_t hcsr04_read(struct file *fp, char __user *puser, size_t n, loff_t *off) {
int cnt = 0; // 存储Echo高电平持续时间(微秒)
long nret = 0;
struct timeval start_time, end_time;
// 设置Trig为输出模式,发送触发脉冲
gpio_direction_output(gpio_trig, 0);
gpio_set_value(gpio_trig, 1);
udelay(10); // 延时10us
gpio_set_value(gpio_trig, 0);
// 设置Echo为输入模式,等待高电平开始
gpio_direction_input(gpio_echo);
while (0 == gpio_get_value(gpio_echo)); // 等待Echo变为高电平
// 记录开始时间
do_gettimeofday(&start_time);
// 等待Echo变为低电平
while (gpio_get_value(gpio_echo));
// 记录结束时间
do_gettimeofday(&end_time);
// 计算时间差(微秒)
cnt = (end_time.tv_sec * 1000000 + end_time.tv_usec) -
(start_time.tv_sec * 1000000 + start_time.tv_usec);
// 将结果拷贝给用户空间
nret = copy_to_user(puser, &cnt, sizeof(cnt));
if (nret) {
pr_info("copy_to_user failed
");
return -1;
}
return sizeof(cnt); // 返回拷贝的数据长度
}
应用层程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd = 0;
int time_us = 0;
double distance = 0;
fd = open("/dev/misc_hcsr04", O_RDWR); // 打开设备节点
if (-1 == fd) {
perror("fail to open");
return -1;
}
while (1) {
read(fd, &time_us, sizeof(time_us)); // 读取超声波往返时间
distance = (340.0 / 1000000.0 * time_us) / 2; // 计算距离(厘米)
printf("time_us=%d, distance=%.2f cm
", time_us, distance);
usleep(250000); // 延时250ms
}
close(fd);
return 0;
}
理想运行结果:
time_us=1000, distance=17.00 cm
time_us=1500, distance=25.50 cm
time_us=2000, distance=34.00 cm
...
(三)I2C子系统驱动 —— 以MAX30102为例
驱动注册
module_i2c_driver(max30102_driver); // 自动处理insmod/rmmod
I2C驱动结构体
static struct i2c_driver max30102_driver = {
.driver = {
.name = "putemax30102",
},
.remove = max30102_remove,
.id_table = max30102_id_table,
.probe = max30102_probe,
.of_match_table = max30102_of_match_table,
};
设备树匹配表
static struct i2c_device_id max30102_id_table[] = {
{.name = "putemax30102"},
{},
};
static struct of_device_id max30102_of_match_table[] = {
{.compatible = "imx6ull,max30102"},
{},
};
探测函数(probe)
static int max30102_probe(struct i2c_client *client, const struct i2c_device_id *id) {
int ret = 0;
ret = misc_register(&misc); // 注册混杂设备
if (ret != 0)
return ret;
// 初始化其他资源
return 0;
}
文件操作结构体
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = max30102_read,
.open = max30102_open,
.release = max30102_close,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_max30102",
.fops = &fops,
};
I2C数据传输(write/read)
struct i2c_adapter *max30102_adp = NULL;
struct i2c_msg msg;
unsigned char i2cbuf[2] = {0};
int ret = 0;
max30102_adp = max30102_client->adapter;
i2cbuf[0] = regaddr; // 寄存器地址
i2cbuf[1] = data; // 数据值
msg.addr = max30102_client->addr; // 从机地址
msg.len = 2;
msg.flags = 0; // 写操作
msg.buf = i2cbuf;
ret = max30102_adp->algo->master_xfer(max30102_adp, &msg, 1); // 发送数据
if (ret < 0)
return ret;
open函数实现
static int max30102_open(struct inode *inode, struct file *fp) {
int ret = 0;
unsigned char id = 0;
unsigned char i2cbuf[6] = {0};
// 读ID寄存器验证芯片
max30102_read_register(0xFF, &id, 1);
printk("Part ID:%#x
", id);
max30102_read_register(0xFE, &id, 1);
printk("Revision ID:%#x
", id);
// 重置芯片
ret = max30102_write_register(0x09, 0x40);
mdelay(10);
ret = max30102_write_register(0x02, 0xCE);
if (ret < 0)
return ret;
// 配置中断
ret = max30102_write_register(0x03, 0x00);
if (ret < 0)
return ret;
ret = max30102_write_register(0x04, 0x00); // FIFO_WR_PTR[4:0]
// 初始化等待队列
condition = 0;
init_waitqueue_head(&waitqueue_head);
// 申请中断(下降沿触发)
irqno = of_irq_get(max30102_client->dev.of_node, 0);
if (irqno < 0) {
pr_info("of_irq_get failed
");
return -1;
}
ret = request_irq(irqno, max30102_irq_handler, IRQF_TRIGGER_FALLING, "pute-max30102-irq", NULL);
if (ret != 0) {
pr_info("request irq failed
");
return -1;
}
max30102_fifo_readbytes(0x07, i2cbuf); // 读取FIFO初始数据
return 0;
}
中断服务函数
static irqreturn_t max30102_irq_handler(int irqno, void *arg) {
condition = 1; // 标志中断发生
wake_up_interruptible(&waitqueue_head); // 唤醒等待队列
return IRQ_HANDLED;
}
read函数实现
static ssize_t max30102_read(struct file *fp, char __user *puser, size_t n, loff_t *off) {
unsigned char i2cbuf[6] = {0};
long nret = 0;
wait_event_interruptible(waitqueue_head, condition); // 阻塞等待中断
condition = 0; // 重置标志
max30102_fifo_readbytes(0x07, i2cbuf); // 读取FIFO数据
nret = copy_to_user(puser, i2cbuf, 6); // 拷贝给用户空间
if (nret) {
pr_info("copy_to_user failed
");
return -1;
}
return 6; // 返回读取的数据长度
}
(四)ADC驱动 —— 以MQ-2气体传感器为例
设备树配置
&adc1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_adc1>;
num-channels = <2>;
vref-supply = <®_vref_adc>;
status = "okay";
};
&iomuxc {
pinctrl_adc1: adc1grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__ADC1_CH1 0xb0b0
>;
};
};
// 在regulators节点添加基准电压
regulators {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <0>;
reg_vref_adc: regulator@1 {
compatible = "regulator-fixed";
reg = <1>;
regulator-name = "vref-adc";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
};
};
应用层程序(adc_app.c)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>
typedef struct {
int raw;
float act;
} IMX6ULL_ADC;
int adc_read(IMX6ULL_ADC *imx6ulladc) {
int fd_scale, fd_raw;
char buf[32];
int ret;
fd_scale = open("/sys/bus/iio/devices/iio:device0/in_voltage_scale", O_RDONLY);
if (fd_scale < 0) {
perror("open scale failed");
return -1;
}
fd_raw = open("/sys/bus/iio/devices/iio:device0/in_voltage1_raw", O_RDONLY);
if (fd_raw < 0) {
perror("open raw failed");
close(fd_scale);
return -1;
}
ret = read(fd_raw, buf, sizeof(buf));
if (ret < 0) {
perror("read raw failed");
close(fd_scale);
close(fd_raw);
return -1;
}
sscanf(buf, "%d", &imx6ulladc->raw);
ret = read(fd_scale, buf, sizeof(buf));
if (ret < 0) {
perror("read scale failed");
close(fd_scale);
close(fd_raw);
return -1;
}
float scale;
sscanf(buf, "%f", &scale);
imx6ulladc->act = (scale * imx6ulladc->raw) / 1000.0f;
close(fd_scale);
close(fd_raw);
return 0;
}
int main(void) {
IMX6ULL_ADC imx6ulladc;
int ret;
float R0 = 6.64; // 校准值
float Rs = 0;
float ppmVal = 0;
while (1) {
ret = adc_read(&imx6ulladc);
if (ret == 0) {
Rs = (5 - imx6ulladc.act) / imx6ulladc.act * 0.5; // 计算Rs
ppmVal = pow(11.5428 * R0 / Rs, 0.6549f) * 100; // 计算ppm浓度
printf("ADC value:%d, v%.3fV, ppmVal = %.2f
", imx6ulladc.raw, imx6ulladc.act, ppmVal);
}
sleep(1);
}
return 0;
}
理想运行结果:
ADC value:1800, v1.449V, ppmVal = 25.00
ADC value:1900, v1.529V, ppmVal = 20.00
ADC value:2000, v1.609V, ppmVal = 15.00
...
(五)摄像头驱动 —— 以OV5640为例
设备树配置
ov5640: ov5640@3c {
compatible = "ovti,ov5640";
reg = <0x3c>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_csi1 &csi_pwn_rst>;
clocks = <&clks IMX6UL_CLK_CSI>;
clock-names = "csi_mclk";
pwn-gpios = <&gpio1 4 1>;
rst-gpios = <&gpio1 2 0>;
csi_id = <0>;
mclk = <24000000>;
mclk_source = <0>;
status = "okay";
port {
ov5640_ep: endpoint {
remote-endpoint = <&csi1_ep>;
};
};
};
&csi {
status = "okay";
port {
csi1_ep: endpoint {
remote-endpoint = <&ov5640_ep>;
};
};
};
&iomuxc {
csi_pwn_rst: csi_pwn_rstgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO02_GPIO1_IO02 0x10B0
MX6UL_PAD_GPIO1_IO04_GPIO1_IO04 0x10B0
>;
};
pwm3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm3>;
clocks = <&clks IMX6UL_CLK_PWM3>;
status = "disable"; // 禁用冲突的PWM3
};
};
内核配置(menuconfig)
Device Drivers -> Multimedia support -> V4L platform devices
<*> MXC Video For Linux Video output
<*> MXC Video For Linux Video Capture
<*> OmniVision ov5640 camera support
驱动模块加载
modprobe mx6s_capture
modprobe ov5640_camera
V4L2应用层采集流程
核心步骤:
- 打开设备:
open("/dev/video1", O_RDWR) - 查询能力:
ioctl(fd, VIDIOC_QUERYCAP, &cap) - 设置格式:
ioctl(fd, VIDIOC_S_FMT, &fmt)(设置分辨率、像素格式) - 申请缓冲区:
ioctl(fd, VIDIOC_REQBUFS, &req)(通常4个) - 映射内存:
mmap()将缓冲区映射到用户空间 - 缓冲区入队:
ioctl(fd, VIDIOC_QBUF, &buf) - 启动采集:
ioctl(fd, VIDIOC_STREAMON, &type) - 监听事件:
select()等待帧完成 - 缓冲区出队:
ioctl(fd, VIDIOC_DQBUF, &buf)读取数据 - 重新入队:
ioctl(fd, VIDIOC_QBUF, &buf)循环采集
图像显示:
由于OV5640默认输出YUYV,而LCD屏需RGB888,需在应用层进行转换:
// 示例:YUV422转RGB888
void yuv422_to_rgb888(unsigned char *yuv, unsigned char *rgb, int width, int height) {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int y = yuv[i * width * 2 + j * 2]; // Y分量
int u = yuv[i * width * 2 + j * 2 + 1]; // U分量
int v = yuv[i * width * 2 + j * 2 + 2]; // V分量
int r = y + 1.402 * (v - 128); // RGB计算公式
int g = y - 0.344 * (u - 128) - 0.714 * (v - 128);
int b = y + 1.772 * (u - 128);
rgb[(i * width + j) * 3 + 0] = CLAMP(r, 0, 255);
rgb[(i * width + j) * 3 + 1] = CLAMP(g, 0, 255);
rgb[(i * width + j) * 3 + 2] = CLAMP(b, 0, 255);
}
}
}
(六)串口(UART)驱动 —— 以GPS为例
硬件连接
- GPS TX → i.MX UART_RX
- GPS RX → i.MX UART_TX
- 或使用USB-TTL模块,识别为
/dev/ttyUSB0
数据协议
- 波特率:9600
- 格式:NMEA 0183,例如:
$GPGGA,161229.00,3958.12345,N,11620.56789,E,1,08,1.0,50.0,M,0.0,M,,*62
应用层程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
int main() {
int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
perror("Open serial port error");
return -1;
}
struct termios options;
tcgetattr(fd, &options);
cfsetispeed(&options, B9600); // 设置波特率
cfsetospeed(&options, B9600);
options.c_cflag |= (CLOCAL | CREAD); // 启用本地模式和接收
options.c_cflag &= ~CSIZE; // 清除数据位掩码
options.c_cflag |= CS8; // 设置8位数据
options.c_cflag &= ~PARENB; // 无奇偶校验
options.c_cflag &= ~CSTOPB; // 1位停止位
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 无行规程
options.c_oflag &= ~OPOST; // 不处理输出
options.c_cc[VMIN] = 1; // 最少读取1个字符
options.c_cc[VTIME] = 0; // 无超时
tcsetattr(fd, TCSANOW, &options);
char buffer[1024];
while (1) {
int len = read(fd, buffer, sizeof(buffer) - 1);
if (len > 0) {
buffer[len] = '\0';
printf("%s", buffer);
}
}
close(fd);
return 0;
}
四、接口对比总结
| 特性 | UART(串口) | I²C | SPI |
|---|---|---|---|
| 线数 | 2(TX/RX) | 2(SCL/SDA) | 3–4(SCLK/MOSI/MISO/CS) |
| 速度 | 低(kbps 级) | 中(100kbps–400kbps) | 高(MHz 级) |
| 主从 | 点对点 | 多从机(地址区分) | 多从机(CS 区分) |
| 全双工 | 是 | 半双工 | 是 |
| 典型应用 | GPS、蓝牙、调试串口 | 光照、温湿度、RTC | 加速度计、Flash、显示屏 |
五、系统架构与开发流程
(一)通用开发流程
- 阅读手册:明确传感器通信协议、寄存器、时序。
- 配置设备树:添加设备节点,指定引脚、地址、频率。
- 编写驱动:基于Linux子系统(IIO/I²C/SPI/Platform)。
- 应用层测试:通过文件IO或自定义程序读取数据。
- 数据解析:转换为物理量(如lux、g、经纬度)。
(二)系统初始化与循环检测
// 系统初始化
system_init();
// 循环采集
while (1) {
read_hcsr04_distance(); // 读取超声波距离
read_max30102_heart_rate(); // 读取心率
read_mq2_gas_concentration(); // 读取气体浓度
read_gps_position(); // 读取GPS位置
process_data(); // 数据处理与算法计算
output_control(); // 输出控制或显示结果
delay_ms(1000); // 延时1秒
}
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)