
简介面向智能家居开发者和电子爱好者的红外遥控码库围绕红外通信技术提供了一套可直接使用的家电遥控指令集合适用于电视、空调、投影仪、IPTV机顶盒等常见设备解决多遥控器分散管理、难以统一编程控制的痛点。压缩包为RAR格式体积仅27KB内置约20个按设备类型命名的码库文件如“空调V3普通”“IPTV”“相机”等每种对应一组遥控指令文件命名直观便于查找匹配。目前已有3348人学习下载。除本地码库外资源还附带了指向更大开源码库的网址可查询约3万种设备的遥控码几乎覆盖常见家用电子产品。开发者可将这些码值配合Python、C等语言进行编码与解码用于制作自定义智能遥控器、接入红外中转模块或搭建场景自动化从而节省逐台采集码值的调试成本也为红外遥控方案的二次开发提供了可靠参考。 做红外遥控项目最绕不开的就是码库这关。红外遥控码库听起来像个简单的数据表但真正上手做过解码、学习和转发之后才会明白它其实是整个红外方案的骨架——键值对不上按键映射就是空谈后面所有逻辑都白搭。我最早做自学习遥控器时被各种协议格式折腾得够呛后来才慢慢沉淀出一套适合实际工程的码库设计与解码方法。这篇就按我自己的实践路径把码库从底层原理到STM32落地再到项目里的应用扩展完整拆一遍给正在捣鼓红外遥控、智能家居或者万能遥控器的朋友做个参考。1. 红外遥控码库到底是什么先从协议说起1.1 码库在红外遥控系统里扮演什么角色码库本质上是“遥控器按键编码”的集合。每一台红外遥控器每一个按键发出来的都是一串特定时序的红外脉冲而这串脉冲对应的数据——协议类型、地址码、命令码——组合在一起就成了码库里的一个条目。码库的价值在于它把物理世界里的“按一下电源键”抽象成了一个可存储、可匹配、可复现的数据结构让单片机或云端应用可以精准控制对应的设备。一个完整的红外控制系统通常包含三层最底层是硬件层发射管、接收头、载波电路中间是协议解析层解码、编码、时序控制最上层才是业务层按键映射、学习、场景联动。码库就是连接协议解析层和业务层的数据枢纽它决定了系统“认识”哪些遥控器、“听懂”哪些按键。1.2 NEC协议拆解码库的底层语言要理解码库必须先理解红外协议。市面上最常见的红外协议是NEC协议几乎所有日系和中高端家电都在用。NEC协议有几个核心参数需要牢牢记住载波频率38kHz绝大多数红外遥控器都工作在这个频率上引导码9ms载波 4.5ms无载波用来标记一帧数据的开始逻辑0560us载波 560us无载波逻辑1560us载波 1690us无载波重复码9ms载波 2.25ms无载波按键长按时发送一帧标准NEC数据共32位依次是8位地址码、8位地址反码、8位命令码、8位命令反码。反码存在的意义是校验——地址码和地址反码按位取反后相加应该等于0xFF这样就能快速判断一帧数据是不是有效帧。这里有个新手容易踩的坑NEC协议是低位先发。比如命令码0x45二进制是01000101先发出去的是最低位1依次往上最后发最高位0。这意味着收到原始字节后进行位反转才能得到我们习惯的十六进制表示法。我在最初调试时就直接用反转前的原始值去查表结果怎么都对不上卡了大半天。1.3 常用红外协议速查对照NEC只是起点。做了多个设备兼容之后才发现不同厂家的协议区别很大码库设计必须考虑多种协议并存的情况。整理一个常用协议对照表做设计时可以参照协议名载波频率编码方式地址长度命令长度典型设备NEC38kHz脉宽调制8位可扩展16位8位东芝、日立、国产多数SONY SIRC40kHz脉宽调制5位7位或13位索尼电视、摄像机RC536kHz曼彻斯特编码5位6位飞利浦、老式功放RC636kHz曼彻斯特编码8位4位或8位飞利浦新款、微软MCESamsung38kHz脉宽调制12位4位数据...三星电视这些协议的载波频率、位数长度、编码规则都不同码库设计如果只按NEC一种协议来做换一个品牌的遥控器就无法识别。成熟的做法是在码库条目里显式保存协议类型解析时先识别协议再按对应的位长和校验规则提取数据。2. 构建码库前必须想清楚的三个设计问题2.1 存储结构协议地址命令的三元组码库的存储结构直接决定系统的扩展性和维护成本。我最早用的是一个只有“命令码”的二维表后来接入第二个品牌设备时当场傻眼——不同品牌同一个“电源”键编码完全不同必须把地址信息也存进去并且把协议类型作为最高维度的分类键。推荐的结构是定义协议枚举和通用键值结构体这样代码里的逻辑键和物理红外码就能解绑typedef enum { IR_PROTO_NEC 0, IR_PROTO_SONY, IR_PROTO_RC5, IR_PROTO_RC6, IR_PROTO_SAMSUNG, IR_PROTO_UNKNOWN // 未识别协议走波形透传 } IrProtocol_t; typedef struct { IrProtocol_t protocol; uint32_t addr; // 设备地址NEC为8位或16位 uint32_t cmd; // 命令码SONY可为7/13位 } IrKey_t;有了这个基础结构码库表就可以按设备类型分组每一条对应一个逻辑按键typedef struct { uint16_t dev_id; // 设备ID0电视 1空调 2机顶盒 uint16_t key_id; // 逻辑键ID0电源 1音量 ... IrKey_t ir_key; // 物理红外键值 } IrCodeEntry_t;这里的思路和软件开发里的“接口分离”非常像。业务层只认识dev_id key_id不知道底层是NEC还是RC5协议层负责把红外波形解码成IrKey_t码库层负责两者之间的映射。各层独立后续新增设备类型或协议时改动可以限制在对应模块内不会牵一发动全身。2.2 逻辑键与物理键解耦代码里的按键映射桥如果不做解耦直接把红外码写死在业务逻辑里项目初期看起来很直接但后期维护会非常痛苦。比如空调的“温度”和电视的“音量”都叫KEY_UP但码值完全不同甚至同一台设备的不同工作模式下同一个遥控器按键发射的码也会变化。这时候就需要一张映射表把“逻辑键”和“具体红外码”分开维护。实际项目中我会把映射表做成数组配合二分查找或者哈希表来查询。因为码表通常是静态数据运行时改动不大用排序数组加二分查找就够高效了没必要上复杂的数据结构。一个将近1000条记录的码表二分查找最坏情况下也就十次比较性能完全足够。如果设备需要学习的按键很多建议把映射表拆成“查询表”和“配置表”两层。查询表存逻辑键和红外键值的关系配置表存用户自定义方案。自学习时先写配置表再更新查询表这样就能实现“学习保存复用”的完整闭环。2.3 兼容性处理同键不同码、同码不同键实际调试中碰到的另一个经典问题是“同键不同码”。同一品牌的电视新款和旧款可能换了地址码或者改变了按键命令码。这时候码库不能只保留一条记录最好支持“一个逻辑键对应多条候选红外码”匹配时逐条尝试。我的做法是在码库结构里增加alt_count和alt_keys字段指向备用键值列表。还有个容易忽略的点重复码和自动关机的处理。NEC协议长按后会发重复码如果码库处理不当一个按键会重复触发多次业务动作。我在设计时专门为重复码做了一个标志位长按状态只触发一次动作直到收到下一条新指令才再次响应。这个细节对空调、风扇这类需要连续调节的设备尤其重要。3. STM32红外解码实战从波形到码值3.1 硬件连接与定时器配置硬件上最常用的方案是一体化红外接收头比如HS0038B输出引脚直接接STM32的定时器输入捕获通道。我用的是PA0接TIM2_CH1这样可以直接利用定时器的输入捕获功能来实现精确脉宽测量。输入捕获的思路是红外接收头在没有收到38kHz载波时输出高电平收到载波时输出低电平。配置定时器为上升沿和下降沿都捕获在中断里读取CCR寄存器两次捕获之间的差值就能得到高电平或低电平的持续时间。这里一个关键配置项是分频系数我习惯把定时器时钟分频到1MHz也就是1us计数一次这样读取到的捕获值就直接是微秒数方便后续判断协议时序。TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; // 定时器时钟72MHz分频71 1MHz计数频率 TIM_TimeBaseStructure.TIM_Prescaler 71; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); // 通道1配置为输入捕获上升沿和下降沿都触发 TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x03; // 滤掉高频毛刺 TIM_ICInit(TIM2, TIM_ICInitStructure);特别注意TIM_ICFilter这个字段建议至少设成2或3。没有滤波时环境光干扰和脉冲毛刺极易触发误捕获导致解码出错。但滤波值也不能太大否则窄脉冲会被滤掉逻辑0就解不出来了。3.2 捕获中断里完成NEC时序判断解码的核心逻辑在中断服务函数里。我的判断流程是在两个连续的上升沿之间测量时间差根据差值落入哪个区间来判断是引导码、逻辑1还是逻辑0。时间间隔大于7ms判定为引导码或重复码初始化数据缓冲区准备接收新帧时间间隔介于2ms和7ms之间判定为逻辑1因为NEC逻辑1的时间约2.25ms时间间隔介于1.1ms和2ms之间判定为逻辑0逻辑0总时长约1.12ms这里阈值选用的是两个相邻时序的中间值留出适应的余量。如果实测波形有偏差还可以根据具体情况微调这些阈值。不过这种方式有一个前提必须保证中断足够快不能在捕获中断里做耗时操作。我的原则是中断里只做数据移位和计数累加标记一个“帧接收完毕”的标志位后续的协议解析、查表匹配、业务处理全部放到主循环里完成。3.3 完整解码代码与CRC校验逻辑下面是一段实际可用的解码核心代码保留了关键逻辑方便对照理解#define TH_LEAD 7000 // 大于7ms认为是引导码 #define TH_BIT1 1800 // 大于1.8ms认为是逻辑1 #define TH_BIT0 900 // 大于0.9ms认为是逻辑0 volatile uint8_t ir_rx_done 0; volatile uint32_t ir_frame 0; volatile uint8_t ir_bit_cnt 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) ! RESET) { static uint32_t last_cap 0; uint32_t cur_cap TIM_GetCapture1(TIM2); uint32_t diff cur_cap - last_cap; // 相邻两次捕获的间隔(us) last_cap cur_cap; if (diff TH_LEAD) { // 引导码或重复码重置帧数据 ir_bit_cnt 0; ir_frame 0; } else if (diff TH_BIT1) { // 逻辑1置位当前bit ir_frame | (1UL ir_bit_cnt); ir_bit_cnt; if (ir_bit_cnt 32) ir_rx_done 1; } else if (diff TH_BIT0) { // 逻辑0bit默认就是0只需计数 ir_bit_cnt; if (ir_bit_cnt 32) ir_rx_done 1; } TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); } }代码看似简单小细节很讲究。同步以两次上升沿间隔为判断对象就不需要额外区分高低电平逻辑上简洁很多。但NEC是LSB先发解码后得到一个32位原始值其中位0对应地址的最低位。做码库匹配前我会先做位反转得到标准值再拿协议字段和校验字段做合法性判断。uint8_t addr (uint8_t)(ir_frame 0xFF); uint8_t addr_inv (uint8_t)((ir_frame 8) 0xFF); uint8_t cmd (uint8_t)((ir_frame 16) 0xFF); uint8_t cmd_inv (uint8_t)((ir_frame 24) 0xFF); // 反码校验不通过则丢弃该帧 if ((addr ^ addr_inv) ! 0xFF || (cmd ^ cmd_inv) ! 0xFF) { ir_rx_done 0; return; }调试时我强烈建议把整帧的32位原始值通过串口打印出来和逻辑分析仪的波形对比确认。我第一次做时对不上就是靠串口打印和示波器一起查最终定位到位序理解错误的。3.4 发射实现把码库里的值重新变成红外光解码是接收的过程发射则是把码库里的键值还原成红外波形。核心思路是用定时器产生38kHz的PWM方波控制PWM输出引脚的通断来模拟时序。void ir_carrier_on(uint16_t us) { TIM_Cmd(TIM3, ENABLE); // 开启PWM输出 delay_us(us); TIM_Cmd(TIM3, DISABLE); // 关闭PWM输出 } void nec_send_one_frame(uint8_t addr, uint8_t cmd) { ir_carrier_on(9000); // 引导码高电平 delay_us(4500); // 引导码低电平 for (uint8_t i 0; i 8; i) { if (addr (1 i)) { ir_carrier_on(560); delay_us(1690); } else { ir_carrier_on(560); delay_us(560); } } // 地址反码、命令码、命令反码按同样逻辑发送 // ... }发射驱动电路用一颗三极管加红外发射管即可。发射距离和载波占空比关系很大我是把PWM的占空比调在1/3左右发射距离比50%占空比明显更远因为红外发射管的峰值电流可以做得更大。电源滤波要做好特别是电池供电时猛然拉大电流会导致电压跌落影响MCU工作。4. 码库在项目中的应用与业务扩展4.1 解码查表与设备映射的工程实践码库建立后整个系统的业务逻辑就变得很清爽。接收端把波形解析成IrKey_t然后查表匹配IrCodeEntry_t最终得到dev_id和key_id再执行对应动作。我把这个流程封装成三个接口方便上层调用// 解码波形数据 - 协议键值 IrKey_t ir_decode_wave(uint32_t *samples, uint16_t len); // 查表协议键值 - 逻辑键ID int16_t ir_code_lookup(IrKey_t *key, uint8_t dev_id); // 发射逻辑键ID - 红外波形 void ir_send_key(uint8_t dev_id, uint16_t key_id);三个接口分工明确上层只需要关心设备和按键不需要理解协议细节。这种封装方式也方便后面做单元测试我通常会在PC上先把码库查表逻辑跑通再移植到STM32上。4.2 自学习遥控器的设计与实现学习型遥控器的核心是“录波-回放”。和纯码库方案不同它不依赖预先存储某个协议而是把实际红外波形完整保存下来再按需重放。最简单的实现方式是把一串脉冲宽度存成数组比如用两个数组分别存高电平持续时间和低电平持续时间学习时记录这些时间回放时按记录值控制载波输出。这种方案的优点是兼容性极好——管你什么协议波形一模一样就能控制目标设备缺点是单个键位占用空间较大但考虑到MCU的Flash通常都有几十KB以上存几十个常用按键完全没问题。实践中我会把“码库解析”和“波形学习”结合能识别的协议走结构化存储省空间、易校验识别不了的存原始波形保证兼容性。一个学习键存储时先自动试解码失败就转为波形存储这样既不丢失功能也能利用码库的查询优势。4.3 公开码库资源的获取与二次处理除了自己录制也有一些公开的码库资源可以参考。Linux社区有一个红外遥控工具项目维护了大量遥控器的键值配置文件里面包含了NEC、RC5等常见协议的定义是很不错的参考源。获取这些配置文件后通常需要写个脚本转换成自己的IrCodeEntry_t数组这里有个技巧脚本里把键值校验一并做掉过滤掉异常记录避免坏数据带进固件。完全自建码库耗时比较长我通常的做法是先找公开码库覆盖常见品牌然后针对手头的设备逐台录制补充最后再人工整理出几张映射表。这个过程比较琐碎但一次做好后面所有项目都复用同一套码库体系效率反而更高。5. 常见问题与排查技巧实录5.1 典型故障对照速查表现象常见原因处理方案解码成功率低输入捕获滤波过大导致窄脉冲丢失将TIM_ICFilter调到2或3并实测波形确认同一按键重复触发长按重复码未处理增加重复码忽略机制同一键值限定时间内只执行一次能解码但无法控制设备发射载波频率不对用频响好的红外接收头配合示波器确认PWM频率接收距离突然变短发射管老化或电源电压跌落检查供电电容考虑加大储能电容解出来的码顺序颠倒NEC低位先发未做位反转解码后做位反转再做码库匹配用逻辑分析仪抓不到波形红外接收头反相输出触发条件设反检查输出电平极性调整触发边沿5.2 排查思路与几个独家技巧排查解码问题我的习惯是先用逻辑分析仪抓接收头的输出波形确认时序长度是否和协议标称值匹配。分析仪能直接看到每个高电平、低电平的宽度一眼就能判断是时序错了还是协议判断错了。一个很容易被忽略的问题是环境光的干扰。阳光、白炽灯、LED灯都会发射红外成分可能导致接收头持续输出异常脉冲。解决方案一是给接收头加遮光罩二是在捕获初始化时开启数字滤波三是软件里增加“非法帧丢弃”逻辑——连续一帧内出现超过64个边沿就强制复位接收状态。另一个经验是学习功能测试时遥控器和接收头的距离最好保持在15cm以内。距离太远会引入环境噪声导致波形边缘出现抖动。录制完的波形数据不要直接使用建议做一次毛刺剔除和最小脉宽过滤把小于100us的异常脉冲删除。这个预处理对后续重放稳定性提升非常明显。硬件排障也有一个心得如果发射距离明显不正常不要急着换发射管先看看驱动限流电阻和供电电容。我曾经把限流电阻从22欧降到10欧发射距离并没有提升多少反而因为电源瞬间压降导致单片机复位。后来改成大容量储电电容加上合理占空比问题才真正解决。5.3 我做码库时踩过的一个大坑最后分享一个最痛的教训。早期做学习遥控器时我把所有按键的学习数据放在内部Flash里直接按扇区擦写。测试发现调试器频繁连接或频繁擦写后码库偶尔会整体丢失。排查后才知道是Flash擦写期间发生了中断导致读改写流程乱掉。正确的做法是把Flash擦写放到临界段保护里或者使用双缓冲方案先把完整数据写到临时扇区校验无误后再切换到正式扇区。我后来改成“备份扇区写入后校验上电自检”的结构再也没遇到过码库丢失的问题。红外遥控码库的价值不在于存了多少条码而在于设计时有没有把协议兼容性、数据结构和业务解耦考虑清楚。一个结构清晰、便于扩展的码库可以支撑多个产品项目反复使用省下的调试时间才是最大的收益。做这类基础组件值得多花几天把架构打磨好后续开发会顺畅非常多。本文还有配套的精品资源点击获取