本文主要讲解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)三、典型应用场景
- 嵌入式设备:树莓派、STM32/ARM 工控板通过 GPIO 红外模块(如 HS0038 接收器、红外 LED 发射器),基于该子系统实现遥控功能;
- 机顶盒 / 电视棒:如 Android TV、树莓派电视盒子,依赖该子系统处理红外遥控;
- 工控 / 车载系统:Linux 车载系统通过红外遥控子系统适配车载红外设备;
- 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.c、videobuf2/缓冲区管理)dvb/数字电视(Digital Video Broadcasting) 卫星 / 有线 / 地面数字电视接收卡、DVB 协议栈(如 dvb-core.c、frontends/调谐器驱动)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.c、video/v4l2-ioctl.c;- 作用:统一管理视频采集 / 输出设备(摄像头、视频卡),提供标准的 ioctl 接口(如
VIDIOC_QUERYCAP、VIDIOC_STREAMON),应用层可通过/dev/videoX访问;RC(Remote Control)
- 核心文件:
rc/rc-core.c、rc/ir-nec-decoder.c;- 作用:管理红外遥控设备,如前文提到的
struct rc_dev就定义在此;DVB
- 核心文件:
dvb/dvb-core.c;- 作用:支持数字电视接收设备,兼容 DVB-T/C/S 等标准。
四、典型使用场景
- 嵌入式摄像头 :树莓派 / ARM 开发板的 CSI 摄像头,驱动位于
media/i2c/,通过 V4L2 接口提供视频采集;- USB 摄像头 :普通 USB 摄像头的驱动
uvcvideo位于media/usb/uvc/,是 V4L2 的经典实现;- 红外遥控 :GPIO 红外接收器驱动位于
media/rc/gpio-ir.c,依赖 RC 子系统上报按键事件;- 电视卡 / 电视棒 :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)子系统,核心是历史依赖、硬件集成、功能协同、框架复用四大原因,并非单纯的分类问题,而是内核架构演进与实际应用需求的必然选择。以下从技术与历史角度详细说明:
一、历史起源:从电视卡到统一框架
- 最初的硬件绑定早期红外遥控的核心应用场景是电视卡、视频采集卡(如 CX23885)------ 这些设备本身就是多媒体设备,红外接收器常作为板载附属模块出厂。当时内核中没有独立的 "遥控子系统",红外驱动自然与视频采集、DVB(数字电视)驱动一起放在多媒体代码路径下,形成 "多媒体设备 + 遥控" 的绑定开发模式。
- 子系统整合与标准化 2000 年后 LinuxTV 项目推动多媒体子系统统一,将分散的视频、DVB、遥控等功能整合到
drivers/media/,并引入RC_CORE作为红外遥控的核心框架,替代早期用户态 LIRC 的碎片化实现,形成内核态统一管理的规范。二、硬件与功能的强耦合性
- 物理集成 多数红外接收器并非独立外设,而是与多媒体硬件(电视卡、机顶盒芯片、摄像头模组)物理集成或共享总线(I2C/SPI),例如:
- 电视卡的红外接收器与调谐器、视频解码器共用 PCIe/USB 接口与中断;
- 嵌入式 SoC 的红外模块与多媒体控制器(如 CSI/DSI)共享 GPIO 或 DMA 资源。
- 功能协同 红外遥控的核心用途是控制多媒体设备 (电视、机顶盒、投影仪、播放器),其按键事件(如
KEY_POWER、KEY_VOLUMEUP)与多媒体操作强关联,需与 V4L2/DVB 子系统协同工作(如遥控唤醒视频采集、切换频道)。三、内核框架复用:避免重复造轮子
多媒体子系统提供的核心能力,恰好适配红外遥控的需求,无需重新设计独立框架:
复用能力 红外遥控的应用场景 统一设备模型 复用 media_device与entity架构,抽象红外接收器 / 发射器为标准设备节点(/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/,归多媒体子系统,核心是按键事件处理,二者定位不同,避免功能冲突。五、统一管理的优势
- 降低维护成本:由 Linux Media Maintainer 团队统一维护,避免跨子系统协调;
- 驱动开发高效:开发者可直接复用多媒体子系统的硬件抽象、电源管理、中断处理等代码;
- 应用层兼容 :用户态通过
/dev/rcX或/dev/input/eventX访问,无需适配不同子系统的接口。
总结
红外遥控归类到多媒体子系统,是历史路径、硬件集成、功能协同、框架复用共同作用的结果,而非随意分类。这种架构既符合 "控制多媒体设备" 的核心场景,又能复用成熟的内核框架,是 Linux 内核中 "高内聚、低耦合" 设计理念的典型体现。
LIRC是啥意思?LIRC 是 Linux 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_gpio、lirc_serial),用于适配各类红外硬件,直接操作 GPIO、串口等接口采集 / 发射红外信号,生成/dev/lircX设备节点。- 用户态工具
irw:监听红外按键事件,打印按键码与协议信息,用于调试。irrecord:录制红外遥控的按键信号,生成自定义按键映射配置文件(.lircrc)。irsend:发送红外信号,可模拟遥控器按键操作。mode2:查看红外原始脉冲数据(脉冲 / 间隙时长),用于协议分析。- 配置文件
/etc/lirc/lircd.conf:全局硬件与协议配置,定义红外设备的参数(如载波频率、采样率)。~/.lircrc:用户自定义按键映射,指定某按键码对应触发的系统动作或应用命令。三、LIRC 的适用场景
- 老旧硬件 / 系统 :对于没有内核
rc-core支持的老式红外设备(如串口红外模块),LIRC 是唯一可行的方案。- 自定义协议需求 :当需要处理小众红外协议,而内核
rc-core未支持时,可通过 LIRC 直接解析原始脉冲数据,实现自定义解码。- 特定应用适配:部分老旧媒体应用(如早期 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_dev2. 红外协议解码 / 编码(
drivers/media/rc/)
文件名 支持协议 关键说明 ir-nec-decoder.cNEC 协议(最常用) 解码 NEC 红外脉冲(38kHz 载波、地址码 + 数据码) ir-rc5-decoder.cRC5 协议 飞利浦 RC5 协议解码(双向通信、14 位编码) ir-rc6-decoder.cRC6 协议 RC6 协议(扩展位、多种子版本) ir-sony-decoder.cSony SIRC 协议 索尼 12/15/20 位编码协议 ir-jvc-decoder.cJVC 协议 JVC 红外协议(可变长度编码) ir-raw-event.c通用 raw 事件处理 脉冲 / 间隙时长解析、协议匹配分发 ir-tx-core.c红外发射核心 红外发射硬件抽象、编码后数据下发 3. 硬件适配驱动(
drivers/media/rc/)
文件名 适配硬件类型 关键说明 gpio-ir.cGPIO 红外接收器 通用 GPIO 红外接收模块(如 HS0038)驱动,基于中断采集脉冲 gpio-ir-tx.cGPIO 红外发射器 通用 GPIO 红外发射模块驱动,输出调制后的红外脉冲 rc-loopback.c环回测试驱动 红外信号环回测试(接收→发射),用于调试 rc-rockchip.c瑞芯微 SoC 红外 瑞芯微 RK3399/RK3568 内置红外控制器驱动 rc-stm32.cSTM32 红外控制器 STM32 单片机内置红外解码 / 编码外设驱动 4. 按键映射表(
drivers/media/rc/keymaps/)
文件名 映射场景 关键说明 rc-map-nec.cNEC 协议通用映射 将 NEC 按键码(如 0x00ff00ff)映射为 KEY_POWER等标准码rc-map-rc6.cRC6 协议通用映射 RC6 协议按键码与标准输入事件的映射 rc-map-default.c默认通用映射 未指定映射表时的兜底按键映射 rc-map-sony.cSony 协议映射 索尼遥控器专用映射 5. 头文件(
include/media/)
头文件路径 核心定义 关键内容 include/media/rc-core.h核心结构体 / API struct rc_dev、struct rc_ops、rc_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.hRaw 事件定义 struct rc_raw_event(脉冲 / 间隙事件)、解码回调定义6. 设备树绑定文档(
Documentation/devicetree/bindings/rc/)
文件名 绑定规范 关键说明 rc.yaml红外设备通用绑定 定义红外设备的 DT 属性( compatible、gpios、carrier-frequency)gpio-ir-receiver.yamlGPIO 红外接收器 _GPIO 红外接收模块的 DT 配置规范(如 gpios指向接收引脚)gpio-ir-transmitter.yamlGPIO 红外发射器 GPIO 红外发射模块的 DT 配置(如 tx-gpios、carrier-frequency)rockchip,ir.yaml瑞芯微 SoC 红外 瑞芯微内置红外控制器的 DT 绑定 7. 示例代码(
samples/media/rc/)
文件名 示例功能 适用场景 rc-example.c红外设备注册示例 演示如何初始化 rc_dev、绑定协议、上报按键事件rc-raw-example.cRaw 数据处理示例 自定义解析红外原始脉冲数据 三、关联的通用子系统文件
红外子系统依赖内核其他通用组件,关键关联文件:
路径 功能 关联点 drivers/input/input.cInput 子系统核心 红外按键事件最终通过 input_event()上报include/linux/input.hInput 事件定义 标准按键码( KEY_POWER/KEY_OK)定义drivers/gpio/gpiolib.cGPIO 子系统 GPIO 红外模块的引脚控制(申请 / 配置中断) 四、快速查找技巧
- 找核心框架:直接看
rc-core.c(注册 / 注销)、rc-main.c(核心逻辑);- 找协议解码:文件名含
ir-xxx-decoder.c(xxx 为协议名);- 找硬件驱动:按硬件类型(
gpio-ir.c/rc-rockchip.c);- 找 DT 配置:
Documentation/devicetree/bindings/rc/下的 YAML 文件;- 找头文件:
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. 硬件属性(描述设备能力)
成员名 类型 作用说明 namechar[32]设备名称(如 "gpio-ir-receiver"),用于 sysfs 标识(/sys/class/rc/rc0/name)driver_namechar[32]驱动名称(如 "gpio-ir"),关联具体驱动模块hor_res/ver_resu32兼容视频子系统的分辨率(红外设备一般无需设置) allowed_protocolsu64支持的红外协议掩码(如 `RC_PROTO_BIT_NEC RC_PROTO_BIT_RC6`) tx_carrieru32发射载波频率(默认 38000Hz,红外遥控标准载波) tx_carrier_rangestruct rc_carrier_range发射载波频率范围(如 30k~40kHz) rx_resolutionu32接收分辨率(单位 ns,默认 1000ns=1μs,即脉冲采样精度) rx_filter_sizeu32接收脉冲滤波窗口大小(过滤噪声) 2. 功能接口(绑定驱动回调)
成员名 类型 作用说明 opsstruct rc_ops *硬件操作接口(核心!绑定中断处理、发射控制、参数配置等回调) input_devstruct input_dev *关联的 Input 子系统设备,红外按键事件最终通过它上报(如 /dev/input/eventX)raw_decodebool是否启用原始脉冲解码(开启后可自定义协议解析) s_tx_masku32发射掩码(控制多通道发射) 3. 运行状态(记录设备当前状态)
成员名 类型 作用说明 protocolenum rc_proto当前激活的红外协议(如 RC_PROTO_NEC)devnodev_t设备号(对应 /dev/rc0等字符设备节点)rc_rawstruct rc_raw_buffer原始红外脉冲缓冲区(存储未解码的脉冲 / 间隙数据) map_nameconst char *按键映射表名称(如 "rc-map-nec"),关联按键码与标准输入事件privvoid *私有数据指针(驱动可存放硬件专属数据,如 GPIO 引脚信息) 三、核心配套结构体:
struct rc_ops
rc_dev的ops成员指向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);五、关键总结
struct rc_dev是红外设备的 "抽象代表":不管硬件形态(GPIO/USB/SoC 内置),内核都通过它管理红外设备;- 核心价值是 "统一接口":驱动只需实现
rc_ops中的硬件回调,无需关心上层事件上报、协议匹配等逻辑;- 与 Input 子系统联动:最终红外按键事件通过
input_dev上报,应用层可通过evtest等工具直接读取,无需适配不同硬件。不同版本略有差异,但总体是类似的。
收发功能流程
想熟悉一个gpio红外设备的收发功能实现,要怎么看?
要熟悉 Linux 内核中 GPIO 红外设备(接收 + 发射)的收发功能实现,核心是按 "硬件驱动→子系统框架→协议编解码→事件交互" 的链路拆解,从代码入口到关键逻辑逐步深挖。以下是分步骤的学习路径,结合具体文件、核心函数和调试方法,覆盖接收(RX)和发射(TX)全流程:
前置准备
- 确定核心文件 :GPIO 红外的核心驱动是
drivers/media/rc/gpio-ir.c(接收)和drivers/media/rc/gpio-ir-tx.c(发射),先定位这两个文件;- 内核配置 :确保开启
CONFIG_RC_CORE、CONFIG_GPIO_IR、CONFIG_GPIO_IR_TX(menuconfig 路径:Device Drivers → Multimedia support → Remote Controller support → GPIO IR receiver/transmitter);- 硬件背景 :了解 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 # 查看接收/发射的脉冲时长、电平状态等日志第五步:进阶深挖(可选)
如果想更深入,可继续看这些关联逻辑:
- 协议编解码细节 :比如
ir-nec-decoder.c中 NEC 协议的引导码、地址码、数据码解析逻辑;- raw 事件处理 :
rc-raw.c中脉冲时长的校验、协议匹配的优先级;- 设备树适配 :
Documentation/devicetree/bindings/rc/gpio-ir-receiver.yaml中 DT 属性(如gpios、carrier-frequency)如何映射到驱动中;- 与 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_CORE、CONFIG_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_dev:rc_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_dev:rc_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_SENDioctl 命令,控制发射逻辑:
// 示例代码:发射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_handler→rc_raw_event_store→协议解码→rc_keydown→/dev/input/eventX读取发射 用户态 ioctl(RC_IOCTL_TX_SEND)→gpio_ir_tx_tx→ir_encode_raw→gpio_ir_tx_emit→GPIO 输出脉冲通过以上流程,既能完成 GPIO 红外收发的驱动适配,也能在用户态灵活调用红外功能。如果需要针对某一步骤(如 DT 配置、ioctl 命令)的更细节代码 / 说明,可随时补充。