Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录

Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录

摘要:本文详细记录了在 IMX6ULL 开发板上,将一份 GPIO 按键输入驱动改造成 SR501 人体红外传感器驱动的完整过程。内容涵盖硬件连接、驱动代码逐行讲解、关键函数剖析、中断配置、常见错误排查(如 GPIO 占用、模块版本不匹配、设备节点未创建等)以及最终成功运行的测试结果。文中提供了完整可编译的驱动代码与测试程序,并配有流程图和知识巩固问答,适合嵌入式 Linux 驱动初学者参考。


目录

### 文章目录

  • [Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [目录](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [@[TOC]](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [1. 项目背景与目标](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [2. 硬件连接与 GPIO 编号计算](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [3. 驱动代码逐行详解](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [3.1 头文件与全局结构](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [3.2 环形缓冲区与等待队列](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [3.3 文件操作函数(read/poll/fasync)](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [read 函数](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [poll 函数](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [fasync 函数](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [3.4 中断处理函数](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [3.5 模块初始化函数](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [3.6 模块退出函数](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [4. 测试程序代码及说明](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [5. 编译与加载流程](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [5.1 Makefile 编写](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [5.2 编译驱动与测试程序](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [5.3 加载模块与设备节点创建](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [6. 实战中遇到的错误及解决方案](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [6.1 模块版本不匹配](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [6.2 GPIO 被配置为输出无法申请中断](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [6.3 sysfs 类目录重复导致加载失败](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [6.4 传感器预热与硬件接触问题](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [7. 驱动与应用程序流程图](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [驱动初始化流程](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [中断处理与数据流](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [应用程序执行流程](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [8. 知识巩固:10 道简答题及答案](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)
  • [9. 总结与展望](#文章目录 Linux 驱动实战:SR501 人体红外传感器驱动开发与调试全记录 目录 @[TOC] 1. 项目背景与目标 2. 硬件连接与 GPIO 编号计算 3. 驱动代码逐行详解 3.1 头文件与全局结构 3.2 环形缓冲区与等待队列 3.3 文件操作函数(read/poll/fasync) read 函数 poll 函数 fasync 函数 3.4 中断处理函数 3.5 模块初始化函数 3.6 模块退出函数 4. 测试程序代码及说明 5. 编译与加载流程 5.1 Makefile 编写 5.2 编译驱动与测试程序 5.3 加载模块与设备节点创建 6. 实战中遇到的错误及解决方案 6.1 模块版本不匹配 6.2 GPIO 被配置为输出无法申请中断 6.3 sysfs 类目录重复导致加载失败 6.4 传感器预热与硬件接触问题 7. 驱动与应用程序流程图 驱动初始化流程 中断处理与数据流 应用程序执行流程 8. 知识巩固:10 道简答题及答案 9. 总结与展望)

1. 项目背景与目标

在嵌入式 Linux 系统中,GPIO 是最基础的外设之一。SR501 是一款常用的人体红外传感器,当检测到人体移动时,其 OUT 引脚会输出高电平,无人时恢复低电平。本项目旨在编写一个字符设备驱动,实现以下功能:

  • 将 SR501 的 OUT 引脚连接到 IMX6ULL 的 GPIO4_19(编号 115)。
  • 驱动能够捕获引脚电平变化(上升沿和下降沿),并通过中断方式通知应用程序。
  • 提供标准的文件操作接口:readpollfasync
  • 自动创建设备节点 /dev/sr501
  • 编写测试程序,实时打印传感器状态(有人/无人)。

通过本项目,你将掌握:

  • GPIO 的申请、方向设置、电平读取
  • GPIO 中断的注册与处理
  • 环形缓冲区、等待队列、异步通知等内核机制
  • 字符设备驱动的完整框架
  • 常见编译、加载错误的排查方法

2. 硬件连接与 GPIO 编号计算

根据开发板接口图,SR501 与 IMX6ULL 的连接如下:

SR501 引脚 开发板引脚 说明
VCC 5V 供电正极
GND GND 供电负极
OUT GPIO4_19 信号输出

GPIO 编号计算公式

对于 i.MX6ULL,GPIO 编号 = 组号 × 32 + 组内偏移。

  • GPIO4 对应组号 3(因为从 0 开始计数:GPIO1=0,GPIO2=1,GPIO3=2,GPIO4=3)
  • GPIO4_19 的编号 = 3 × 32 + 19 = 115

因此,驱动代码中使用 GPIO 编号 115

注意:该引脚在设备树中默认可能被复用为 CSI 摄像头接口功能,需要确保硬件连接无误,并在驱动中正确申请 GPIO 资源。


3. 驱动代码逐行详解

3.1 头文件与全局结构

c

复制代码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/uaccess.h>

头文件作用

  • module.h:内核模块必需
  • fs.h:文件操作结构体
  • gpio.h:GPIO API(申请、设置方向、读写)
  • interrupt.hirq.h:中断注册
  • wait.hpoll.h:等待队列与 poll 机制
  • uaccess.hcopy_to_user / copy_from_user

自定义结构体

c

复制代码
struct gpio_desc {
    int gpio;       // GPIO 编号
    int irq;        // 中断号
    char *name;     // 标签名称
    int key;        // 传感器编号(本例未使用)
    struct timer_list key_timer; // 定时器(本例未使用)
};

static struct gpio_desc gpios[1] = {
    {115, 0, "sr501", },
};

虽然结构体包含了 keykey_timer 字段,但这是从按键驱动改造过程中保留的,SR501 驱动并未使用它们。保留是为了代码修改的连续性,实际可精简。

3.2 环形缓冲区与等待队列

c

复制代码
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void) { return (r == w); }
static int is_key_buf_full(void) { return (r == NEXT_POS(w)); }

static void put_key(int key) {
    if (!is_key_buf_full()) {
        g_keys[w] = key;
        w = NEXT_POS(w);
    }
}

static int get_key(void) {
    int key = 0;
    if (!is_key_buf_empty()) {
        key = g_keys[r];
        r = NEXT_POS(r);
    }
    return key;
}

static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
struct fasync_struct *button_fasync;

环形缓冲区 :用于缓存传感器事件,防止应用程序来不及读取时丢失数据。put_key 由中断处理函数调用,将打包的事件数据存入缓冲区;get_keyread 函数调用,取出最早的事件。

等待队列 :当缓冲区为空时,read 调用 wait_event_interruptible 使进程睡眠;当中断产生并放入数据后,调用 wake_up_interruptible 唤醒睡眠进程。

异步通知结构体button_fasync 用于支持 fcntl(fd, F_SETFL, O_ASYNC),当有数据可读时,内核向应用进程发送 SIGIO 信号。

3.3 文件操作函数(read/poll/fasync)

read 函数

c

复制代码
static ssize_t gpio_drv_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    int err;
    int key;

    if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
        return -EAGAIN;

    wait_event_interruptible(gpio_wait, !is_key_buf_empty());
    key = get_key();
    err = copy_to_user(buf, &key, 4);

    return 4;
}

逻辑

  1. 若文件打开时设置了非阻塞标志且缓冲区为空,立即返回 -EAGAIN
  2. 否则调用 wait_event_interruptible 睡眠,直到缓冲区非空。
  3. 被唤醒后从缓冲区取出一个 key 值(4 字节整数)。
  4. 通过 copy_to_user 将数据拷贝到用户空间。

数据格式key 的低 8 位为传感器编号(本例中为 0),高 8 位为电平值(1 表示高电平有人,0 表示低电平无人)。应用层打印 0x%x 即可看到类似 0x100(有人)和 0x0(无人)的输出。

poll 函数

c

复制代码
static unsigned int gpio_drv_poll(struct file *fp, poll_table *wait)
{
    poll_wait(fp, &gpio_wait, wait);
    return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

支持 select/poll 多路复用。将等待队列添加到 poll_table,并返回可读状态掩码。

fasync 函数

c

复制代码
static int gpio_drv_fasync(int fd, struct file *file, int on)
{
    if (fasync_helper(fd, file, on, &button_fasync) >= 0)
        return 0;
    else
        return -EIO;
}

当应用程序设置异步标志时调用,内核负责管理异步通知链表。中断处理函数中通过 kill_fasync 发送信号。

3.4 中断处理函数

c

复制代码
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
    struct gpio_desc *gpio_desc = dev_id;
    int val;
    int key;

    printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);

    val = gpio_get_value(gpio_desc->gpio);
    key = (gpio_desc->key) | (val << 8);
    put_key(key);
    wake_up_interruptible(&gpio_wait);
    kill_fasync(&button_fasync, SIGIO, POLL_IN);

    return IRQ_HANDLED;
}

执行流程

  1. dev_id 获取 GPIO 描述结构体。
  2. 读取当前引脚电平 val
  3. 打包 key 值:低 8 位为 gpio_desc->key(此处为 0),高 8 位为 val
  4. key 存入环形缓冲区。
  5. 唤醒等待队列上的睡眠进程。
  6. 向设置了异步通知的进程发送 SIGIO 信号。

为什么在中断中做这么多事?

因为 SR501 输出的是稳定数字信号,无需软件消抖,直接在中断里完成数据采集和通知可保证最佳实时性。

3.5 模块初始化函数

c

复制代码
static int __init gpio_drv_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios) / sizeof(gpios[0]);

    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    for (i = 0; i < count; i++) {
        // 1. 申请 GPIO
        err = gpio_request(gpios[i].gpio, gpios[i].name);
        if (err) {
            printk(KERN_ERR "can not request gpio %s %d\n", gpios[i].name, gpios[i].gpio);
            return -ENODEV;
        }
        // 2. 设置为输入
        gpio_direction_input(gpios[i].gpio);
        // 3. 获取中断号
        gpios[i].irq = gpio_to_irq(gpios[i].gpio);
        // 4. 注册中断
        err = request_irq(gpios[i].irq, gpio_key_isr,
                          IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                          gpios[i].name, &gpios[i]);
        if (err) {
            printk(KERN_ERR "request_irq failed for %s\n", gpios[i].name);
            gpio_free(gpios[i].gpio);
            return err;
        }
    }

    // 注册字符设备
    major = register_chrdev(0, "100ask_gpio_key", &gpio_key_drv);
    // 创建类
    gpio_class = class_create(THIS_MODULE, "100ask_gpio_key_class");
    if (IS_ERR(gpio_class)) {
        unregister_chrdev(major, "100ask_gpio_key");
        return PTR_ERR(gpio_class);
    }
    // 创建设备节点
    device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "sr501");

    return 0;
}

关键步骤说明

  • gpio_request:向内核申请 GPIO 资源,防止冲突。
  • gpio_direction_input:将引脚配置为输入模式(必须,否则无法作为中断源)。
  • gpio_to_irq:将 GPIO 编号转换为中断号。
  • request_irq:注册中断处理函数,触发方式为双边沿(上升沿和下降沿都触发)。
  • register_chrdev:注册字符设备,动态分配主设备号。
  • class_create + device_create:创建类与设备节点,触发 udev/mdev 自动创建设备文件。

3.6 模块退出函数

c

复制代码
static void __exit gpio_drv_exit(void)
{
    int i;
    int count = sizeof(gpios) / sizeof(gpios[0]);

    device_destroy(gpio_class, MKDEV(major, 0));
    class_destroy(gpio_class);
    unregister_chrdev(major, "100ask_gpio_key");

    for (i = 0; i < count; i++) {
        free_irq(gpios[i].irq, &gpios[i]);
        gpio_free(gpios[i].gpio);
    }
}

逆序释放资源:先销毁设备节点,再销毁类,然后注销字符设备,最后释放中断和 GPIO 资源。


4. 测试程序代码及说明

c

复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

static int fd;

int main(int argc, char **argv)
{
    int val;
    int i;

    if (argc != 2) {
        printf("Usage: %s <dev>\n", argv[0]);
        return -1;
    }

    fd = open(argv[1], O_RDWR);
    if (fd == -1) {
        printf("can not open file %s\n", argv[1]);
        return -1;
    }

    while (1) {
        if (read(fd, &val, 4) == 4)
            printf("get button: 0x%x\n", val);
        else
            printf("get button: -1\n");
    }

    close(fd);
    return 0;
}

程序逻辑

  1. 接收设备路径参数(如 /dev/sr501)。
  2. 以阻塞方式打开设备文件。
  3. 循环调用 read 读取 4 字节数据,打印十六进制值。
  4. 当传感器状态变化时,驱动唤醒 read,打印对应数据。

输出示例

text

复制代码
get button: 0x100   // 有人,高电平
get button: 0x0     // 无人,低电平

5. 编译与加载流程

5.1 Makefile 编写

makefile

复制代码
KERNEL_DIR := /home/book/100ask_imx6ull-sdk/Linux-4.9.88
CURRENT_DIR := $(shell pwd)
obj-m := gpio_drv.o

all:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
	arm-buildroot-linux-gnueabihf-gcc -o button_test button_test.c

clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
	rm -rf modules.order button_test
  • obj-m:指定内核模块目标。
  • make modules:在内核源码树下编译外部模块。
  • 测试程序使用交叉工具链编译。

5.2 编译驱动与测试程序

在驱动源码目录执行:

bash

复制代码
make clean
make

生成 gpio_drv.kobutton_test

5.3 加载模块与设备节点创建

将文件传输到开发板后:

bash

复制代码
insmod gpio_drv.ko           # 加载模块
dmesg | tail -20             # 查看初始化日志
ls /dev/sr501                # 检查设备节点
./button_test /dev/sr501     # 运行测试

如果节点未自动创建,手动创建:

bash

复制代码
cat /proc/devices | grep gpio   # 获取主设备号,如 248
mknod /dev/sr501 c 248 0

6. 实战中遇到的错误及解决方案

6.1 模块版本不匹配

错误信息

text

复制代码
led_drv: disagrees about version of symbol module_layout

原因:编译模块的内核源码版本与开发板运行的内核版本不一致。

解决 :确保 KERNEL_DIR 指向与开发板内核完全匹配的源码树,重新编译。

6.2 GPIO 被配置为输出无法申请中断

错误信息

text

复制代码
gpiochip_lock_as_irq: tried to flag a GPIO set as output for IRQ
genirq: Failed to request resources for sr501

原因 :驱动在 request_irq 之前没有调用 gpio_direction_input 将引脚设为输入模式,或者该引脚在设备树中被复用为其他功能(如 CSI),导致 GPIO 子系统无法将其用于中断。

解决

  1. gpio_request 后立即调用 gpio_direction_input
  2. 如果问题依旧,换一个空闲 GPIO(如 GPIO116)测试,确认驱动代码正确后,再研究设备树修改。

6.3 sysfs 类目录重复导致加载失败

错误信息

text

复制代码
sysfs: cannot create duplicate filename '/class/100ask_gpio_key_class'

原因:之前加载驱动时创建了类目录,但因中途失败未能正确销毁,再次加载时重复创建导致冲突。

解决

  1. 重启开发板(sysfs 是内存文件系统,重启后清空)。
  2. 或手动删除残留目录:rm -rf /sys/class/100ask_gpio_key_class(需要 root 权限)。

6.4 传感器预热与硬件接触问题

现象:驱动加载成功,中断注册成功,但测试程序无输出,中断计数为 0。

排查过程

  1. 通过 sysfs 读取 GPIO 电平:cat /sys/class/gpio/gpio115/value,发现电平会变化,说明传感器工作正常。
  2. 用杜邦线手动触碰 3.3V 和 GND,测试程序立即有输出,证明中断功能正常。
  3. 重新插拔传感器接线,等待一段时间(SR501 需预热),再次测试,成功。

结论:SR501 上电后有初始化时间(约 30~60 秒),期间输出不稳定;同时杜邦线可能存在接触不良,重新插拔后恢复正常。


7. 驱动与应用程序流程图

驱动初始化流程

text

复制代码
模块加载
    │
    ▼
gpio_drv_init()
    │
    ├─► gpio_request() 申请 GPIO
    ├─► gpio_direction_input() 配置输入
    ├─► gpio_to_irq() 获取中断号
    ├─► request_irq() 注册中断
    │
    ├─► register_chrdev() 注册字符设备
    ├─► class_create() 创建类
    └─► device_create() 创建设备节点 /dev/sr501

中断处理与数据流

text

复制代码
硬件电平跳变
    │
    ▼
gpio_key_isr()
    │
    ├─► gpio_get_value() 读取电平
    ├─► 打包 key 值
    ├─► put_key() 存入环形缓冲区
    ├─► wake_up_interruptible() 唤醒睡眠进程
    └─► kill_fasync() 发送异步信号

应用程序执行流程

text

复制代码
开始
    │
    ├─► open("/dev/sr501", O_RDWR)
    │
    └─► while (1)
            │
            ├─► read(fd, &val, 4)  阻塞等待数据
            │
            └─► printf("get button: 0x%x\n", val)

8. 知识巩固:10 道简答题及答案

Q1:gpio_requestgpio_direction_input 的作用分别是什么?
A1: gpio_request 向内核申请独占使用某个 GPIO,防止冲突;gpio_direction_input 将 GPIO 设置为输入模式,只有输入模式才能正确读取外部电平并产生中断。

Q2:为什么中断处理函数中要调用 wake_up_interruptiblekill_fasync
A2: wake_up_interruptible 唤醒因 wait_event_interruptible 睡眠的进程,使其能继续执行 read 读取数据;kill_fasync 向设置了异步通知的进程发送 SIGIO 信号,告知数据可读。

Q3:环形缓冲区的作用是什么?
A3: 当传感器事件产生速度快于应用程序读取速度时,环形缓冲区可以暂存多个事件,避免数据丢失。它通过读写指针实现先进先出。

Q4:register_chrdev 第一个参数传 0 的含义是什么?
A4: 表示由内核动态分配一个未使用的主设备号,返回值即为主设备号。

Q5:设备节点 /dev/sr501 是如何自动创建的?
A5: 驱动中调用 class_create 创建类,再调用 device_create 创建设备并发送 uevent 事件;用户空间的 mdevudev 监听该事件,自动在 /dev 下创建节点。

Q6:中断触发方式 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING 表示什么?
A6: 表示上升沿和下降沿都会触发中断。对于 SR501,有人时输出上升沿,无人时输出下降沿,使用双边沿触发可以捕获所有状态变化。

Q7:copy_to_usercopy_from_user 为什么不能省略?
A7: 用户空间和内核空间内存隔离,直接访问用户指针可能导致内核崩溃。这两个函数会进行地址合法性检查,确保数据安全传输。

Q8:驱动退出时释放资源的顺序为什么必须与初始化相反?
A8: 因为资源间存在依赖关系。例如,先销毁设备节点再销毁类,否则可能出现空指针;先释放中断再释放 GPIO,防止正在处理的中断访问已释放的 GPIO。

Q9:测试程序中 read(fd, &val, 4) 返回 4 表示什么?
A9: 表示成功读取了 4 字节数据,即驱动打包的 key 值。若返回值小于 4,则可能是被信号中断或发生错误。

Q10:SR501 上电后为什么需要等待一段时间才能稳定输出?
A10: 传感器内部电路需要预热和自校准,通常需要 30~60 秒才能进入稳定工作状态。在此期间输出可能不稳定或不响应。


9. 总结与展望

通过本次项目,我们成功将一份复杂的按键驱动精简并改造成了适配 SR501 传感器的输入驱动,完整实现了 GPIO 中断、环形缓冲、等待队列、异步通知等内核机制。在调试过程中,我们排查了模块版本不匹配、GPIO 方向错误、类目录残留、传感器预热等实际问题,积累了宝贵的实战经验。

最终成果

  • 驱动代码稳定运行,中断响应及时。
  • 测试程序能准确打印传感器状态。
  • 硬件连接正确,传感器触发正常。

后续可扩展方向

  • 使用设备树描述硬件,避免硬编码 GPIO 编号。
  • 实现 sysfs 属性接口,方便用户空间配置触发边沿、消抖时间等。
  • 支持多个传感器同时工作(如 GPIO116、GPIO117 等)。

希望本文能为 Linux 驱动初学者提供清晰的指引,助力大家快速上手嵌入式开发!

相关推荐
正点原子2 小时前
【正点原子Linux连载】第三章 U-Boot使用 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南
linux·运维·驱动开发
Qbw20042 小时前
【Linux】进程地址空间
linux·c++
忍冬行者2 小时前
MongoDB 三节点副本集离线部署运维手册
运维·数据库·mongodb
爱学习的小囧2 小时前
ESXi VMkernel 端口 MTU 最佳设置详解
运维·服务器·网络·php·虚拟化
jamon_tan3 小时前
linux下lvgl8.3动态库编译
linux
Elastic 中国社区官方博客4 小时前
Elastic Security、Observability 和 Search 现在在你的 AI 工具中提供交互式 UI
大数据·运维·人工智能·elasticsearch·搜索引擎·安全威胁分析·可用性测试
星辰_mya5 小时前
OSI 七层模型之“跨国诈骗集团”深度讲解
运维·服务器·后端·面试·架构师
贝锐5 小时前
如何破解商用安卓无人值守运维痛点,向日葵赋能数字化高效转型
运维
子牙老师5 小时前
软件虚拟化 vs 硬件虚拟化
linux·性能优化·云计算