1. Linux红外遥控子系统概述
Linux内核中的红外遥控(IR Remote Control)子系统是多媒体子系统(Media Subsystem)的重要组成部分,它提供了一套完整的框架来处理红外遥控设备的接收和发射功能。这个子系统并非废弃功能,而是持续维护并广泛应用于嵌入式系统、工业控制和消费电子等领域。
1.1 子系统架构与定位
红外遥控子系统在内核中的代码主要位于drivers/media/rc/目录下,其核心功能包括:
- 硬件抽象层:封装各种红外接收/发射硬件设备
- 协议处理:支持NEC、RC5/RC6、Sony、JVC等主流红外协议
- 事件上报:将红外按键事件转换为标准Input子系统事件
- 设备树支持:通过DT配置红外设备的GPIO、载波频率等参数
该子系统依赖于CONFIG_RC_CORE配置项,由Linux Media Maintainer团队持续维护,近年来仍在不断更新,新增对更多红外协议的支持,优化与Input子系统和设备树的适配。
2. 内核配置与子系统启用
2.1 内核编译配置
要启用红外遥控子系统,需要在编译内核时进行以下配置:
bash复制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等)
<*> IR transmitter support (如需红外发射功能)
<*> GPIO IR receiver/transmitter support (GPIO红外模块必选)
2.2 子系统工作验证
子系统启用后,可以通过以下命令验证其是否正常工作:
bash复制# 查看已注册的红外设备
ls /dev/rc*
ls /sys/class/rc/
# 查看红外事件
evtest /dev/input/eventX # 替换X为红外设备对应的event号
# 查看支持的协议
cat /sys/class/rc/rc0/protocols
3. 核心数据结构与驱动开发
3.1 struct rc_dev详解
struct rc_dev是红外子系统的核心数据结构,它抽象了红外设备的硬件属性、功能接口和运行状态。主要成员包括:
-
硬件属性:
name:设备名称driver_name:驱动名称allowed_protocols:支持的红外协议掩码tx_carrier:发射载波频率
-
功能接口:
ops:硬件操作回调函数集合input_dev:关联的Input子系统设备
-
运行状态:
protocol:当前激活的红外协议devno:设备号priv:驱动私有数据指针
3.2 驱动开发流程
开发红外设备驱动的基本流程如下:
c复制// 1. 分配rc_dev实例
struct rc_dev *rcdev = rc_allocate_device(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";
// 3. 绑定回调函数
rcdev->ops->irq_handler = gpio_ir_irq_handler;
rcdev->priv = &gpio_ir_data;
// 4. 注册设备
int ret = rc_register_device(rcdev);
if (ret) {
rc_free_device(rcdev);
return ret;
}
// 5. 驱动卸载时注销
rc_unregister_device(rcdev);
rc_free_device(rcdev);
4. GPIO红外设备实现
4.1 接收功能实现
GPIO红外接收的核心是中断处理函数,它负责采集红外脉冲数据:
c复制static irqreturn_t gpio_ir_irq_handler(int irq, void *dev_id)
{
struct gpio_ir_data *data = dev_id;
u64 now = ktime_get_ns();
bool level = gpiod_get_value(data->gpiod);
// 计算脉冲/间隙时长
u32 duration = now - data->last_event;
data->last_event = now;
// 上报原始事件
rc_raw_event_store(&data->rcdev, level, duration);
rc_raw_event_handle(&data->rcdev);
return IRQ_HANDLED;
}
4.2 发射功能实现
GPIO红外发射的核心是协议编码和GPIO输出:
c复制static int gpio_ir_tx_tx(struct rc_dev *dev, unsigned int *txbuf, unsigned int len)
{
struct gpio_ir_tx_data *data = dev->priv;
struct ir_raw_event *events;
int count = ir_encode_raw(dev, txbuf, len, &events);
// 输出调制后的红外信号
gpio_ir_tx_emit(data, events, count);
kfree(events);
return 0;
}
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);
udelay(events[i].duration / 1000);
}
}
5. 用户态接口与使用
5.1 接收事件处理
用户态可以通过三种方式处理红外接收事件:
- 读取Input事件(推荐):
c复制int fd = open("/dev/input/eventX", O_RDONLY);
struct input_event ev;
while (read(fd, &ev, sizeof(ev)) > 0) {
if (ev.type == EV_KEY) {
printf("按键码:%d,状态:%s\n", ev.code, ev.value ? "按下" : "松开");
}
}
close(fd);
- 读取原始脉冲数据:
c复制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);
}
close(fd);
- 使用evtest工具:
bash复制evtest /dev/input/eventX
5.2 发射功能调用
用户态可以通过两种方式发送红外信号:
- 使用ir-ctl工具:
bash复制ir-ctl -d /dev/rc0 -s nec:0x00ff00ff
- 通过ioctl编程:
c复制int fd = open("/dev/rc0", O_RDWR);
__u64 protocols = RC_PROTO_BIT_NEC;
ioctl(fd, RC_IOCTL_SET_PROTOCOLS, &protocols);
struct rc_tx_request req = {
.protocol = RC_PROTO_NEC,
.length = 32,
.tx_data = {0x00, 0xff, 0x00, 0xff},
};
ioctl(fd, RC_IOCTL_TX_SEND, &req);
close(fd);
6. 设备树配置
6.1 接收器配置
dts复制ir-receiver {
compatible = "gpio-ir-receiver";
gpios = <&gpio1 2 IRQ_TYPE_EDGE_BOTH>;
carrier-frequency = <38000>;
};
6.2 发射器配置
dts复制ir-transmitter {
compatible = "gpio-ir-transmitter";
gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>;
carrier-frequency = <38000>;
};
7. 调试与问题排查
7.1 调试方法
- 检查设备注册:
bash复制ls /sys/class/rc/rc0/
cat /sys/class/rc/rc0/protocols
- 查看内核日志:
bash复制dmesg | grep gpio-ir
- 协议分析:
bash复制ir-ctl -d /dev/rc0 --read=raw
7.2 常见问题
- 接收无事件:
- 检查GPIO中断配置
- 验证协议是否匹配
- 检查硬件接线
- 发射无信号:
- 确认GPIO输出模式
- 检查载波频率设置
- 验证协议编码
- 解码错误:
- 调整接收分辨率(rx_resolution)
- 增加滤波(rx_filter_size)
- 检查环境干扰
8. 实际应用场景
Linux红外遥控子系统广泛应用于以下场景:
- 嵌入式设备:树莓派、STM32/ARM工控板通过GPIO红外模块实现遥控功能
- 机顶盒/电视棒:Android TV、树莓派电视盒子处理红外遥控
- 工控/车载系统:Linux车载系统适配车载红外设备
- USB红外适配器:USB红外遥控棒接入Linux主机
9. 性能优化建议
- 中断处理优化:
- 使用线程化中断减少延迟
- 合理设置中断触发方式
- 避免在中断上下文中进行复杂操作
- 协议处理优化:
- 选择硬件支持的协议
- 合理设置协议参数
- 使用raw模式处理自定义协议
- 电源管理:
- 实现合理的suspend/resume回调
- 动态调整采样率
- 支持唤醒功能
10. 开发注意事项
- 协议兼容性:
- 不同厂商的遥控器可能使用相同协议的不同变种
- 需要测试多种遥控器确保兼容性
- 必要时支持多种协议变种
- 硬件差异:
- 不同红外接���模块的灵敏度可能不同
- 发射功率影响控制距离
- 环境光线可能干扰接收
- 安全性考虑:
- 对关键功能(如电源控制)增加保护
- 防止恶意红外信号攻击
- 实现适当的权限控制
在实际开发中,建议先使用现成的硬件模块(如HS0038接收器和红外LED)进行原型验证,再根据具体需求进行定制开发。对于批量生产的产品,可以考虑使用集成度更高的专用红外芯片,以提高可靠性和降低成本。
