1. 从一个“吃灰”的红外接收头说起如果你手头有一堆闲置的遥控器——电视的、机顶盒的、老式风扇的、甚至某个不知道什么设备的——然后你盯着它们想能不能让我的开发板听懂这些按键在说什么那这个项目就是为你准备的。Arduino-IRremote是一个在嵌入式圈子里流传了很久的红外遥控编解码库它的核心使命只有一件事让微控制器能够接收红外信号并解析出按键编码同时也能够模拟发射红外信号去控制其他设备。说白了它就是一个红外世界的翻译官把遥控器发出的那一串肉眼看不见的闪烁翻译成代码能理解的数字。我第一次接触这个库是在做一个万能遥控器的小项目时。当时的需求很简单用一块开发板接收家里旧空调遥控器的信号然后通过串口把编码打印出来再根据这些编码去控制空调的开关和温度调节。听起来像是三行代码就能搞定的事情但实际踩进去才发现红外编解码这件事远比想象中要复杂得多。不同品牌、不同年代的遥控器使用的编码协议五花八门载波频率有 38kHz 的、有 40kHz 的、还有 56kHz 的编码方式有 NEC、Sony SIRC、RC5、RC6、Samsung、LG 等等每一种协议的时序、帧结构、校验方式都不一样。Arduino-IRremote这个库的价值就在于它把这一大堆让人头大的协议细节封装成了几个简单的 API让你不需要从头去啃数据手册就能快速上手。这篇文章适合哪些人看如果你是完全没接触过红外通信的新手我会从最基础的红外信号原理讲起告诉你接收头和发射管怎么接线、库怎么装、代码怎么写。如果你已经用过这个库但总是遇到接收不稳定、解码错误、发射距离短之类的问题我也会把我在实际项目中踩过的坑和解决方案整理出来。整篇文章不会只停留在“把库装上、跑个示例”这个层面而是会深入到协议解析的细节、参数选择的依据、以及不同场景下的优化策略。2. 红外通信的底层逻辑与这个库的定位2.1 红外遥控到底在“说”什么要理解Arduino-IRremote在做什么首先得搞清楚红外遥控器发出的信号本质上是什么。遥控器前端的红外 LED 并不是持续发光的它发出的是一连串经过调制的脉冲。这个调制过程包含两个层面载波和编码。载波是一个固定频率的方波信号常见的是 38kHz。为什么要用载波因为环境中有大量的红外噪声——阳光、白炽灯、人体热辐射都会产生红外线。如果直接用基带信号发射接收端根本分不清哪些是遥控器发的、哪些是环境噪声。用一个特定频率的载波去调制信号接收头内部就只需要对这个频率敏感相当于给信号加了一个“身份标签”。38kHz 这个频率的选择也不是随便定的它在成本、功耗和抗干扰之间取了一个平衡点绝大多数消费电子遥控器都采用这个频率。编码则是在载波的基础上用脉冲的长短组合来表示二进制的 0 和 1。以最常见的 NEC 协议为例它用一个 560 微秒的载波脉冲加上 560 微秒的空闲表示一个逻辑“0”用一个 560 微秒的载波脉冲加上 1690 微秒的空闲表示逻辑“1”。一帧完整的 NEC 信号包含一个 9 毫秒的引导脉冲、8 位地址码、8 位地址反码、8 位命令码、8 位命令反码最后以一个 560 微秒的结束脉冲收尾。地址反码和命令反码的作用是校验接收端把地址码和地址反码做异或如果结果不是全 1 就说明接收出错了。Arduino-IRremote做的事情就是通过一个红外接收头把光信号转换成电信号然后用定时器精确测量每一个脉冲的持续时间再根据预设的协议规则去匹配这些时间序列最终还原出地址码和命令码。发射过程则是反过来根据你要发送的地址和命令按照协议规则生成对应的脉冲序列驱动红外 LED 发射出去。2.2 为什么选这个库而不是自己写有人可能会想红外解码不就是测脉冲宽度吗我自己用pulseIn()函数也能做。确实如果你只需要解码一种固定协议、对稳定性要求不高自己写几十行代码也能跑起来。但一旦涉及到多协议兼容、中断处理、非阻塞接收、发射功能自己从头造轮子的成本就会急剧上升。Arduino-IRremote的优势体现在几个方面。第一它支持的协议非常全NEC、Sony、RC5、RC6、Samsung、LG、JVC、Panasonic、Denon、Sharp 等等主流协议都有现成的解码器你不需要为每个品牌单独写解析逻辑。第二它利用了硬件定时器中断来捕获脉冲边沿而不是用阻塞式的pulseIn()这意味着你的主程序可以在等待红外信号的同时处理其他任务。第三它提供了统一的接口来获取解码结果无论底层是什么协议你拿到的都是地址、命令、协议类型这几个字段上层逻辑不需要关心协议差异。第四它同时支持接收和发射接收到的编码可以直接存下来用于后续发射做学习型遥控器非常方便。当然这个库也不是没有代价的。它占用的 Flash 和 RAM 资源在资源紧张的开发板上需要留意尤其是当你开启了多个协议支持的时候。另外它的定时器中断和某些其他库比如舵机控制库可能会产生冲突因为两者都想独占定时器资源。这些问题在后面讲实操的时候会详细展开。2.3 接收头的选型与硬件连接要点红外接收头常见的有三种封装三脚直插式比如 TSOP1838、贴片式、以及带金属屏蔽罩的抗干扰型。选型的时候主要看三个参数载波频率、接收角度、输出极性。载波频率必须和遥控器匹配。市面上绝大多数遥控器是 38kHz但有些老式 Sony 设备用 40kHz某些高速红外通信用 56kHz。如果你用 38kHz 的接收头去收 40kHz 的信号距离近的时候可能还能解出来但距离一远就完全没反应。所以如果你要做万能接收最好备几种不同频率的接收头。接收角度方面直插式的通常有 90 度左右的接收锥角贴片式的角度更窄安装位置需要对准遥控器方向。输出极性方面绝大多数接收头是“低电平有效”——收到 38kHz 载波时输出低电平没有载波时输出高电平。Arduino-IRremote默认就是按这个极性来设计的。接线方面三脚接收头一般是这样面对接收头的受光面从左到右依次是 OUT、GND、VCC。但这个顺序并不是绝对的不同厂家可能不一样接之前一定要查数据手册。我见过有人把 VCC 和 GND 接反导致接收头瞬间烧掉的案例虽然接收头很便宜但烧了还得等快递耽误事。建议在 VCC 和 GND 之间并一个 100nF 的陶瓷电容再串一个 100 欧姆的电阻到 VCC这样可以滤掉电源上的高频噪声对接收稳定性有明显提升。发射部分就更简单了一个红外 LED 加上一个限流电阻和一个驱动三极管或者直接用开发板的 IO 口驱动但电流不能超过 IO 口的最大输出能力。红外 LED 和普通 LED 的区别在于发光波长红外 LED 通常是 940nm 左右肉眼看不到光但用手机摄像头对着看能看到微弱的紫色光点。限流电阻的计算要看 LED 的正向电流和正向压降一般红外 LED 的正向压降在 1.2V 到 1.5V 之间正向电流取 20mA 到 100mA 不等。如果直接用 IO 口驱动电流要限制在 20mA 以内电阻大概在 150 欧姆到 220 欧姆之间。如果要增加发射距离就需要用三极管扩流把电流提到 100mA 以上但要注意 LED 的功耗限制和占空比不能长时间连续发射。3. 库的安装、配置与核心 API 拆解3.1 安装方式与版本选择Arduino-IRremote的安装方式取决于你用的开发环境。如果你用的是 Arduino IDE最省事的方式是通过库管理器搜索 “IRremote” 然后安装。但这里有一个坑库管理器里可能存在多个名字相似的库比如IRremote和IRremoteESP8266后者是专门为 ESP8266/ESP32 系列优化的分支功能更丰富但 API 略有不同。如果你用的是 ESP 系列芯片建议直接用IRremoteESP8266它对 WiFi 和红外共存的场景做了优化。如果你用的是传统的 AVR 系列比如 Uno、Nano用原版IRremote就可以了。版本选择上2.x 版本和 3.x 版本的 API 差异比较大。2.x 版本的接口是IRrecv irrecv(RECV_PIN);然后irrecv.decode(results);3.x 版本改成了IrReceiver.begin(RECV_PIN);和IrReceiver.decode();。网上很多老教程还是 2.x 的写法如果你照着抄发现编译报错大概率就是版本不匹配。我建议新项目直接用 3.x 或更新的版本API 更清晰功能也更完善。手动安装的话从代码托管平台下载 ZIP 包然后在 Arduino IDE 里通过“项目 - 加载库 - 添加 .ZIP 库”导入。手动安装的好处是你可以直接修改库文件里的配置参数比如调整接收缓冲区大小、开启或关闭某些协议支持这在资源受限的板子上很有用。3.2 接收端核心 API 与参数含义接收端的核心流程分三步初始化、解码、获取结果。初始化用IrReceiver.begin(pin, enableLEDFeedback)第一个参数是接收头连接的引脚号第二个参数是是否启用板载 LED 反馈接收时 LED 闪烁调试的时候可以开正式产品里建议关掉省电。解码用IrReceiver.decode()这个函数是非阻塞的它在后台通过定时器中断捕获脉冲主循环里调用它只是检查是否已经收到完整的一帧。如果返回true说明有新的解码结果可用。然后通过IrReceiver.decodedIRData这个结构体来获取具体信息。这个结构体里包含的字段比较多常用的有protocol协议类型、address地址码、command命令码、flags标志位、rawDataPtr原始脉冲数据指针。protocol字段是一个枚举值比如NEC、SONY、RC5等等。address和command的位宽取决于协议NEC 是 8 位地址加 8 位命令Sony 是 5 位地址加 7 位命令RC5 是 5 位地址加 6 位命令。你在写上层逻辑的时候不能假设它们都是 8 位要根据协议类型做不同的处理。flags字段里有一些有用的标志比如IRDATA_FLAGS_IS_REPEAT表示这是一个重复帧按住按键不放时遥控器会发送重复帧IRDATA_FLAGS_IS_MSB_FIRST表示数据是高位在前。获取完结果后必须调用IrReceiver.resume()来重置接收状态准备接收下一帧。如果你忘了调用这个函数第二次解码就会失败。这是一个非常常见的初学者错误我当初就因为这个卡了半天以为接收头坏了换了好几个才发现是漏了一行代码。3.3 发射端核心 API 与参数含义发射端的 API 在 3.x 版本里统一成了IrSender.sendNEC(address, command, repeats)这样的形式每种协议有对应的发送函数。以 NEC 为例address是 8 位地址command是 8 位命令repeats是重复发送的次数0 表示只发一帧1 表示发一帧加一个重复帧以此类推。发射引脚并不是随便选一个 IO 口就行。在 AVR 系列上库默认使用定时器来生成 38kHz 载波而定时器的输出引脚是固定的。比如在 Uno 上定时器 2 的输出引脚是 D3所以你必须把红外 LED 接到 D3 上才能正常发射。如果你接到其他引脚上要么完全没信号要么频率不对。这个问题在库的文档里有说明但很多人不看文档直接接线然后发现发射没反应以为是 LED 坏了。3.x 版本对这个问题做了一些改进支持软件 PWM 发射可以指定任意引脚但软件 PWM 的时序精度不如硬件定时器发射距离会打折扣。repeats参数的使用场景是这样的NEC 协议规定当用户按住按键不放时遥控器会先发送一帧完整的数据帧然后每隔 108 毫秒发送一个重复帧重复帧只包含引导脉冲和结束脉冲不包含数据。接收端通过检测重复帧来判断用户是否在长按。所以如果你要模拟长按效果就需要把repeats设成大于 0 的值并且在每次发送之间保持适当的间隔。4. 完整实操从零搭建一个红外解码与回放系统4.1 硬件准备与接线检查先列一下我这次实操用到的物料一块 Arduino Uno 兼容板、一个 38kHz 红外接收头三脚直插式、一个红外 LED940nm、一个 220 欧姆电阻、一个 100 欧姆电阻、一个 100nF 电容、若干杜邦线、一个面包板。接收头的接线是OUT 接 D2GND 接 GNDVCC 接 5V。在 VCC 和 GND 之间并上 100nF 电容VCC 串联 100 欧姆电阻。发射 LED 的接线是正极串联 220 欧姆电阻后接 D3负极接 GND。这里有一个细节值得展开为什么接收头的 VCC 要串电阻并加电容接收头内部是一个光敏二极管加上放大和解调电路它对电源噪声非常敏感。开发板上的 5V 电源虽然看起来稳定但实际上因为数字电路的高速开关电源线上会有很多高频毛刺。这些毛刺会耦合到接收头的输出上导致误触发。串一个 100 欧姆电阻和并一个 100nF 电容构成一个低通滤波器可以把高频噪声滤掉。实测下来不加这个滤波电路的时候接收头在日光灯下会频繁输出错误的脉冲加了之后误触发率大幅下降。接线完成后先不要急着写代码用万用表测一下接收头的输出引脚电压。没有红外信号的时候应该是高电平接近 5V用遥控器对着接收头按按键的时候电压应该有明显的下降波动。如果电压一直是低电平说明接线有问题或者接收头坏了。这个简单的检查可以帮你排除掉大部分硬件故障。4.2 接收端代码实现与串口输出接收端的代码结构很清晰引入库、定义引脚、初始化串口和接收器、主循环里检查解码结果并打印。下面是我实际使用的代码加了详细的注释#include IRremote.hpp const uint8_t RECV_PIN 2; void setup() { Serial.begin(115200); IrReceiver.begin(RECV_PIN, DISABLE_LED_FEEDBACK); Serial.println(IR receiver ready); } void loop() { if (IrReceiver.decode()) { // 打印协议类型 Serial.print(Protocol: ); Serial.println(getProtocolString(IrReceiver.decodedIRData.protocol)); // 打印地址和命令注意用十六进制方便对照 Serial.print(Address: 0x); Serial.println(IrReceiver.decodedIRData.address, HEX); Serial.print(Command: 0x); Serial.println(IrReceiver.decodedIRData.command, HEX); // 判断是否为重复帧 if (IrReceiver.decodedIRData.flags IRDATA_FLAGS_IS_REPEAT) { Serial.println((repeat frame)); } Serial.println(---); IrReceiver.resume(); } }这段代码里有一个getProtocolString()函数它是库提供的辅助函数可以把协议枚举转换成可读的字符串。如果你用的版本里没有这个函数可以自己写一个 switch-case 来映射。烧录后打开串口监视器把遥控器对准接收头按按键你应该能看到类似这样的输出Protocol: NEC Address: 0x00 Command: 0x18 --- Protocol: NEC Address: 0x00 Command: 0x18 (repeat frame) ---如果串口里什么都没有先检查接收头的接线和电源然后确认遥控器的电池有没有电。如果输出的是乱码或者协议显示为UNKNOWN可能是载波频率不匹配或者遥控器使用了库不支持的协议。有些空调遥控器的编码非常长一帧包含几十个字节NEC 解码器处理不了需要用到decodeHash()或者手动分析原始脉冲数据。4.3 原始脉冲数据的获取与分析当你遇到库无法自动识别的协议时就需要把原始脉冲数据打印出来自己分析。IrReceiver.decodedIRData.rawDataPtr指向一个数组里面存储了每个脉冲的高低电平和持续时间。库提供了一个IrReceiver.printIRResultRawFormatted()函数可以把原始数据以微秒为单位格式化输出到串口。我拿一个老式风扇遥控器做过测试它的协议不在库的支持列表里。打印出来的原始数据是一串交替的正负数值正数表示高电平持续时间负数表示低电平持续时间。通过观察这些数值的规律我发现它的引导脉冲是 4000 微秒高电平加 4000 微秒低电平逻辑 0 是 500 微秒高电平加 500 微秒低电平逻辑 1 是 500 微秒高电平加 1500 微秒低电平。这就是一个典型的非标准协议我根据这个规律自己写了一个简单的解码函数把脉冲序列转换成二进制再转换成按键编码。这个过程听起来很硬核但实际操作起来并不难。你只需要把原始数据复制到电子表格里用公式把正负值分开然后根据阈值判断每个脉冲代表 0 还是 1。关键是找到引导脉冲和结束脉冲的特征它们通常比数据脉冲长很多或者短很多很容易识别。4.4 发射端代码实现与编码回放发射端的代码同样简洁。假设我们已经通过接收端获取了某个按键的地址和命令现在要把它发射出去#include IRremote.hpp const uint8_t SEND_PIN 3; void setup() { Serial.begin(115200); IrSender.begin(SEND_PIN); Serial.println(IR sender ready); } void loop() { // 每隔 2 秒发送一次 NEC 编码 IrSender.sendNEC(0x00, 0x18, 0); delay(2000); }IrSender.begin()的参数是发射引脚号。在 Uno 上如果你用硬件定时器生成载波这个引脚必须是 D3。如果你用的是支持软件 PWM 的版本可以指定其他引脚但发射距离会短一些。发射距离的测试结果直接用 IO 口驱动 LED串联 220 欧姆电阻在 5V 供电下有效控制距离大约在 1 到 2 米之间。如果把电阻换成 100 欧姆电流增大到约 35mA超过了 IO 口的推荐值但还在绝对最大值以内距离可以延伸到 3 米左右。如果要达到 5 米以上就需要用三极管扩流把 LED 电流提到 100mA 以上同时用多个 LED 并联或者使用带透镜的 LED 来增加发射强度。这里有一个实操心得红外 LED 的发射角度很关键。普通的 5mm 红外 LED 发射角度大约在 20 到 30 度之间如果你需要覆盖更大的角度可以选用发射角度更宽的型号或者用两个 LED 朝不同方向发射。我在做一个电视遥控器替代方案时就因为 LED 角度太窄导致必须对准电视才能控制后来换成宽角度 LED 并调整了安装位置才解决。5. 常见问题排查与避坑经验实录5.1 接收不稳定、时好时坏的排查思路接收不稳定是最高频的问题表现是同一个按键有时候能解出来、有时候解不出来、有时候解出来的是错误的编码。这个问题可能的原因很多我按照排查优先级列一个表现象可能原因排查方法解决方案近距离正常远距离失败接收头灵敏度不足或发射功率不够逐步拉远距离测试临界点增加发射 LED 电流或更换高灵敏度接收头日光灯下误触发频繁电源噪声或环境光干扰遮挡接收头看是否改善加电源滤波电路或使用带屏蔽罩的接收头同一按键编码偶尔错误定时器中断被其他任务打断检查是否有其他库占用定时器关闭冲突库或调整中断优先级完全无输出接线错误或引脚配置错误用万用表测接收头输出检查接线确认引脚号与代码一致只能解出一种协议库配置中未启用其他协议检查库的配置文件在配置文件中开启需要的协议宏我遇到过一次很隐蔽的问题接收头输出正常但解码结果总是差一位。排查了很久才发现是电源纹波导致的。开发板通过 USB 供电而 USB 线质量不好5V 电源上有大约 200mV 的纹波。这个纹波叠加在接收头的输出上导致脉冲边沿的触发时间偏移了几十微秒刚好足以让解码器把某些位判错。换了一根带屏蔽的 USB 线之后问题就消失了。所以如果你遇到莫名其妙的解码错误不妨换一根好一点的电源线试试。5.2 定时器冲突与资源占用问题Arduino-IRremote在 AVR 平台上默认使用定时器 2 来生成 38kHz 载波和捕获脉冲。定时器 2 同时被 Arduino 的tone()函数和某些舵机控制库使用。如果你在代码里同时用了这些功能就会出现冲突——要么红外接收失效要么舵机抖动要么tone()发出的声音频率不对。解决冲突的办法有几种。第一种是改用其他定时器在库的配置文件里可以修改IR_USE_AVR_TIMER2这个宏把它改成IR_USE_AVR_TIMER1这样库就会使用定时器 1。但定时器 1 被Servo库占用了所以如果你同时用舵机和红外就需要更复杂的方案。第二种是使用软件 PWM 模式在配置里开启IR_USE_AVR_TIMER2的替代方案让库用micros()来做软件计时。软件计时的精度差一些但在大多数场景下够用。第三种是换用 ESP32 或 STM32 这类有多个定时器的平台从根本上避免资源冲突。Flash 和 RAM 的占用也值得关注。在 Uno 上如果开启了全部协议支持库会占用大约 20KB 的 Flash 和 1KB 的 RAM。Uno 总共只有 32KB Flash 和 2KB RAM这意味着你的主程序只剩下 12KB Flash 和 1KB RAM 可用。如果你的程序比较复杂就需要在库的配置文件里关掉不需要的协议。比如你只做 NEC 解码就把DECODE_SONY、DECODE_RC5这些宏都注释掉可以省下不少空间。5.3 发射距离短与信号方向性差发射距离短的原因通常有三个LED 驱动电流不足、载波频率偏差、LED 发射角度太窄。驱动电流的问题前面已经讲过这里重点说载波频率偏差。库默认的载波频率是 38kHz但实际生成的频率会受到定时器时钟源精度的影响。开发板上的陶瓷谐振器或晶振的精度通常在 1% 到 5% 之间这意味着实际载波频率可能在 36kHz 到 40kHz 之间波动。大多数接收头对载波频率的容忍度是正负 2kHz 左右所以轻微的偏差不会导致完全失效但会降低接收灵敏度。如果你发现发射距离明显短于预期可以用示波器或者逻辑分析仪测一下发射引脚的波形确认载波频率是否在 38kHz 附近。如果偏差太大可以在库的配置文件里调整定时器的预分频系数来校准。信号方向性差的问题在需要全向控制的场景下特别明显。红外 LED 的发光强度在正前方最强偏离轴线 30 度之后强度就下降一半以上。如果你把 LED 朝向一个方向遥控器就必须对准那个方向才能生效。解决方案是用多个 LED 组成阵列每个 LED 朝向不同的方向或者使用带散射透镜的 LED。我在一个项目中用了三个 LED分别朝向正前方、左前方和右前方覆盖角度从原来的 30 度扩展到了 90 度以上用户体验好了很多。5.4 空调遥控器长编码的处理技巧空调遥控器的红外编码和电视遥控器完全不同。电视遥控器按一次键发一帧一帧只有几个字节。空调遥控器按一次键会把当前的所有状态——温度、模式、风速、摆风、定时等等——打包成一帧发送一帧可能有十几到几十个字节。Arduino-IRremote的 NEC 解码器处理不了这么长的帧会报UNKNOWN或者只解出前面几个字节。处理空调编码有几种思路。第一种是用decodeHash()函数它不解析协议只是把原始脉冲序列计算出一个哈希值。你可以把这个哈希值当作按键编码来用接收端收到相同的哈希值就执行对应的操作。这种方法的优点是简单粗暴缺点是哈希值不包含状态信息你只能知道“用户按了某个键”不知道具体是什么状态。第二种是手动分析原始脉冲数据根据空调品牌的协议规范自己写解码器。这需要一些逆向工程的耐心但一旦解出来你就能获取完整的温度、模式等信息。第三种是直接用库的原始数据记录功能把整个脉冲序列存下来发射的时候原样回放。这种方法不需要理解协议但存储空间占用大而且换一个空调就要重新记录。我个人的建议是如果只是做简单的开关控制用decodeHash()就够了。如果需要精确控制温度和模式那就花时间手动分析协议。网上有一些开源项目已经逆向了不少常见空调品牌的协议可以参考它们的分析结果但要注意不同型号之间可能存在差异不能直接照搬。6. 进阶玩法与项目扩展方向6.1 用红外接收做信号分析仪Arduino-IRremote不仅能解码还能把原始脉冲数据输出出来这相当于一个简易的红外信号分析仪。你可以把接收到的原始数据通过串口发送到电脑用 Python 或 Excel 画成波形图直观地看到脉冲的时间分布。这对于分析未知协议非常有帮助。我写过一个简单的 Python 脚本从串口读取原始脉冲数据然后用 matplotlib 画出波形。通过观察波形可以很快判断出引导脉冲、数据位、结束脉冲的位置和宽度。如果你对某个遥控器的协议感兴趣这个方法比查资料快得多。脚本的核心逻辑就是解析串口输出的文本把正负数值分别提取出来然后用plt.bar()画柱状图。虽然简陋但足够用了。6.2 结合网络模块做远程红外控制把红外发射和网络模块结合起来就可以做一个可以通过手机或电脑远程控制的红外网关。接收端用红外接收头学习遥控器编码把编码存储在微控制器的 Flash 里然后通过网络模块接收控制指令根据指令发射对应的红外编码。这个项目的技术难点在于编码的存储和管理。如果你要学习多个遥控器的多个按键就需要一个结构化的存储方案。我的做法是用一个简单的键值对结构键是“设备名按键名”值是对应的协议类型、地址和命令。存储用微控制器的 EEPROM 或者外部 Flash 芯片。网络模块可以用常见的 WiFi 模块通过简单的 HTTP 接口或者 MQTT 协议来接收指令。实际部署的时候要注意红外发射的覆盖范围。如果你把网关放在客厅角落红外信号可能覆盖不到所有设备。我的解决方案是用多个红外发射管通过延长线分布在房间的不同位置或者用红外转发器把信号中继到更远的地方。6.3 低功耗场景下的优化策略如果你打算用电池供电做一个便携式的红外遥控器功耗就是一个必须考虑的问题。Arduino-IRremote在接收模式下会持续运行定时器中断功耗不低。在 3.3V 供电下一块开发板加上接收头的待机电流大约在 10mA 到 20mA 之间用普通电池撑不了太久。降低功耗的思路有几个。第一用休眠模式。在没有红外信号的时候让微控制器进入深度休眠只保留外部中断唤醒。接收头的输出引脚可以连接到外部中断引脚当有红外信号时接收头输出电平变化触发中断唤醒微控制器然后再启动红外解码。第二降低接收头的供电电压。大多数接收头的工作电压范围是 2.7V 到 5.5V在 3.3V 下工作比在 5V 下工作省电不少而且灵敏度下降不多。第三减少解码协议的开启数量只保留你实际需要的协议这样可以降低 CPU 的处理负担间接降低功耗。我在一个电池供电的项目中实测过用休眠模式加上 3.3V 供电待机电流可以降到 1mA 以下两节 AA 电池可以撑好几个月。唤醒响应时间大约在 10 毫秒左右对于遥控器应用来说完全够用。6.4 多协议共存时的内存管理当你需要同时支持多种协议时内存管理就变得重要了。每种协议的解码器都需要一定的 RAM 来存储中间状态和原始脉冲数据。库默认的原始脉冲缓冲区大小是 100 个条目每个条目 2 个字节也就是 200 字节。如果你开启了多个协议每个协议可能还需要额外的状态变量。在 RAM 紧张的板子上可以通过修改库的配置文件来减小缓冲区大小。比如你只解码 NEC 协议NEC 一帧最多 67 个脉冲缓冲区设成 80 就足够了不需要 100。另外如果你不需要原始脉冲数据可以在解码后立即释放缓冲区或者干脆关闭原始数据记录功能。这些调整虽然看起来不起眼但在资源受限的环境下能省出不少空间。还有一个容易被忽略的点是栈空间。红外解码过程中会调用多层函数如果栈空间不足可能会导致程序跑飞或者复位。在 AVR 平台上栈和堆是共享 RAM 的如果你动态分配了很多内存栈空间就会被压缩。建议在红外解码相关的代码中避免使用递归和大型局部数组把大数组定义成全局变量或者用static修饰让编译器把它分配到静态存储区而不是栈上。7. 一些零散但实用的经验关于接收头的选型我再补充一点金属屏蔽罩的接收头抗干扰能力明显强于塑料封装的但价格也贵一些。如果你做的是产品原型或者对稳定性要求高的场景多花几毛钱用屏蔽罩版本是值得的。另外接收头的输出引脚上最好串一个 1k 欧姆的电阻再接到微控制器这样可以限制电流防止接收头内部电路故障时把微控制器的引脚烧掉。关于代码调试我强烈建议在开发阶段打开DECODE_IR_SERIAL或者类似的调试输出宏把解码过程中的中间状态打印出来。虽然会占用一些串口带宽但能帮你快速定位问题。比如你可以看到引导脉冲是否被正确识别、数据位的宽度是否在预期范围内、校验是否通过等等。等到功能稳定了再关掉调试输出。关于遥控器的兼容性有一个事实需要接受没有任何一个库能支持所有遥控器。总会有一些冷门品牌或者老式设备使用非标准协议。遇到这种情况不要死磕库的自动解码功能直接上原始数据分析自己写解码逻辑。红外通信的原理并不复杂一旦你理解了脉冲和编码的关系手动分析一个未知协议通常只需要一两个小时。最后说一个关于发射的小技巧如果你发现发射时好时坏但代码和硬件都没问题检查一下发射 LED 的供电。红外 LED 在发射瞬间的电流脉冲可能达到几百毫安如果电源的内阻较大或者走线太细电压会瞬间跌落导致载波频率偏移或者发射功率不足。在 LED 的电源引脚附近并一个 100uF 的电解电容可以缓解这个问题它能在发射瞬间提供足够的电荷储备。这个技巧在电池供电的场景下尤其重要因为电池的内阻通常比稳压电源大得多。