Linux驱动中的红外遥控子系统

本文主要讲解Linux驱动中的红外遥控子系统

红外遥控子系统

Linux 内核中仍然保留并持续维护红外遥控(IR Remote Control)子系统,它是 Linux 媒体子系统(Media Subsystem)的核心组成部分之一,并非废弃功能,且在嵌入式、工控、消费电子等场景中广泛应用。

一、子系统的核心定位与现状

  • 归属与代码路径 核心代码位于内核 drivers/media/rc/ 目录下(Linux 2.6 + 至今均存在,最新 6.x/7.x 内核仍在迭代),依赖 CONFIG_RC_CORE 配置项开启,是 Linux 统一管理红外遥控设备(接收器 / 发射器)的标准框架。

  • 维护状态该子系统由 Linux Media Maintainer 团队(如 Hans Verkuil、Mauro Carvalho Chehab)持续维护,近年还在迭代:

    • 新增对更多红外协议的支持(如 RC-MM、Apple Remote);
    • 优化与 Input 子系统、设备树(DT)的适配;
    • 支持基于 GPIO/PCI/USB 等不同硬件形态的红外设备。
  • 核心能力依然覆盖红外遥控的全流程:

    • 硬件抽象:封装红外接收 / 发射硬件(GPIO 红外模块、专用红外芯片如 Cirrus Logic CX23885);
    • 协议解码 / 编码:支持 NEC、RC5/RC6、Sony、JVC 等主流红外协议;
    • 事件上报:将红外按键事件转换为标准 Input 子系统事件(如KEY_POWER),用户空间可通过/dev/input/eventX/dev/rcX访问;
    • 设备树适配:支持通过 DT 配置红外设备的 GPIO、载波频率、协议等参数。

二、启用与验证方法

1. 内核配置(开启子系统)

编译内核时需开启以下关键配置(以 menuconfig 为例):

复制代码
Device Drivers →
  Multimedia support →
    Remote Controller support (RC_CORE [=y]) →
      <*> Remote controller core support
      <*>   IR raw decoder/encoder support
      <*>   IR protocol decoders (如NEC、RC5/RC6、Sony等)
      <*>   IR transmitter support (如需红外发射功能)
      <*>   GPIO IR receiver/transmitter support (GPIO红外模块必选)

2. 验证子系统是否工作

  • 查看已加载的红外设备

    复制代码
    ls /dev/rc*  # 输出/dev/rc0、/dev/rc1等,代表红外设备已注册
    ls /sys/class/rc/  # 查看红外设备的sysfs节点
  • 查看红外事件

    复制代码
    evtest /dev/input/eventX  # 替换X为红外设备对应的event号,按遥控按键可看到事件输出
  • 查看支持的协议

    复制代码
    cat /sys/class/rc/rc0/protocols  # 输出当前支持的红外协议(如nec rc6)

三、典型应用场景

  1. 嵌入式设备:树莓派、STM32/ARM 工控板通过 GPIO 红外模块(如 HS0038 接收器、红外 LED 发射器),基于该子系统实现遥控功能;
  2. 机顶盒 / 电视棒:如 Android TV、树莓派电视盒子,依赖该子系统处理红外遥控;
  3. 工控 / 车载系统:Linux 车载系统通过红外遥控子系统适配车载红外设备;
  4. USB 红外适配器:USB 红外遥控棒(如 ITE IT8772)接入 Linux 主机后,自动适配该子系统。

四、常见误区:"子系统是否被替代?"

  • 不存在 "替代" 说法:红外遥控子系统是 Linux 处理红外设备的唯一标准框架,没有其他内核子系统替代它;
  • 仅部分小众场景(如极简嵌入式)会直接操作 GPIO 模拟红外协议,但这并非 "替代子系统",而是绕开框架的极简实现(无通用性)。

总结

Linux 红外遥控子系统不仅存在,且是处理红外遥控设备的标准、主流方案,适用于绝大多数 Linux 系统(桌面、嵌入式、工控)。只要你的硬件是红外遥控相关设备(接收器 / 发射器),基于该子系统开发驱动 / 应用是最合规、可移植的选择。
drivers/media这个目录是啥?

drivers/media/ 是 Linux 内核中多媒体子系统(Linux Media Subsystem) 的核心代码目录,负责管理所有与音视频、图像、红外遥控相关的硬件设备驱动,是 Linux 统一管理多媒体硬件的标准化框架,覆盖从嵌入式到桌面 / 服务器的全场景多媒体设备。

一、核心定位与设计目标

  • 归属 :Linux 内核设备驱动层(drivers/),由 Linux Media Maintainers 社区维护;
  • 核心目标:提供统一的硬件抽象层(HAL),让不同厂商的多媒体设备(如摄像头、电视卡、红外遥控)遵循相同的内核接口,降低驱动开发和应用层适配成本;
  • 关键特性:兼容 V4L2(Video for Linux 2)、DVB(数字电视)、RC(红外遥控)等核心子规范,是 Linux 处理音视频采集 / 输出、红外控制的 "统一入口"。

二、目录结构与核心子模块

drivers/media/ 内部按设备类型拆分,核心子目录如下(基于 Linux 6.x/7.x 内核):

子目录 核心功能 典型设备 / 文件
rc/ 红外遥控(Remote Control)子系统 红外接收器 / 发射器驱动(如 gpio-ir.c)、协议解码(ir-nec-decoder.c)、struct rc_dev 核心定义
video/ 视频设备驱动(V4L2 核心) 摄像头(UVC USB 摄像头、CSI 摄像头)、视频采集卡、视频编码 / 解码芯片(如 v4l2-dev.cvideobuf2/ 缓冲区管理)
dvb/ 数字电视(Digital Video Broadcasting) 卫星 / 有线 / 地面数字电视接收卡、DVB 协议栈(如 dvb-core.cfrontends/ 调谐器驱动)
radio/ 射频 / 广播设备 调频收音机、软件无线电(SDR)设备驱动
usb/ USB 多媒体设备 USB 摄像头(uvcvideo)、USB 电视棒、USB 红外适配器
pci/ PCI/PCIe 多媒体设备 台式机视频采集卡(如 CX23885)、PCIe 电视卡
i2c/ I2C 接口多媒体外设 摄像头传感器(如 OV5640)、音视频解码芯片(如 TW9910)
common/ 多媒体子系统通用工具 共享的缓冲区管理、设备树适配、事件上报接口
platform/ 平台总线多媒体设备 嵌入式 SoC 内置的多媒体控制器(如树莓派 CSI/DSI、ARM SoC 的视频编码模块)

三、核心子系统与关键规范

drivers/media/ 封装了多个核心子系统,其中最常用的是:

V4L2(Video for Linux 2)

  • 核心文件:video/v4l2-dev.cvideo/v4l2-ioctl.c
  • 作用:统一管理视频采集 / 输出设备(摄像头、视频卡),提供标准的 ioctl 接口(如 VIDIOC_QUERYCAPVIDIOC_STREAMON),应用层可通过 /dev/videoX 访问;

RC(Remote Control)

  • 核心文件:rc/rc-core.crc/ir-nec-decoder.c
  • 作用:管理红外遥控设备,如前文提到的 struct rc_dev 就定义在此;

DVB

  • 核心文件:dvb/dvb-core.c
  • 作用:支持数字电视接收设备,兼容 DVB-T/C/S 等标准。

四、典型使用场景

  1. 嵌入式摄像头 :树莓派 / ARM 开发板的 CSI 摄像头,驱动位于 media/i2c/,通过 V4L2 接口提供视频采集;
  2. USB 摄像头 :普通 USB 摄像头的驱动 uvcvideo 位于 media/usb/uvc/,是 V4L2 的经典实现;
  3. 红外遥控 :GPIO 红外接收器驱动位于 media/rc/gpio-ir.c,依赖 RC 子系统上报按键事件;
  4. 电视卡 / 电视棒 :USB 数字电视棒驱动位于 media/usb/dvb-usb/,兼容 DVB 标准。

五、内核配置关联

编译内核时,需开启 CONFIG_MEDIA_SUPPORT(多媒体总开关),再按需开启子模块:

复制代码
Device Drivers →
  Multimedia support (MEDIA_SUPPORT [=y]) →
    # 按需选择子模块:
    Video capture adapters (V4L2核心)
    Remote Controller support (红外遥控)
    Digital Video Broadcasting (DVB)
    Media USB Adapters (USB多媒体设备)

总结

drivers/media/ 是 Linux 多媒体硬件的 "总管家",涵盖视频、音频、红外、数字电视等所有多媒体设备的驱动框架,是嵌入式 / 桌面 Linux 开发中处理摄像头、红外遥控、电视卡等设备的核心依赖。
为什么红外会归类在多媒体子系统里面?

红外遥控(RC)归类到 Linux 多媒体(Media)子系统,核心是历史依赖、硬件集成、功能协同、框架复用四大原因,并非单纯的分类问题,而是内核架构演进与实际应用需求的必然选择。以下从技术与历史角度详细说明:


一、历史起源:从电视卡到统一框架

  1. 最初的硬件绑定早期红外遥控的核心应用场景是电视卡、视频采集卡(如 CX23885)------ 这些设备本身就是多媒体设备,红外接收器常作为板载附属模块出厂。当时内核中没有独立的 "遥控子系统",红外驱动自然与视频采集、DVB(数字电视)驱动一起放在多媒体代码路径下,形成 "多媒体设备 + 遥控" 的绑定开发模式。
  2. 子系统整合与标准化 2000 年后 LinuxTV 项目推动多媒体子系统统一,将分散的视频、DVB、遥控等功能整合到drivers/media/,并引入RC_CORE作为红外遥控的核心框架,替代早期用户态 LIRC 的碎片化实现,形成内核态统一管理的规范。

二、硬件与功能的强耦合性

  1. 物理集成 多数红外接收器并非独立外设,而是与多媒体硬件(电视卡、机顶盒芯片、摄像头模组)物理集成或共享总线(I2C/SPI),例如:
    • 电视卡的红外接收器与调谐器、视频解码器共用 PCIe/USB 接口与中断;
    • 嵌入式 SoC 的红外模块与多媒体控制器(如 CSI/DSI)共享 GPIO 或 DMA 资源。
  2. 功能协同 红外遥控的核心用途是控制多媒体设备 (电视、机顶盒、投影仪、播放器),其按键事件(如KEY_POWERKEY_VOLUMEUP)与多媒体操作强关联,需与 V4L2/DVB 子系统协同工作(如遥控唤醒视频采集、切换频道)。

三、内核框架复用:避免重复造轮子

多媒体子系统提供的核心能力,恰好适配红外遥控的需求,无需重新设计独立框架:

复用能力 红外遥控的应用场景
统一设备模型 复用media_deviceentity架构,抽象红外接收器 / 发射器为标准设备节点(/dev/rcX
协议处理框架 直接复用多媒体子系统的协议解码 / 编码逻辑,支持 NEC、RC5/RC6 等红外协议,无需单独开发协议栈The Linux Kernel Archives
事件上报机制 借助 Input 子系统接口,将红外按键事件转为标准KEY_*事件,应用层通过/dev/input/eventX统一读取
缓冲区管理 复用videobuf2等通用缓冲区机制,处理红外原始脉冲数据(raw IR)的采集与传输
设备树适配 共享多媒体子系统的 DT 绑定规范(如rc.yaml),统一配置 GPIO、载波频率、协议等参数

四、与 IrDA 的区别:避免子系统混淆

需要注意,红外遥控(RC)与红外数据通信(IrDA,用于设备间文件传输)是完全独立的功能:

  • IrDA :属于net/irda/目录,归网络子系统,核心是数据传输(如 IrCOMM、IrOBEX 协议);
  • RC 红外遥控 :属于media/rc/,归多媒体子系统,核心是按键事件处理,二者定位不同,避免功能冲突。

五、统一管理的优势

  1. 降低维护成本:由 Linux Media Maintainer 团队统一维护,避免跨子系统协调;
  2. 驱动开发高效:开发者可直接复用多媒体子系统的硬件抽象、电源管理、中断处理等代码;
  3. 应用层兼容 :用户态通过/dev/rcX/dev/input/eventX访问,无需适配不同子系统的接口。

总结

红外遥控归类到多媒体子系统,是历史路径、硬件集成、功能协同、框架复用共同作用的结果,而非随意分类。这种架构既符合 "控制多媒体设备" 的核心场景,又能复用成熟的内核框架,是 Linux 内核中 "高内聚、低耦合" 设计理念的典型体现。
LIRC是啥意思?

LIRCLinux Infrared Remote Control 的缩写,中文译为 Linux 红外遥控工具集 ,是一套用于在 Linux 系统上处理红外遥控信号的用户态工具链 + 驱动框架,也是早期 Linux 红外遥控领域的主流解决方案。

一、LIRC 的核心定位与作用

核心功能

  • 红外信号的接收与发射:支持各类红外接收器(如 GPIO 红外模块、USB 红外棒)和发射器,能采集原始红外脉冲数据,也能将编码后的信号转为红外光发射。
  • 协议解码与编码:内置对 NEC、RC5、RC6、Sony 等主流红外协议的支持,可将原始脉冲数据解析为按键码,也能将按键码编码为红外脉冲。
  • 按键映射与事件转发 :通过配置文件将红外按键码映射为系统按键事件(如 KEY_POWER),或转发给特定应用(如媒体播放器、机顶盒程序)。

与内核红外遥控子系统(rc-core)的关系 两者是 "用户态工具" 与 "内核态框架" 的互补关系,也存在一定的演进替代关系:

特性 LIRC 内核 rc-core 子系统
运行层级 用户态(用户空间程序) 内核态(内核驱动框架)
核心依赖 可直接操作硬件,或依赖内核驱动 内核原生框架,与 Input 子系统深度整合
设备节点 /dev/lircX /dev/rcX + /dev/input/eventX
演进趋势 早期主流方案,目前多用于老旧设备或特殊场景 现代 Linux 红外遥控的标准框架,逐步替代 LIRC

二、LIRC 的典型组成

  • 内核驱动模块 早期 LIRC 提供了一批内核驱动(如 lirc_gpiolirc_serial),用于适配各类红外硬件,直接操作 GPIO、串口等接口采集 / 发射红外信号,生成 /dev/lircX 设备节点。
  • 用户态工具
    • irw:监听红外按键事件,打印按键码与协议信息,用于调试。
    • irrecord:录制红外遥控的按键信号,生成自定义按键映射配置文件(.lircrc)。
    • irsend:发送红外信号,可模拟遥控器按键操作。
    • mode2:查看红外原始脉冲数据(脉冲 / 间隙时长),用于协议分析。
  • 配置文件
    • /etc/lirc/lircd.conf:全局硬件与协议配置,定义红外设备的参数(如载波频率、采样率)。
    • ~/.lircrc:用户自定义按键映射,指定某按键码对应触发的系统动作或应用命令。

三、LIRC 的适用场景

  1. 老旧硬件 / 系统 :对于没有内核 rc-core 支持的老式红外设备(如串口红外模块),LIRC 是唯一可行的方案。
  2. 自定义协议需求 :当需要处理小众红外协议,而内核 rc-core 未支持时,可通过 LIRC 直接解析原始脉冲数据,实现自定义解码。
  3. 特定应用适配:部分老旧媒体应用(如早期 Kodi 版本)仅支持 LIRC 接口,需依赖 LIRC 转发红外事件。

四、LIRC 与内核 rc-core 的选择建议

  • 新设备 / 现代系统 :优先使用内核 rc-core 子系统,无需额外安装 LIRC,直接通过 Input 子系统获取标准按键事件,兼容性更好。
  • 老旧设备 / 特殊协议:使用 LIRC,借助其用户态工具链实现灵活的红外信号处理。

相关目录和文件

红外子系统涉及到的目录和文件?

Linux 红外遥控(RC)子系统的核心代码集中在 drivers/media/rc/ 目录下,同时关联内核其他目录的通用组件(如 Input 子系统、设备树绑定)。以下是按功能分类的核心目录 + 关键文件,覆盖从驱动框架、协议解码到硬件适配的全链路(基于 Linux 6.x/7.x 内核):

一、核心目录结构(按功能划分)

目录路径 核心功能 关键说明
drivers/media/rc/ 红外遥控子系统核心 包含框架、协议、硬件驱动、工具函数
drivers/media/rc/keymaps/ 红外按键映射表 预设不同遥控器的按键码映射(如 NEC/RC6 通用映射)
drivers/media/usb/ USB 红外适配器驱动 如 USB 红外棒(ITE IT8772)、USB 电视棒集成的红外
drivers/media/i2c/ I2C 红外芯片驱动 如专用红外解码芯片(TI IR3688)
drivers/media/platform/ 平台总线红外驱动 嵌入式 SoC 内置红外控制器(如树莓派、全志 / 瑞芯微 SoC)
include/media/ 头文件定义 红外子系统的公共接口、结构体、宏定义
Documentation/devicetree/bindings/rc/ 设备树绑定文档 红外设备的 DT 配置规范(YAML/TXT 格式)
samples/media/rc/ 示例代码 红外子系统使用示例(如自定义按键映射、raw 数据处理)

二、核心文件详解(按功能优先级)

1. 子系统框架核心(drivers/media/rc/

文件名 核心作用 关键内容
rc-core.c 红外子系统主框架 实现 rc_dev 分配 / 注册 / 注销(rc_allocate_device()/rc_register_device())、设备模型管理、事件上报核心逻辑
rc-main.c 核心工具函数 协议匹配、按键事件转换、sysfs 节点创建(如 /sys/class/rc/rc0/protocols
rc-raw.c 原始红外数据处理 管理 raw 脉冲 / 间隙数据缓冲区、raw 事件存储与解码触发
rc-input.c 与 Input 子系统联动 将红外按键码转为标准 Input 事件(KEY_POWER/KEY_VOLUMEUP),绑定 input_dev

2. 红外协议解码 / 编码(drivers/media/rc/

文件名 支持协议 关键说明
ir-nec-decoder.c NEC 协议(最常用) 解码 NEC 红外脉冲(38kHz 载波、地址码 + 数据码)
ir-rc5-decoder.c RC5 协议 飞利浦 RC5 协议解码(双向通信、14 位编码)
ir-rc6-decoder.c RC6 协议 RC6 协议(扩展位、多种子版本)
ir-sony-decoder.c Sony SIRC 协议 索尼 12/15/20 位编码协议
ir-jvc-decoder.c JVC 协议 JVC 红外协议(可变长度编码)
ir-raw-event.c 通用 raw 事件处理 脉冲 / 间隙时长解析、协议匹配分发
ir-tx-core.c 红外发射核心 红外发射硬件抽象、编码后数据下发

3. 硬件适配驱动(drivers/media/rc/

文件名 适配硬件类型 关键说明
gpio-ir.c GPIO 红外接收器 通用 GPIO 红外接收模块(如 HS0038)驱动,基于中断采集脉冲
gpio-ir-tx.c GPIO 红外发射器 通用 GPIO 红外发射模块驱动,输出调制后的红外脉冲
rc-loopback.c 环回测试驱动 红外信号环回测试(接收→发射),用于调试
rc-rockchip.c 瑞芯微 SoC 红外 瑞芯微 RK3399/RK3568 内置红外控制器驱动
rc-stm32.c STM32 红外控制器 STM32 单片机内置红外解码 / 编码外设驱动

4. 按键映射表(drivers/media/rc/keymaps/

文件名 映射场景 关键说明
rc-map-nec.c NEC 协议通用映射 将 NEC 按键码(如 0x00ff00ff)映射为 KEY_POWER 等标准码
rc-map-rc6.c RC6 协议通用映射 RC6 协议按键码与标准输入事件的映射
rc-map-default.c 默认通用映射 未指定映射表时的兜底按键映射
rc-map-sony.c Sony 协议映射 索尼遥控器专用映射

5. 头文件(include/media/

头文件路径 核心定义 关键内容
include/media/rc-core.h 核心结构体 / API struct rc_devstruct rc_opsrc_allocate_device() 等核心接口
include/media/rc-protocols.h 协议枚举 / 宏 红外协议枚举(RC_PROTO_NEC/RC_PROTO_RC6)、协议掩码定义
include/media/rc-map.h 按键映射接口 按键映射表的注册 / 注销函数(rc_map_register()
include/media/ir-raw-event.h Raw 事件定义 struct rc_raw_event(脉冲 / 间隙事件)、解码回调定义

6. 设备树绑定文档(Documentation/devicetree/bindings/rc/

文件名 绑定规范 关键说明
rc.yaml 红外设备通用绑定 定义红外设备的 DT 属性(compatiblegpioscarrier-frequency
gpio-ir-receiver.yaml GPIO 红外接收器 _GPIO 红外接收模块的 DT 配置规范(如 gpios 指向接收引脚)
gpio-ir-transmitter.yaml GPIO 红外发射器 GPIO 红外发射模块的 DT 配置(如 tx-gpioscarrier-frequency
rockchip,ir.yaml 瑞芯微 SoC 红外 瑞芯微内置红外控制器的 DT 绑定

7. 示例代码(samples/media/rc/

文件名 示例功能 适用场景
rc-example.c 红外设备注册示例 演示如何初始化 rc_dev、绑定协议、上报按键事件
rc-raw-example.c Raw 数据处理示例 自定义解析红外原始脉冲数据

三、关联的通用子系统文件

红外子系统依赖内核其他通用组件,关键关联文件:

路径 功能 关联点
drivers/input/input.c Input 子系统核心 红外按键事件最终通过 input_event() 上报
include/linux/input.h Input 事件定义 标准按键码(KEY_POWER/KEY_OK)定义
drivers/gpio/gpiolib.c GPIO 子系统 GPIO 红外模块的引脚控制(申请 / 配置中断)

四、快速查找技巧

  1. 找核心框架:直接看 rc-core.c(注册 / 注销)、rc-main.c(核心逻辑);
  2. 找协议解码:文件名含 ir-xxx-decoder.c(xxx 为协议名);
  3. 找硬件驱动:按硬件类型(gpio-ir.c/rc-rockchip.c);
  4. 找 DT 配置:Documentation/devicetree/bindings/rc/ 下的 YAML 文件;
  5. 找头文件:include/media/rc-*.h 覆盖所有核心接口。

struct rc_dev

struct rc_dev是啥?

struct rc_dev 是 Linux 内核红外遥控(RC)子系统 的核心数据结构,是驱动与内核交互的 "核心载体"------ 它封装了红外遥控设备(接收器 / 发射器)的硬件属性、功能接口、运行状态,并向上对接内核红外子系统、向下关联具体硬件驱动,是整个红外遥控功能的 "总控结构体"。

简单来说:所有红外遥控设备(不管是 GPIO 红外模块、USB 红外棒,还是 SoC 内置红外控制器),在 Linux 内核中都被抽象为一个 struct rc_dev 实例,内核通过操作这个结构体来管理红外设备的全生命周期。

所在位置include/media/rc-core.h

一、核心定位与设计目的

维度 具体说明
所属子系统 drivers/media/rc/(红外遥控子系统),依赖 CONFIG_RC_CORE 内核配置
核心作用 1. 抽象红外设备的硬件能力(支持的协议、载波频率、分辨率);2. 绑定驱动回调(中断处理、发射控制、参数配置);3. 向上暴露统一接口(如 /dev/rcX 设备节点);4. 联动 Input 子系统,将红外按键转为标准输入事件(如 KEY_POWER
关键关联 依赖 struct input_dev(输入子系统)、struct rc_ops(硬件操作接口)、struct rc_raw_buffer(原始脉冲缓冲区)

二、核心成员(按功能分类,Linux 6.x/7.x 内核)

以下是 struct rc_dev 中最常用的核心字段,按 "硬件属性 - 功能接口 - 运行状态" 分类:

1. 硬件属性(描述设备能力)

成员名 类型 作用说明
name char[32] 设备名称(如 "gpio-ir-receiver"),用于 sysfs 标识(/sys/class/rc/rc0/name
driver_name char[32] 驱动名称(如 "gpio-ir"),关联具体驱动模块
hor_res/ver_res u32 兼容视频子系统的分辨率(红外设备一般无需设置)
allowed_protocols u64 支持的红外协议掩码(如 `RC_PROTO_BIT_NEC RC_PROTO_BIT_RC6`)
tx_carrier u32 发射载波频率(默认 38000Hz,红外遥控标准载波)
tx_carrier_range struct rc_carrier_range 发射载波频率范围(如 30k~40kHz)
rx_resolution u32 接收分辨率(单位 ns,默认 1000ns=1μs,即脉冲采样精度)
rx_filter_size u32 接收脉冲滤波窗口大小(过滤噪声)

2. 功能接口(绑定驱动回调)

成员名 类型 作用说明
ops struct rc_ops * 硬件操作接口(核心!绑定中断处理、发射控制、参数配置等回调)
input_dev struct input_dev * 关联的 Input 子系统设备,红外按键事件最终通过它上报(如 /dev/input/eventX
raw_decode bool 是否启用原始脉冲解码(开启后可自定义协议解析)
s_tx_mask u32 发射掩码(控制多通道发射)

3. 运行状态(记录设备当前状态)

成员名 类型 作用说明
protocol enum rc_proto 当前激活的红外协议(如 RC_PROTO_NEC
devno dev_t 设备号(对应 /dev/rc0 等字符设备节点)
rc_raw struct rc_raw_buffer 原始红外脉冲缓冲区(存储未解码的脉冲 / 间隙数据)
map_name const char * 按键映射表名称(如 "rc-map-nec"),关联按键码与标准输入事件
priv void * 私有数据指针(驱动可存放硬件专属数据,如 GPIO 引脚信息)

三、核心配套结构体:struct rc_ops

rc_devops 成员指向 struct rc_ops,是驱动必须实现的 "硬件操作回调集合",核心定义如下:

复制代码
struct rc_ops {
    // 【接收】中断处理函数(GPIO红外接收的核心,电平变化时触发)
    irqreturn_t (*irq_handler)(int irq, void *dev_id);
    // 【发射】发送红外数据(输入按键码,输出调制后的脉冲)
    int (*tx_ir)(struct rc_dev *dev, unsigned int *txbuf, unsigned int len);
    // 【配置】设置红外设备参数(如载波频率、协议)
    int (*set_config)(struct rc_dev *dev, struct rc_config *config);
    // 【电源】挂起/恢复设备
    int (*suspend)(struct rc_dev *dev);
    int (*resume)(struct rc_dev *dev);
    // 【解码】自定义原始脉冲解码(替代内核默认协议解析)
    int (*raw_decode)(struct rc_dev *dev, struct rc_raw_event *ev);
};

四、rc_dev 的生命周期(驱动视角)

驱动中使用 rc_dev 的核心流程(以 GPIO 红外接收器为例):

复制代码
// 1. 分配rc_dev实例
struct rc_dev *rcdev = rc_allocate_device(RC_DRIVER_IR_RX); // RC_DRIVER_IR_RX=仅接收
if (!rcdev) return -ENOMEM;

// 2. 初始化硬件属性
rcdev->name = "gpio-ir-receiver";
rcdev->allowed_protocols = RC_PROTO_BIT_NEC | RC_PROTO_BIT_RC6;
rcdev->rx_resolution = 1000; // 1μs分辨率
rcdev->map_name = "rc-map-nec"; // 绑定NEC按键映射

// 3. 绑定回调函数
rcdev->ops->irq_handler = gpio_ir_irq_handler; // 中断处理
rcdev->priv = &gpio_ir_data; // 私有数据(GPIO引脚信息)

// 4. 注册到内核(生成/dev/rc0、/dev/input/eventX节点)
int ret = rc_register_device(rcdev);
if (ret) {
    rc_free_device(rcdev); // 注册失败释放
    return ret;
}

// 5. 驱动卸载时注销
rc_unregister_device(rcdev);
rc_free_device(rcdev);

五、关键总结

  1. struct rc_dev 是红外设备的 "抽象代表":不管硬件形态(GPIO/USB/SoC 内置),内核都通过它管理红外设备;
  2. 核心价值是 "统一接口":驱动只需实现 rc_ops 中的硬件回调,无需关心上层事件上报、协议匹配等逻辑;
  3. 与 Input 子系统联动:最终红外按键事件通过 input_dev 上报,应用层可通过 evtest 等工具直接读取,无需适配不同硬件。

不同版本略有差异,但总体是类似的。

收发功能流程

想熟悉一个gpio红外设备的收发功能实现,要怎么看?

要熟悉 Linux 内核中 GPIO 红外设备(接收 + 发射)的收发功能实现,核心是按 "硬件驱动→子系统框架→协议编解码→事件交互" 的链路拆解,从代码入口到关键逻辑逐步深挖。以下是分步骤的学习路径,结合具体文件、核心函数和调试方法,覆盖接收(RX)和发射(TX)全流程:

前置准备

  1. 确定核心文件 :GPIO 红外的核心驱动是 drivers/media/rc/gpio-ir.c(接收)和 drivers/media/rc/gpio-ir-tx.c(发射),先定位这两个文件;
  2. 内核配置 :确保开启 CONFIG_RC_CORECONFIG_GPIO_IRCONFIG_GPIO_IR_TX(menuconfig 路径:Device Drivers → Multimedia support → Remote Controller support → GPIO IR receiver/transmitter);
  3. 硬件背景 :了解 GPIO 红外的基础原理 ------
    • 接收(RX):红外接收器(如 HS0038)将红外光转为高低电平,通过 GPIO 中断触发,采集脉冲 / 间隙时长;
    • 发射(TX):通过 GPIO 输出 38kHz(默认)载波调制的高低电平脉冲,模拟红外遥控信号。

第一步:先看 GPIO 红外接收(RX)的实现(核心:gpio-ir.c)

接收是红外功能的基础,先吃透 RX 再看 TX,逻辑更顺。

1. 驱动入口与设备注册(gpio-ir.c)

复制代码
// 1. 驱动模块入口(平台驱动/设备树匹配)
static struct platform_driver gpio_ir_driver = {
    .probe      = gpio_ir_probe,  // 设备匹配成功后执行
    .remove     = gpio_ir_remove,
    .driver     = {
        .name   = "gpio-ir-receiver",
        .of_match_table = of_match_ptr(gpio_ir_of_match), // 设备树匹配表
    },
};
module_platform_driver(gpio_ir_driver);
  • 关键函数:gpio_ir_probe() ------ 驱动初始化的核心入口,重点看这几步:① GPIO 与中断配置 :申请红外接收引脚、配置为输入模式、注册中断(上升沿 / 下降沿触发);② rc_dev 初始化 :调用 rc_allocate_device() 创建 struct rc_dev,设置支持的协议(如 NEC/RC6)、接收分辨率(默认 1000ns);③ 绑定回调函数 :将中断处理函数 gpio_ir_irq_handler 绑定到 rc_dev,注册 rc_dev 到内核;

2. 中断处理(核心:采集红外脉冲)

复制代码
// 2. 中断处理函数(GPIO电平变化时触发)
static irqreturn_t gpio_ir_irq_handler(int irq, void *dev_id) {
    struct gpio_ir_data *data = dev_id;
    u64 now = ktime_get_ns();  // 获取当前时间(ns)
    bool level = gpiod_get_value(data->gpiod); // 读取GPIO电平

    // 计算脉冲/间隙时长(当前时间 - 上一次中断时间)
    u32 duration = now - data->last_event;
    data->last_event = now;

    // 将脉冲/间隙事件上报给rc子系统(raw事件)
    rc_raw_event_store(&data->rcdev, level, duration);
    rc_raw_event_handle(&data->rcdev); // 触发协议解码

    return IRQ_HANDLED;
}
  • 核心逻辑:
    • 每次 GPIO 电平变化(红外信号的 "脉冲"/"间隙")触发中断,记录电平状态和时长;
    • 通过 rc_raw_event_store() 将原始脉冲 / 间隙数据存入 rc_dev 的缓冲区;
    • 调用 rc_raw_event_handle() 触发子系统的协议解码(如 NEC 协议)。

3. 协议解码与事件上报

  • 解码逻辑:rc_raw_event_handle() 会调用 ir-raw-event.c 中的通用解码逻辑,根据脉冲时长匹配协议(如 NEC 的 9ms 引导码 + 4.5ms 间隔);
  • 事件上报:解码出按键码后,通过 rc_keydown()/rc_keyup() 转为 Input 子系统事件(如KEY_POWER),最终上报到/dev/input/eventX

第二步:再看 GPIO 红外发射(TX)的实现(核心:gpio-ir-tx.c)

发射是接收的反向流程,核心是 "编码协议→生成脉冲→GPIO 输出"。

1. 驱动初始化(gpio-ir-tx.c)

复制代码
static int gpio_ir_tx_probe(struct platform_device *pdev) {
    // ① 申请GPIO发射引脚(输出模式)
    data->gpiod = devm_gpiod_get(&pdev->dev, "tx", GPIOD_OUT_LOW);
    // ② 初始化rc_dev,设置发射属性(载波频率38000Hz、支持的协议)
    data->rcdev = rc_allocate_device(RC_DRIVER_IR_TX);
    data->rcdev->tx_carrier = 38000;
    data->rcdev->allowed_protocols = RC_PROTO_BIT_NEC;
    // ③ 绑定发射回调函数
    data->rcdev->ops->tx_ir = gpio_ir_tx_tx;
    // ④ 注册rc_dev
    return rc_register_device(data->rcdev);
}

2. 红外发射核心逻辑

复制代码
// 发射回调函数(上层调用rc_transmit()时触发)
static int gpio_ir_tx_tx(struct rc_dev *dev, unsigned int *txbuf, unsigned int len) {
    struct gpio_ir_tx_data *data = dev->priv;
    // ① 解析txbuf中的按键码,编码为红外脉冲序列(如NEC协议的脉冲/间隙)
    struct ir_raw_event *events;
    int count = ir_encode_raw(dev, txbuf, len, &events); // 协议编码
    // ② 生成38kHz载波调制的电平信号,通过GPIO输出
    gpio_ir_tx_emit(data, events, count);
    // ③ 释放资源
    kfree(events);
    return 0;
}

// 电平输出函数(核心:按脉冲时长翻转GPIO)
static void gpio_ir_tx_emit(struct gpio_ir_tx_data *data, struct ir_raw_event *events, int count) {
    for (int i = 0; i < count; i++) {
        gpiod_set_value(data->gpiod, events[i].level); // 设置GPIO电平
        udelay(events[i].duration / 1000); // 按时长延时(转为us)
    }
}
  • 关键逻辑:
    • 编码:ir_encode_raw() 将按键码(如 0x00ff00ff)转为符合 NEC/RC6 协议的脉冲 / 间隙序列;
    • 调制:通过 GPIO 快速翻转生成 38kHz 载波,叠加到脉冲序列上;
    • 输出:按脉冲时长控制 GPIO 电平,最终发射红外光。

第三步:梳理 "收发" 的核心链路

功能 核心流程 关键函数 / 文件
接收(RX) 硬件中断→采集脉冲→raw 事件存储→协议解码→Input 事件上报 gpio_ir_irq_handler() → rc_raw_event_store() → ir-nec-decoder.c → rc_keydown()
发射(TX) 按键码输入→协议编码→脉冲序列生成→GPIO 电平输出 rc_transmit() → gpio_ir_tx_tx() → ir_encode_raw() → gpio_ir_tx_emit()

第四步:调试验证(动手实操加深理解)

光看代码不够,通过调试能快速定位关键逻辑,推荐这几个方法:

1. 查看设备注册状态

复制代码
# 查看GPIO红外设备是否注册成功
ls /sys/class/rc/rc0/
cat /sys/class/rc/rc0/protocols  # 查看支持的协议(如nec rc6)
cat /sys/class/rc/rc0/name       # 应显示"gpio-ir-receiver"/"gpio-ir-transmitter"

2. 监听接收事件

复制代码
evtest /dev/input/eventX  # X为红外设备对应的event号
# 按遥控器按键,会输出如下事件(代表接收成功):
Event: time 1704361234.567890, type 1 (EV_KEY), code 116 (KEY_POWER), value 1 (按下)
Event: time 1704361234.678901, type 1 (EV_KEY), code 116 (KEY_POWER), value 0 (松开)

3. 测试发射功能

复制代码
# 安装ir-ctl工具(需编译v4l-utils)
ir-ctl -d /dev/rc0 -s nec:0x00ff00ff  # 发射NEC协议的0x00ff00ff按键码
# 用另一台设备的红外接收器验证是否能收到该信号

4. 内核打印调试

gpio-ir.c/gpio-ir-tx.c 的关键函数中添加 pr_debug()printk(),编译内核后查看日志:

复制代码
dmesg | grep gpio-ir  # 查看接收/发射的脉冲时长、电平状态等日志

第五步:进阶深挖(可选)

如果想更深入,可继续看这些关联逻辑:

  1. 协议编解码细节 :比如 ir-nec-decoder.c 中 NEC 协议的引导码、地址码、数据码解析逻辑;
  2. raw 事件处理rc-raw.c 中脉冲时长的校验、协议匹配的优先级;
  3. 设备树适配Documentation/devicetree/bindings/rc/gpio-ir-receiver.yaml 中 DT 属性(如gpioscarrier-frequency)如何映射到驱动中;
  4. 与 Input 子系统联动rc-input.c 中如何将红外按键码转为标准 Input 事件。

总结

GPIO 红外收发的核心是:

  • 接收:中断采集脉冲 + 协议解码 + 事件上报
  • 发射:协议编码 + 载波调制 + GPIO 输出

先吃透 gpio-ir.c/gpio-ir-tx.c 的 probe、中断 / 发射回调,再结合 rc-core 子系统的框架逻辑(rc-core.c/rc-raw.c),最后通过调试验证,就能完整掌握 GPIO 红外设备的收发实现。

实际使用

gpio红外子系统的发射和接收的使用流程?

GPIO 红外子系统的发射(TX)和接收(RX)使用流程可分为内核驱动层实现用户态使用两大维度,前者是驱动开发视角(适配硬件),后者是应用开发视角(调用功能)。以下按 "接收→发射" 的逻辑,结合具体代码、命令和步骤,完整梳理全流程:

前置基础

  • 硬件:GPIO 红外接收器(如 HS0038)/ 发射器(红外 LED),已接好 MCU / 开发板的 GPIO 引脚;
  • 内核配置:开启 CONFIG_RC_CORECONFIG_GPIO_IR(接收)、CONFIG_GPIO_IR_TX(发射);
  • 核心驱动:drivers/media/rc/gpio-ir.c(RX)、drivers/media/rc/gpio-ir-tx.c(TX);
  • 设备节点:驱动注册成功后生成 /dev/rcX(红外核心)、/dev/input/eventX(Input 事件)。

一、接收(RX)流程(驱动层 + 用户态)

接收是 "硬件中断采集脉冲→内核解码→用户态读取事件" 的过程,分驱动实现和用户态使用两步:

1. 驱动层:GPIO 红外接收的实现流程(核心:gpio-ir.c)

这是驱动开发者需要完成的核心步骤,最终向内核注册红外接收设备:

步骤 核心函数 / 操作 关键逻辑
① 设备树配置(可选) 编写 DT 节点 定义接收 GPIO 引脚、载波频率、支持的协议:dts<br>ir-receiver {<br> compatible = "gpio-ir-receiver";<br> gpios = <&gpio1 2 IRQ_TYPE_EDGE_BOTH>; // 接收引脚,双边沿中断<br> carrier-frequency = <38000>; // 38kHz载波<br>};<br>
② 驱动 probe 初始化 gpio_ir_probe() 1. 申请 GPIO 引脚 + 注册中断(devm_gpiod_get()+devm_request_irq());2. 分配rc_devrc_allocate_device(RC_DRIVER_IR_RX);3. 配置rc_dev:设置支持的协议(如 NEC/RC6)、接收分辨率(1000ns);4. 绑定中断回调:rcdev->ops->irq_handler = gpio_ir_irq_handler;5. 注册设备:rc_register_device(rcdev)
③ 中断处理(采集脉冲) gpio_ir_irq_handler() 1. GPIO 电平变化触发中断,读取当前电平(高 / 低);2. 计算脉冲 / 间隙时长(ktime_get_ns() - 上一次中断时间);3. 将原始事件存入缓冲区:rc_raw_event_store(rcdev, level, duration);4. 触发协议解码:rc_raw_event_handle(rcdev)
④ 内核协议解码 ir-nec-decoder.c 内核根据脉冲时长匹配协议(如 NEC 的 9ms 引导码 + 4.5ms 间隔),解析出按键码(如 0x00ff00ff);
⑤ 上报 Input 事件 rc_keydown()/rc_keyup() 将按键码转为标准 Input 事件(如KEY_POWER),通过rcdev->input_dev上报到/dev/input/eventX

2. 用户态:读取红外接收事件(3 种方式)

应用开发者无需关心底层中断,只需读取内核上报的事件,推荐 3 种常用方式:

方式 1:读取 Input 事件(最通用,推荐)

通过/dev/input/eventX读取标准按键事件,兼容所有红外设备:

复制代码
// 示例代码:监听红外按键事件
#include <stdio.h>
#include <fcntl.h>
#include <linux/input.h>

int main() {
    int fd = open("/dev/input/event2", O_RDONLY); // 替换为实际的eventX
    struct input_event ev;

    while (read(fd, &ev, sizeof(ev)) > 0) {
        if (ev.type == EV_KEY) { // 按键事件
            printf("按键码:%d,状态:%s\n", ev.code, ev.value ? "按下" : "松开");
            // 示例:KEY_POWER=116,可映射为具体操作
            if (ev.code == 116 && ev.value == 1) {
                printf("接收到电源键按下\n");
            }
        }
    }
    close(fd);
    return 0;
}

方式 2:读取原始脉冲数据(自定义协议)

通过/dev/rcX读取未解码的脉冲 / 间隙数据,适用于小众协议解析:

复制代码
// 示例代码:读取raw红外数据
#include <stdio.h>
#include <fcntl.h>
#include <linux/rcraw.h>

int main() {
    int fd = open("/dev/rc0", O_RDONLY);
    struct rc_raw_event ev;

    while (read(fd, &ev, sizeof(ev)) > 0) {
        printf("电平:%d,时长:%uus\n", ev.level, ev.duration / 1000);
        // level=1:脉冲(高电平),level=0:间隙(低电平)
    }
    close(fd);
    return 0;
}

方式 3:使用工具快速验证(调试用)

复制代码
# 1. 查看红外设备对应的event号
ls /sys/class/rc/rc0/input/eventX  # X为对应的event编号
# 2. 监听事件
evtest /dev/input/eventX
# 按遥控器按键,输出示例:
# Event: time 1704445678.123456, type 1 (EV_KEY), code 116 (KEY_POWER), value 1

二、发射(TX)流程(驱动层 + 用户态)

发射是 "用户态传入按键码→内核编码→GPIO 输出调制脉冲" 的过程,同样分驱动实现和用户态使用:

1. 驱动层:GPIO 红外发射的实现流程(核心:gpio-ir-tx.c)

步骤 核心函数 / 操作 关键逻辑
① 设备树配置(可选) 编写 DT 节点 定义发射 GPIO 引脚、默认载波频率:dts<br>ir-transmitter {<br> compatible = "gpio-ir-transmitter";<br> gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>; // 发射引脚<br> carrier-frequency = <38000>; // 38kHz载波<br>};<br>
② 驱动 probe 初始化 gpio_ir_tx_probe() 1. 申请 GPIO 引脚(输出模式):devm_gpiod_get(dev, "tx", GPIOD_OUT_LOW);2. 分配rc_devrc_allocate_device(RC_DRIVER_IR_TX);3. 配置rc_dev:设置发射载波、支持的协议;4. 绑定发射回调:rcdev->ops->tx_ir = gpio_ir_tx_tx;5. 注册设备:rc_register_device(rcdev)
③ 接收发射请求(内核) rc_transmit() 内核接收到用户态的发射请求后,调用rcdev->ops->tx_ir
④ 协议编码(生成脉冲) gpio_ir_tx_tx() 1. 将用户态传入的按键码编码为脉冲序列:ir_encode_raw(rcdev, txbuf, len, &events);2. 生成 38kHz 载波调制的电平信号:gpio_ir_tx_emit(data, events, count)
⑤ GPIO 输出脉冲 gpio_ir_tx_emit() 按脉冲时长翻转 GPIO 电平,输出调制后的红外信号(如 NEC 协议的引导码 + 地址码 + 数据码)。

2. 用户态:发送红外信号(2 种方式)

方式 1:使用工具快速发射(调试用)

需安装v4l-utils工具(含ir-ctl),支持常见协议(NEC/RC6/Sony):

复制代码
# 1. 查看设备支持的协议
ir-ctl -d /dev/rc0 --list-protocols
# 2. 发射NEC协议的按键码(0x00ff00ff)
ir-ctl -d /dev/rc0 -s nec:0x00ff00ff
# 3. 发射RC6协议的按键码
ir-ctl -d /dev/rc0 -s rc6:0x12345678

方式 2:通过 ioctl 编程发射(应用开发)

直接调用RC_IOCTL_TX_SEND ioctl 命令,控制发射逻辑:

复制代码
// 示例代码:发射NEC协议红外信号
#include <stdio.h>
#include <fcntl.h>
#include <linux/rcdevice.h>

int main() {
    int fd = open("/dev/rc0", O_RDWR);
    if (fd < 0) { perror("open"); return -1; }

    // 1. 设置发射协议为NEC
    __u64 protocols = RC_PROTO_BIT_NEC;
    ioctl(fd, RC_IOCTL_SET_PROTOCOLS, &protocols);

    // 2. 准备发射数据(NEC按键码:0x00ff00ff)
    struct rc_tx_request req = {
        .protocol = RC_PROTO_NEC,
        .length = 32, // NEC协议32位
        .tx_data = {0x00, 0xff, 0x00, 0xff}, // 地址码+反码+数据码+反码
    };

    // 3. 发送红外信号
    if (ioctl(fd, RC_IOCTL_TX_SEND, &req) < 0) {
        perror("tx send");
        close(fd);
        return -1;
    }

    close(fd);
    return 0;
}

三、关键调试与问题排查

验证设备注册

复制代码
ls /sys/class/rc/rc0/  # 存在则注册成功
cat /sys/class/rc/rc0/protocols  # 查看支持的协议
cat /sys/class/rc/rc0/name       # 显示"gpio-ir-receiver"/"gpio-ir-transmitter"

内核日志调试

复制代码
dmesg | grep gpio-ir  # 查看驱动加载、中断触发、发射成功的日志

常见问题

  • 接收无事件:检查 GPIO 中断配置(双边沿)、协议是否匹配、硬件接线;
  • 发射无信号:检查 GPIO 输出模式、载波频率(默认 38kHz)、协议编码是否正确;
  • 解码错误:调整接收分辨率(rx_resolution)、增加滤波(rx_filter_size)。

四、核心流程总结

功能 核心链路(驱动层→用户态)
接收 GPIO 中断→gpio_ir_irq_handlerrc_raw_event_store→协议解码→rc_keydown/dev/input/eventX读取
发射 用户态ioctl(RC_IOCTL_TX_SEND)gpio_ir_tx_txir_encode_rawgpio_ir_tx_emit→GPIO 输出脉冲

通过以上流程,既能完成 GPIO 红外收发的驱动适配,也能在用户态灵活调用红外功能。如果需要针对某一步骤(如 DT 配置、ioctl 命令)的更细节代码 / 说明,可随时补充。

相关推荐
今晚务必早点睡4 小时前
Windows 还是 Linux?一次真实项目中的压力测试使用场景对比与总结
linux·windows·压力测试
阿拉伯柠檬4 小时前
传输层协议TCP(三)
linux·网络·网络协议·tcp/ip·面试
102400244 小时前
ubuntu图形化桌面崩溃,无法进入ttl&grub,导出数据教程
linux·运维·ubuntu
松涛和鸣4 小时前
46、线程邮箱系统(C语言+多线程通信)
linux·c语言·网络·网络协议·tcp/ip·html
用户263351880664 小时前
Linux磁盘占用分析指南
linux
AndyHeee5 小时前
【rk3576 Buildroot屏幕双显示分辨率、imx219摄像头、pcie接口挂载nvme问题——持续更新中】
linux·驱动开发
HalvmånEver5 小时前
Linux:匿名管道的五大特性(进程间通信三)
linux·运维·c++·管道pipe
北京盟通科技官方账号5 小时前
EC-Master 适配 Xenomai 4:构建 Linux 环境下的硬实时 EtherCAT 主站架构
linux·运维·网络·人工智能·架构·机器人
水天需0105 小时前
Linux Bash 中脱字符 ^ 的完整用法详解
linux
小码吃趴菜5 小时前
线程同步-消息队列-互斥锁-补充两个面试问题
linux·分布式