当前位置: 首页 > news >正文

Day73 嵌入式传感器技术全栈开发

day73 嵌入式传感器技术全栈开发

本笔记系统性整理了嵌入式开发中常用传感器(GPIO、I2C、SPI、ADC、UART)的分类、工作原理、驱动开发框架及应用层实现,涵盖从硬件连接、设备树配置、内核驱动编写到用户空间程序调用的完整流程。内容基于Linux内核子系统(字符设备、杂项设备、I2C、SPI、IIO),并结合OV5640摄像头、HCSR04超声波、MAX30102心率血氧、MQ系列气体传感器等实例,提供可直接运行的代码及详细注释。


一、传感器分类与核心参数

(一)按连接方式分类

连接方式传感器型号功能
GPIO(单总线)dht11温湿度检测
ds18b20温度检测
hcsr-04超声波测距
I2CLM75温度检测
BH1750光照强度检测
MPU6050六轴姿态检测
MAX30100心率、血氧浓度检测
SPIADXL345三轴加速度检测
ADCMQ系列(MQ-2、MQ-135、MQ-7等)气体浓度检测(如MQ-2检测烟雾浓度)
UARTGPS传感器定位(精度、维度、海拔)检测
GY-35红外测距检测

(二)关键传感器指标参数

传感器功能连接方式量程(测量范围)精度分辨率工作电压
dht11温湿度GPIO(单总线)温度:0-50℃;湿度:20-90%RH温度:±2℃;湿度:±5%RH13.3V-5.5V
dht22温湿度GPIO(单总线)温度:-40-80℃;湿度:0-99.9%RH温度:±0.5℃;湿度:±2%RH0.1℃/0.1%RH3.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超声波测距GPIO2cm-450cm0.3cm15V
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字节之和)

时序

  1. 主机起始信号:拉低DATA引脚 ≥18ms,再拉高 ≥20-40us。
  2. 从机应答信号:拉低 ≥80us,再拉高 ≥80us。
  3. 数据传输
    • bit “0”:拉低50us,再拉高26-28us。
    • bit “1”:拉低50us,再拉高70us。
  4. 结束信号:主机将DATA拉高。

(二)HC-SR04超声波传感器

实物图:标注“4.0000H00”和“HC-SR04”。

引脚

  • VCC:5V电源
  • GND:接地
  • Trig:触发端(控制)
  • Echo:回波端(接收)

工作原理/时序

  1. 向Trig发送≥10us的高电平脉冲。
  2. 传感器内部发射8个40kHz超声波脉冲。
  3. Echo引脚输出高电平,持续时间为超声波往返时间。
  4. 计算距离公式:距离 = (Echo高电平时间 × 340m/s) / 2

IMX6ULL原理图连接

  • VCC → P4 46号引脚
  • GND → P4 47号引脚
  • Trig → SNVS_TAMPER4 (P4 1号引脚)
  • Echo → SNVS_TAMPER5 (P4 6号引脚)

三、Linux驱动框架详解

(一)驱动类型概述

  1. 字符型设备驱动
    • 以字节为单位读写,无缓存。
    • 适用于鼠标、键盘、串口等。
    • 核心结构体:cdev, file_operations
  2. 块设备驱动
    • 以固定块大小(如512k)读写,带缓存。
    • 适用于硬盘、Flash等存储设备。
  3. 网络设备驱动
    • 基于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 0x0pinctrl_hcsr04: hcsr04_fsl,pins {MX6UL_PAD_SNVS_TAMPER4_GPIO5_IO04 0x10B0MX6UL_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;// 解析设备树获取GPIOgpio_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_operationsstatic 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); // 延时10usgpio_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 = <&reg_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; // 计算RsppmVal = 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 0x10B0MX6UL_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应用层采集流程

核心步骤

  1. 打开设备:open("/dev/video1", O_RDWR)
  2. 查询能力:ioctl(fd, VIDIOC_QUERYCAP, &cap)
  3. 设置格式:ioctl(fd, VIDIOC_S_FMT, &fmt) (设置分辨率、像素格式)
  4. 申请缓冲区:ioctl(fd, VIDIOC_REQBUFS, &req) (通常4个)
  5. 映射内存:mmap() 将缓冲区映射到用户空间
  6. 缓冲区入队:ioctl(fd, VIDIOC_QBUF, &buf)
  7. 启动采集:ioctl(fd, VIDIOC_STREAMON, &type)
  8. 监听事件:select() 等待帧完成
  9. 缓冲区出队:ioctl(fd, VIDIOC_DQBUF, &buf) 读取数据
  10. 重新入队: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²CSPI
线数2(TX/RX)2(SCL/SDA)3–4(SCLK/MOSI/MISO/CS)
速度低(kbps 级)中(100kbps–400kbps)高(MHz 级)
主从点对点多从机(地址区分)多从机(CS 区分)
全双工半双工
典型应用GPS、蓝牙、调试串口光照、温湿度、RTC加速度计、Flash、显示屏

五、系统架构与开发流程

(一)通用开发流程

  1. 阅读手册:明确传感器通信协议、寄存器、时序。
  2. 配置设备树:添加设备节点,指定引脚、地址、频率。
  3. 编写驱动:基于Linux子系统(IIO/I²C/SPI/Platform)。
  4. 应用层测试:通过文件IO或自定义程序读取数据。
  5. 数据解析:转换为物理量(如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秒
}

http://www.dtcms.com/a/545026.html

相关文章:

  • 学历提升有几种方式长沙正规seo优化公司
  • 合肥网络公司 网站建设网站建设 预算
  • 23大数据 数据挖掘集合
  • Docker Compose曝路径遍历漏洞,可致任意覆写文件(CVE-2025-62725)
  • 网站可以分为哪些类型怎样优化自己的网站
  • Rust 模式匹配的穷尽性检查:从编译器证明到工程演进
  • C# Entity Framework Core 中的 Include 和 ThenInclude 详解
  • Linux如何远程控制Windows?rdesktop让跨系统操作像本地一样流畅
  • Spring Boot3零基础教程,JVM 编译原理,笔记87
  • Rust 变量声明与可变性:从设计哲学到工程实践
  • 深圳苍松大厦 网站建设对网站做综合搜索引擎优化分析
  • 数据结构 09 二叉树作业
  • 建网站需要买什么平台公司信用评级
  • 算法19.0
  • 【第五章:计算机视觉-项目实战之推荐/广告系统】3.精排算法-(3)精排模块多目标融合:从线性融合到Bayes方程融合原理
  • 【详细教程】对拍 0 基础学习小课堂 [内附例题演示]
  • 在 Windows 系统中安装 Oracle、SQL Server(MSSQL)和 MySQL
  • 企业网站导航代码国外代码开源网站
  • 深圳网站开发公司哪家好平面设计岗位职责
  • mooc网站开发案例ip138域名查询
  • 黑白图片智能上色API技术文档 - 让你的老照片重获新生
  • 【Android】Dalvik 对比 ART
  • 【游戏设计】如何建立个人的游戏创意库
  • 手表电商网站湖南人文科技学院官网教务系统
  • 【软件可维护性测试:构建可持续演进更新的软件系统】
  • 【小白笔记】 while 与 for + break 的比较分析
  • STM32中死机 Crash dump 打印出函数调用关系
  • STM32的GPIOx_IDR 与 GPIOx_ODR
  • Rust 借用检查器(Borrow Checker)的工作原理:编译期内存安全的守护者
  • 仓颉语言核心技术深度解析:面向全场景智能时代的现代编程语言