1. 项目概述Infrared 是一个面向对象的 Arduino 红外信号处理库专为嵌入式底层开发者设计提供完整的 IR 信号生成、发送、接收、解码与协议建模能力。该库不依赖 Arduino 标准IRremote的中断密集型架构而是采用可配置的定时器驱动机制与状态机解码逻辑在资源受限的 8 位 AVR如 ATmega328P及 32 位 ARM Cortex-M如 STM32F103平台上均能实现高精度、低抖动的红外通信。其核心价值在于将物理层时序抽象为可继承、可组合的 C 类结构使开发者既能快速接入 NEC、RC5、Sony、NECx、Raw 等主流协议又能通过子类扩展自定义协议或硬件适配层。该库并非仅适用于 Arduino IDE 生态——其底层驱动模块如IRTimer,IRPin已明确分离硬件抽象层HAL支持无缝移植至 STM32 HAL 库、CMSIS-RTOS 或裸机环境。在实际工业场景中该库已被用于红外遥控学习型网关、多协议家电控制器、红外信标定位节点等对时序精度与协议兼容性要求严苛的嵌入式系统。2. 系统架构与设计哲学2.1 分层架构模型Infrared 库采用清晰的四层架构每一层职责单一且接口稳定层级模块名职责可移植性物理层PHYIRPin,IRTimer管理 GPIO 输入/输出模式切换、精确载波频率生成38kHz/40kHz/56kHz、边沿捕获定时器配置✅ 完全可重写适配任意 MCU 定时器如 STM32 TIM1 CH1 输出 PWM TIM2 输入捕获链路层LINKIRSignal,IRProtocol封装原始脉宽数据mark/space 阵列、定义协议帧结构起始位、地址、命令、校验、实现编码/解码状态机✅ 协议逻辑与硬件无关可跨平台复用应用层APPIRTransmitter,IRReceiver,IRDecoder提供面向用户的高级 API发送命令、注册回调、设置超时、启动接收循环✅ 接口统一上层业务逻辑无需修改集成层INTEGRATIONArduinoAdapter,STM32HALAdapter提供 Arduinomicros()/digitalWrite()与 STM32 HALHAL_GetTick()/HAL_GPIO_WritePin()的胶水代码⚠️ 需按目标平台选择对应适配器此分层设计使得工程师可在不改动协议逻辑的前提下将 ATmega328P 上验证通过的 NEC 解码器直接部署到 STM32G071RB 上仅需替换IRTimer实现并配置对应 GPIO 引脚。2.2 面向对象建模原理库中所有核心类均基于虚函数机制构建多态接口关键继承关系如下class IRSignal { public: virtual uint8_t getProtocol() const 0; // 返回协议枚举值 virtual uint32_t getAddress() const 0; // 地址字段若支持 virtual uint32_t getCommand() const 0; // 命令字段 virtual bool isValid() const 0; // 校验是否通过 }; class NEC_Signal : public IRSignal { private: uint16_t address_; uint16_t command_; bool repeat_; public: NEC_Signal(uint16_t addr, uint16_t cmd, bool rpt false); uint8_t getProtocol() const override { return NEC; } uint32_t getAddress() const override { return address_; } uint32_t getCommand() const override { return command_; } bool isValid() const override; }; class Raw_Signal : public IRSignal { private: uint16_t* raw_data_; // mark/space 微秒数组 uint8_t length_; public: Raw_Signal(uint16_t* data, uint8_t len); uint8_t getProtocol() const override { return RAW; } uint32_t getAddress() const override { return 0; } uint32_t getCommand() const override { return 0; } bool isValid() const override { return length_ 0; } };这种设计允许用户在运行时通过基类指针统一处理不同协议信号void processSignal(IRSignal* sig) { switch (sig-getProtocol()) { case NEC: Serial.printf(NEC: Addr0x%04X Cmd0x%04X\n, (uint16_t)sig-getAddress(), (uint16_t)sig-getCommand()); break; case SONY: Serial.printf(SONY: Cmd0x%03X\n, (uint16_t)sig-getCommand()); break; case RAW: Serial.print(RAW: ); for (int i 0; i ((Raw_Signal*)sig)-getLength(); i) { Serial.print(((Raw_Signal*)sig)-getRawData()[i]); Serial.print( ); } Serial.println(); break; } }3. 核心功能详解3.1 红外发射IR Transmission3.1.1 载波生成机制IRTransmitter类通过IRTimer实现精确载波调制。以 38kHz 为例周期为 26.316μs需在 13.158μs 处翻转 GPIO 电平。库采用以下两种模式PWM 模式推荐使用硬件 PWM 输出方波CPU 开销趋近于零。ATmega328P 使用 Timer1 快速 PWM 模式预分频 1OCR1A 20916MHz / (2 × 209) ≈ 38.2kHz。软件翻转模式当硬件 PWM 不可用时如某些引脚启用IRTimer::togglePin()配合TCNT中断在中断服务程序中手动翻转 GPIO。此时需严格控制 ISR 执行时间 1μs避免相位漂移。关键参数配置表参数类型默认值说明carrierFreqHzuint32_t38000载波频率支持 30–56kHz 连续可调dutyCyclePercentuint8_t50占空比影响发射功率与接收灵敏度平衡pinuint8_t3发射引脚需支持 PWMinvertedboolfalse是否反相输出适配某些红外 LED 驱动电路3.1.2 协议编码流程以 NEC 协议为例IRTransmitter::sendNEC()执行以下步骤生成引导脉冲9ms 低电平 4.5ms 高电平发送地址与命令各 8 位低位先行每比特以 560μs 低电平开始后接0: 560μs 高电平1: 1.69ms 高电平发送反码地址与命令的按位取反用于校验结束脉冲560μs 低电平完整发送示例HAL 风格// STM32F103 使用 HAL 库初始化 IRPin txPin(GPIOB, GPIO_PIN_6); // PB6 IRTimer txTimer(htim2); // 使用 TIM2 生成 38kHz PWM IRTransmitter transmitter(txPin, txTimer); void sendPowerCommand() { NEC_Signal powerSig(0x00FF, 0x0002); // 地址 0x00FF, 命令 0x0002 (POWER) transmitter.send(powerSig, 38000, 50); // 38kHz, 50% 占空比 delayMicroseconds(100000); // 确保完成发送NEC 帧长 ~27ms }3.2 红外接收与解码IR Reception Decoding3.2.1 硬件捕获原理IRReceiver依赖IRTimer的输入捕获功能。以 STM32F103 为例配置 TIM2 通道 1 为输入捕获模式检测红外接收头如 VS1838B输出的下降沿有效信号与上升沿空闲。关键配置滤波器ICPSC 0x0F采样 8 次防毛刺预分频PSC 71APB136MHz → 计数器频率500kHz分辨率为 2μs极性下降沿触发接收头空闲高电平信号来临时拉低捕获到的边沿时间戳存入环形缓冲区由IRDecoder后台线程解析。3.2.2 解码状态机设计IRDecoder采用有限状态机FSM处理原始脉宽序列以 NEC 为例的状态转移图IDLE → (9ms↓) → LEADER_LOW LEADER_LOW → (4.5ms↑) → LEADER_HIGH LEADER_HIGH → (560μs↓) → BIT_START BIT_START → (560μs↑) → BIT_0_HIGH BIT_START → (1.69ms↑) → BIT_1_HIGH ... → (8 bits addr) → (8 bits addr_inv) → (8 bits cmd) → (8 bits cmd_inv) → STOP每个状态均设超时阈值如BIT_0_HIGH超时为 1.1ms超时即判定为帧错误返回IRDecoder::ERROR_INVALID_FRAME。3.2.3 多协议动态识别库支持在无协议先验知识下自动识别信号类型。IRDecoder::autoDetect()对首段脉宽序列进行特征匹配特征NECRC5Sony引导低电平9000±500μs无引导2400μs引导高电平4500±500μs无引导600μs比特周期~2.25ms1.778ms双相1.2ms12bit起始标志9ms4.5ms连续两个 889μs 低电平2.4ms0.6ms该机制使单个接收器可同时兼容空调、电视、机顶盒等不同品牌遥控器。3.3 原始信号Raw Signal处理Raw_Signal类是库的底层基石用于捕获未经协议解析的原始时序。其典型应用场景包括协议逆向分析记录未知遥控器波形导出 CSV 供逻辑分析仪比对自定义协议开发基于Raw_Signal构建私有帧结构如添加 CRC16、加密字段抗干扰训练采集多组噪声环境下的信号构建机器学习分类模型原始数据捕获示例ATmega328P#define RAW_BUFFER_SIZE 100 uint16_t rawBuffer[RAW_BUFFER_SIZE]; uint8_t rawLength 0; void onRawCaptureComplete(uint16_t* data, uint8_t len) { if (len RAW_BUFFER_SIZE) { memcpy(rawBuffer, data, len * sizeof(uint16_t)); rawLength len; Serial.print(Raw capture: ); for (int i 0; i len; i) { Serial.print(data[i]); Serial.print( ); } Serial.println(); } } // 启动原始捕获最大 100 个脉宽超时 15ms IRReceiver receiver(rxPin, rxTimer); receiver.startRawCapture(rawBuffer, RAW_BUFFER_SIZE, 15000, onRawCaptureComplete);4. 关键 API 接口详述4.1IRTransmitter类接口函数签名功能参数说明返回值send(const IRSignal* sig, uint32_t freq, uint8_t duty)发送指定协议信号sig: 信号对象指针freq: 载波频率Hzduty: 占空比0–100true成功false失败如引脚未就绪sendRaw(const uint16_t* data, uint8_t len, uint32_t freq, uint8_t duty)发送原始脉宽序列data: mark/space 微秒数组偶数长度索引0为marklen: 元素个数同上setCarrierPolarity(bool inverted)设置载波极性inverted:true表示低电平有效适配 NPN 驱动—isBusy()查询发送状态—true正在发送false空闲4.2IRReceiver类接口函数签名功能参数说明返回值startReceive()启动连续接收—true成功启动stopReceive()停止接收——getLatestSignal()获取最新解码信号—IRSignal*若无新信号则返回nullptrstartRawCapture(uint16_t* buffer, uint8_t size, uint16_t timeout_us, void(*callback)(uint16_t*, uint8_t))启动原始捕获buffer: 存储缓冲区size: 最大元素数timeout_us: 捕获超时callback: 完成回调true成功4.3IRDecoder类接口函数签名功能参数说明返回值decode(const uint16_t* raw, uint8_t len)解码原始脉宽为协议信号raw: 原始数据len: 长度IRSignal*失败返回nullptrautoDetect(const uint16_t* raw, uint8_t len)自动识别协议类型同上uint8_t协议枚举值NEC,RC5,SONY等getErrorReason()获取最近一次解码错误原因—IRDecoder::Error枚举TIMEOUT,INVALID_CHECKSUM,UNKNOWN_PROTOCOL5. 硬件适配与移植指南5.1 STM32 HAL 移植要点在 STM32CubeMX 生成的工程中需完成以下适配GPIO 初始化// 接收引脚PB0上拉输入无外部中断 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, GPIO_InitStruct);TIM2 输入捕获配置// TIM2_CH1 (PB0) 捕获下降沿 htim2.Instance TIM2; htim2.Init.Prescaler 71; // 36MHz / 72 500kHz htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 0xFFFF; HAL_TIM_IC_Init(htim2); TIM_IC_InitTypeDef sConfigIC {0}; sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_FALLING; sConfigIC.ICSelection TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler TIM_ICPSC_DIV8; // 8次采样消抖 sConfigIC.ICFilter 3; // 采样窗口 3 个时钟 HAL_TIM_IC_ConfigChannel(htim2, sConfigIC, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1);中断服务程序void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { if (htim-Instance TIM2 htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { uint32_t ts HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 将时间戳提交给 IRReceiver 的环形缓冲区 IRReceiver::pushTimestamp(ts); } }5.2 FreeRTOS 集成方案在 RTOS 环境中推荐将接收任务设计为高优先级事件驱动QueueHandle_t irSignalQueue; void irReceiverTask(void* pvParameters) { IRReceiver receiver(rxPin, rxTimer); receiver.startReceive(); while (1) { IRSignal* sig receiver.getLatestSignal(); if (sig ! nullptr) { // 发送至队列交由应用任务处理 xQueueSend(irSignalQueue, sig, portMAX_DELAY); } vTaskDelay(1); // 释放 CPU } } // 应用任务中处理 void appTask(void* pvParameters) { IRSignal* sig; while (1) { if (xQueueReceive(irSignalQueue, sig, portMAX_DELAY) pdTRUE) { processSignal(sig); delete sig; // 注意内存释放 } } }6. 实际工程问题与解决方案6.1 接收距离短、误码率高现象VS1838B 在 2 米外接收失败逻辑分析仪显示脉宽抖动 ±300μs。根因分析电源噪声导致接收头供电不稳实测纹波达 150mVppPCB 布线过长10cm引入容性耦合干扰接收头未加装 38kHz 带通滤波器仅依赖内部滤波解决措施在 VS1838B VCC 引脚就近并联 10μF 钽电容 100nF 陶瓷电容接收头输出线使用 50Ω 阻抗匹配走线长度 ≤ 3cm在 MCU 输入引脚前串联 100Ω 电阻 100pF 电容构成 RC 低通截止频率 ≈ 16MHz抑制高频噪声6.2 多遥控器串扰现象同一空间内多个 NEC 遥控器同时操作接收器频繁误触发。解决方案启用IRReceiver::setRepeatFilter(true)丢弃间隔 100ms 的重复帧NEC 重复帧间隔为 108ms在应用层增加地址白名单机制static const uint16_t validAddresses[] {0x00FF, 0x1234, 0xABCD}; bool isFromTrustedDevice(IRSignal* sig) { for (int i 0; i sizeof(validAddresses)/sizeof(uint16_t); i) { if (sig-getAddress() validAddresses[i]) return true; } return false; }6.3 低功耗模式唤醒失效现象MCU 进入 STOP 模式后红外无法唤醒。硬件要求选择支持 EXTI 唤醒的 GPIO如 STM32F103 的 PA0–PA15, PB0–PB15VS1838B 输出需连接至该 GPIO并配置为下降沿触发软件配置// 使能 SYSCFG 时钟 __HAL_RCC_SYSCFG_CLK_ENABLE(); // 映射 PB0 到 EXTI0 SYSCFG_EXTILineConfig(EXTI_PORT_SOURCE_GPIOB, EXTI_PIN_SOURCE_0); // 配置 EXTI0 下降沿触发 EXTI_InitStructure.EXTI_Line EXTI_LINE_0; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); // 进入 STOP 模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);7. 性能基准测试数据在 ATmega328P 16MHz 平台上实测操作时间开销内存占用备注IRTransmitter::sendNEC()26.8ms0 B栈包含 108ms 重复间隔等待IRDecoder::decode()NEC124μs48 B栈从捕获完成到返回IRSignal*IRReceiver::startReceive()3.2μs0 B仅使能定时器与 NVICRaw_Signal100 点捕获15.6ms200 B缓冲区最大支持 200 点原始数据在 STM32F103C8T6 72MHz 平台上IRDecoder::decode()降至 42μs证明 ARM 架构对状态机解码具有显著加速优势。8. 协议扩展开发实践以扩展小米米家协议MiJia为例其帧结构为引导4.5ms 低 4.5ms 高数据32 位每位 1.125ms 周期00.25ms 高0.875ms 低10.875ms 高0.25ms 低校验32 位数据异或和只需继承IRSignal与IRProtocolclass MiJia_Signal : public IRSignal { private: uint32_t payload_; uint8_t checksum_; public: MiJia_Signal(uint32_t pay, uint8_t chk) : payload_(pay), checksum_(chk) {} uint8_t getProtocol() const override { return MIJIA; } uint32_t getCommand() const override { return payload_; } bool isValid() const override { return (checksum_ calcChecksum(payload_)); } }; class MiJia_Protocol : public IRProtocol { public: IRSignal* decode(const uint16_t* raw, uint8_t len) override { if (!checkLeader(raw)) return nullptr; uint32_t data extractData(raw 2, 32); uint8_t chk raw[len-1]; return new MiJia_Signal(data, chk); } private: bool checkLeader(const uint16_t* raw) { return (abs(raw[0]-4500) 500) (abs(raw[1]-4500) 500); } };将MiJia_Protocol注册至IRDecoder::registerProtocol(MIJIA, new MiJia_Protocol())即可启用。在某智能插座项目中工程师基于此框架在 3 天内完成米家协议对接较传统IRremote库节省 70% 调试时间。
面向对象的嵌入式红外协议库设计与跨平台实践
1. 项目概述Infrared 是一个面向对象的 Arduino 红外信号处理库专为嵌入式底层开发者设计提供完整的 IR 信号生成、发送、接收、解码与协议建模能力。该库不依赖 Arduino 标准IRremote的中断密集型架构而是采用可配置的定时器驱动机制与状态机解码逻辑在资源受限的 8 位 AVR如 ATmega328P及 32 位 ARM Cortex-M如 STM32F103平台上均能实现高精度、低抖动的红外通信。其核心价值在于将物理层时序抽象为可继承、可组合的 C 类结构使开发者既能快速接入 NEC、RC5、Sony、NECx、Raw 等主流协议又能通过子类扩展自定义协议或硬件适配层。该库并非仅适用于 Arduino IDE 生态——其底层驱动模块如IRTimer,IRPin已明确分离硬件抽象层HAL支持无缝移植至 STM32 HAL 库、CMSIS-RTOS 或裸机环境。在实际工业场景中该库已被用于红外遥控学习型网关、多协议家电控制器、红外信标定位节点等对时序精度与协议兼容性要求严苛的嵌入式系统。2. 系统架构与设计哲学2.1 分层架构模型Infrared 库采用清晰的四层架构每一层职责单一且接口稳定层级模块名职责可移植性物理层PHYIRPin,IRTimer管理 GPIO 输入/输出模式切换、精确载波频率生成38kHz/40kHz/56kHz、边沿捕获定时器配置✅ 完全可重写适配任意 MCU 定时器如 STM32 TIM1 CH1 输出 PWM TIM2 输入捕获链路层LINKIRSignal,IRProtocol封装原始脉宽数据mark/space 阵列、定义协议帧结构起始位、地址、命令、校验、实现编码/解码状态机✅ 协议逻辑与硬件无关可跨平台复用应用层APPIRTransmitter,IRReceiver,IRDecoder提供面向用户的高级 API发送命令、注册回调、设置超时、启动接收循环✅ 接口统一上层业务逻辑无需修改集成层INTEGRATIONArduinoAdapter,STM32HALAdapter提供 Arduinomicros()/digitalWrite()与 STM32 HALHAL_GetTick()/HAL_GPIO_WritePin()的胶水代码⚠️ 需按目标平台选择对应适配器此分层设计使得工程师可在不改动协议逻辑的前提下将 ATmega328P 上验证通过的 NEC 解码器直接部署到 STM32G071RB 上仅需替换IRTimer实现并配置对应 GPIO 引脚。2.2 面向对象建模原理库中所有核心类均基于虚函数机制构建多态接口关键继承关系如下class IRSignal { public: virtual uint8_t getProtocol() const 0; // 返回协议枚举值 virtual uint32_t getAddress() const 0; // 地址字段若支持 virtual uint32_t getCommand() const 0; // 命令字段 virtual bool isValid() const 0; // 校验是否通过 }; class NEC_Signal : public IRSignal { private: uint16_t address_; uint16_t command_; bool repeat_; public: NEC_Signal(uint16_t addr, uint16_t cmd, bool rpt false); uint8_t getProtocol() const override { return NEC; } uint32_t getAddress() const override { return address_; } uint32_t getCommand() const override { return command_; } bool isValid() const override; }; class Raw_Signal : public IRSignal { private: uint16_t* raw_data_; // mark/space 微秒数组 uint8_t length_; public: Raw_Signal(uint16_t* data, uint8_t len); uint8_t getProtocol() const override { return RAW; } uint32_t getAddress() const override { return 0; } uint32_t getCommand() const override { return 0; } bool isValid() const override { return length_ 0; } };这种设计允许用户在运行时通过基类指针统一处理不同协议信号void processSignal(IRSignal* sig) { switch (sig-getProtocol()) { case NEC: Serial.printf(NEC: Addr0x%04X Cmd0x%04X\n, (uint16_t)sig-getAddress(), (uint16_t)sig-getCommand()); break; case SONY: Serial.printf(SONY: Cmd0x%03X\n, (uint16_t)sig-getCommand()); break; case RAW: Serial.print(RAW: ); for (int i 0; i ((Raw_Signal*)sig)-getLength(); i) { Serial.print(((Raw_Signal*)sig)-getRawData()[i]); Serial.print( ); } Serial.println(); break; } }3. 核心功能详解3.1 红外发射IR Transmission3.1.1 载波生成机制IRTransmitter类通过IRTimer实现精确载波调制。以 38kHz 为例周期为 26.316μs需在 13.158μs 处翻转 GPIO 电平。库采用以下两种模式PWM 模式推荐使用硬件 PWM 输出方波CPU 开销趋近于零。ATmega328P 使用 Timer1 快速 PWM 模式预分频 1OCR1A 20916MHz / (2 × 209) ≈ 38.2kHz。软件翻转模式当硬件 PWM 不可用时如某些引脚启用IRTimer::togglePin()配合TCNT中断在中断服务程序中手动翻转 GPIO。此时需严格控制 ISR 执行时间 1μs避免相位漂移。关键参数配置表参数类型默认值说明carrierFreqHzuint32_t38000载波频率支持 30–56kHz 连续可调dutyCyclePercentuint8_t50占空比影响发射功率与接收灵敏度平衡pinuint8_t3发射引脚需支持 PWMinvertedboolfalse是否反相输出适配某些红外 LED 驱动电路3.1.2 协议编码流程以 NEC 协议为例IRTransmitter::sendNEC()执行以下步骤生成引导脉冲9ms 低电平 4.5ms 高电平发送地址与命令各 8 位低位先行每比特以 560μs 低电平开始后接0: 560μs 高电平1: 1.69ms 高电平发送反码地址与命令的按位取反用于校验结束脉冲560μs 低电平完整发送示例HAL 风格// STM32F103 使用 HAL 库初始化 IRPin txPin(GPIOB, GPIO_PIN_6); // PB6 IRTimer txTimer(htim2); // 使用 TIM2 生成 38kHz PWM IRTransmitter transmitter(txPin, txTimer); void sendPowerCommand() { NEC_Signal powerSig(0x00FF, 0x0002); // 地址 0x00FF, 命令 0x0002 (POWER) transmitter.send(powerSig, 38000, 50); // 38kHz, 50% 占空比 delayMicroseconds(100000); // 确保完成发送NEC 帧长 ~27ms }3.2 红外接收与解码IR Reception Decoding3.2.1 硬件捕获原理IRReceiver依赖IRTimer的输入捕获功能。以 STM32F103 为例配置 TIM2 通道 1 为输入捕获模式检测红外接收头如 VS1838B输出的下降沿有效信号与上升沿空闲。关键配置滤波器ICPSC 0x0F采样 8 次防毛刺预分频PSC 71APB136MHz → 计数器频率500kHz分辨率为 2μs极性下降沿触发接收头空闲高电平信号来临时拉低捕获到的边沿时间戳存入环形缓冲区由IRDecoder后台线程解析。3.2.2 解码状态机设计IRDecoder采用有限状态机FSM处理原始脉宽序列以 NEC 为例的状态转移图IDLE → (9ms↓) → LEADER_LOW LEADER_LOW → (4.5ms↑) → LEADER_HIGH LEADER_HIGH → (560μs↓) → BIT_START BIT_START → (560μs↑) → BIT_0_HIGH BIT_START → (1.69ms↑) → BIT_1_HIGH ... → (8 bits addr) → (8 bits addr_inv) → (8 bits cmd) → (8 bits cmd_inv) → STOP每个状态均设超时阈值如BIT_0_HIGH超时为 1.1ms超时即判定为帧错误返回IRDecoder::ERROR_INVALID_FRAME。3.2.3 多协议动态识别库支持在无协议先验知识下自动识别信号类型。IRDecoder::autoDetect()对首段脉宽序列进行特征匹配特征NECRC5Sony引导低电平9000±500μs无引导2400μs引导高电平4500±500μs无引导600μs比特周期~2.25ms1.778ms双相1.2ms12bit起始标志9ms4.5ms连续两个 889μs 低电平2.4ms0.6ms该机制使单个接收器可同时兼容空调、电视、机顶盒等不同品牌遥控器。3.3 原始信号Raw Signal处理Raw_Signal类是库的底层基石用于捕获未经协议解析的原始时序。其典型应用场景包括协议逆向分析记录未知遥控器波形导出 CSV 供逻辑分析仪比对自定义协议开发基于Raw_Signal构建私有帧结构如添加 CRC16、加密字段抗干扰训练采集多组噪声环境下的信号构建机器学习分类模型原始数据捕获示例ATmega328P#define RAW_BUFFER_SIZE 100 uint16_t rawBuffer[RAW_BUFFER_SIZE]; uint8_t rawLength 0; void onRawCaptureComplete(uint16_t* data, uint8_t len) { if (len RAW_BUFFER_SIZE) { memcpy(rawBuffer, data, len * sizeof(uint16_t)); rawLength len; Serial.print(Raw capture: ); for (int i 0; i len; i) { Serial.print(data[i]); Serial.print( ); } Serial.println(); } } // 启动原始捕获最大 100 个脉宽超时 15ms IRReceiver receiver(rxPin, rxTimer); receiver.startRawCapture(rawBuffer, RAW_BUFFER_SIZE, 15000, onRawCaptureComplete);4. 关键 API 接口详述4.1IRTransmitter类接口函数签名功能参数说明返回值send(const IRSignal* sig, uint32_t freq, uint8_t duty)发送指定协议信号sig: 信号对象指针freq: 载波频率Hzduty: 占空比0–100true成功false失败如引脚未就绪sendRaw(const uint16_t* data, uint8_t len, uint32_t freq, uint8_t duty)发送原始脉宽序列data: mark/space 微秒数组偶数长度索引0为marklen: 元素个数同上setCarrierPolarity(bool inverted)设置载波极性inverted:true表示低电平有效适配 NPN 驱动—isBusy()查询发送状态—true正在发送false空闲4.2IRReceiver类接口函数签名功能参数说明返回值startReceive()启动连续接收—true成功启动stopReceive()停止接收——getLatestSignal()获取最新解码信号—IRSignal*若无新信号则返回nullptrstartRawCapture(uint16_t* buffer, uint8_t size, uint16_t timeout_us, void(*callback)(uint16_t*, uint8_t))启动原始捕获buffer: 存储缓冲区size: 最大元素数timeout_us: 捕获超时callback: 完成回调true成功4.3IRDecoder类接口函数签名功能参数说明返回值decode(const uint16_t* raw, uint8_t len)解码原始脉宽为协议信号raw: 原始数据len: 长度IRSignal*失败返回nullptrautoDetect(const uint16_t* raw, uint8_t len)自动识别协议类型同上uint8_t协议枚举值NEC,RC5,SONY等getErrorReason()获取最近一次解码错误原因—IRDecoder::Error枚举TIMEOUT,INVALID_CHECKSUM,UNKNOWN_PROTOCOL5. 硬件适配与移植指南5.1 STM32 HAL 移植要点在 STM32CubeMX 生成的工程中需完成以下适配GPIO 初始化// 接收引脚PB0上拉输入无外部中断 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, GPIO_InitStruct);TIM2 输入捕获配置// TIM2_CH1 (PB0) 捕获下降沿 htim2.Instance TIM2; htim2.Init.Prescaler 71; // 36MHz / 72 500kHz htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 0xFFFF; HAL_TIM_IC_Init(htim2); TIM_IC_InitTypeDef sConfigIC {0}; sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_FALLING; sConfigIC.ICSelection TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler TIM_ICPSC_DIV8; // 8次采样消抖 sConfigIC.ICFilter 3; // 采样窗口 3 个时钟 HAL_TIM_IC_ConfigChannel(htim2, sConfigIC, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1);中断服务程序void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { if (htim-Instance TIM2 htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { uint32_t ts HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 将时间戳提交给 IRReceiver 的环形缓冲区 IRReceiver::pushTimestamp(ts); } }5.2 FreeRTOS 集成方案在 RTOS 环境中推荐将接收任务设计为高优先级事件驱动QueueHandle_t irSignalQueue; void irReceiverTask(void* pvParameters) { IRReceiver receiver(rxPin, rxTimer); receiver.startReceive(); while (1) { IRSignal* sig receiver.getLatestSignal(); if (sig ! nullptr) { // 发送至队列交由应用任务处理 xQueueSend(irSignalQueue, sig, portMAX_DELAY); } vTaskDelay(1); // 释放 CPU } } // 应用任务中处理 void appTask(void* pvParameters) { IRSignal* sig; while (1) { if (xQueueReceive(irSignalQueue, sig, portMAX_DELAY) pdTRUE) { processSignal(sig); delete sig; // 注意内存释放 } } }6. 实际工程问题与解决方案6.1 接收距离短、误码率高现象VS1838B 在 2 米外接收失败逻辑分析仪显示脉宽抖动 ±300μs。根因分析电源噪声导致接收头供电不稳实测纹波达 150mVppPCB 布线过长10cm引入容性耦合干扰接收头未加装 38kHz 带通滤波器仅依赖内部滤波解决措施在 VS1838B VCC 引脚就近并联 10μF 钽电容 100nF 陶瓷电容接收头输出线使用 50Ω 阻抗匹配走线长度 ≤ 3cm在 MCU 输入引脚前串联 100Ω 电阻 100pF 电容构成 RC 低通截止频率 ≈ 16MHz抑制高频噪声6.2 多遥控器串扰现象同一空间内多个 NEC 遥控器同时操作接收器频繁误触发。解决方案启用IRReceiver::setRepeatFilter(true)丢弃间隔 100ms 的重复帧NEC 重复帧间隔为 108ms在应用层增加地址白名单机制static const uint16_t validAddresses[] {0x00FF, 0x1234, 0xABCD}; bool isFromTrustedDevice(IRSignal* sig) { for (int i 0; i sizeof(validAddresses)/sizeof(uint16_t); i) { if (sig-getAddress() validAddresses[i]) return true; } return false; }6.3 低功耗模式唤醒失效现象MCU 进入 STOP 模式后红外无法唤醒。硬件要求选择支持 EXTI 唤醒的 GPIO如 STM32F103 的 PA0–PA15, PB0–PB15VS1838B 输出需连接至该 GPIO并配置为下降沿触发软件配置// 使能 SYSCFG 时钟 __HAL_RCC_SYSCFG_CLK_ENABLE(); // 映射 PB0 到 EXTI0 SYSCFG_EXTILineConfig(EXTI_PORT_SOURCE_GPIOB, EXTI_PIN_SOURCE_0); // 配置 EXTI0 下降沿触发 EXTI_InitStructure.EXTI_Line EXTI_LINE_0; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); // 进入 STOP 模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);7. 性能基准测试数据在 ATmega328P 16MHz 平台上实测操作时间开销内存占用备注IRTransmitter::sendNEC()26.8ms0 B栈包含 108ms 重复间隔等待IRDecoder::decode()NEC124μs48 B栈从捕获完成到返回IRSignal*IRReceiver::startReceive()3.2μs0 B仅使能定时器与 NVICRaw_Signal100 点捕获15.6ms200 B缓冲区最大支持 200 点原始数据在 STM32F103C8T6 72MHz 平台上IRDecoder::decode()降至 42μs证明 ARM 架构对状态机解码具有显著加速优势。8. 协议扩展开发实践以扩展小米米家协议MiJia为例其帧结构为引导4.5ms 低 4.5ms 高数据32 位每位 1.125ms 周期00.25ms 高0.875ms 低10.875ms 高0.25ms 低校验32 位数据异或和只需继承IRSignal与IRProtocolclass MiJia_Signal : public IRSignal { private: uint32_t payload_; uint8_t checksum_; public: MiJia_Signal(uint32_t pay, uint8_t chk) : payload_(pay), checksum_(chk) {} uint8_t getProtocol() const override { return MIJIA; } uint32_t getCommand() const override { return payload_; } bool isValid() const override { return (checksum_ calcChecksum(payload_)); } }; class MiJia_Protocol : public IRProtocol { public: IRSignal* decode(const uint16_t* raw, uint8_t len) override { if (!checkLeader(raw)) return nullptr; uint32_t data extractData(raw 2, 32); uint8_t chk raw[len-1]; return new MiJia_Signal(data, chk); } private: bool checkLeader(const uint16_t* raw) { return (abs(raw[0]-4500) 500) (abs(raw[1]-4500) 500); } };将MiJia_Protocol注册至IRDecoder::registerProtocol(MIJIA, new MiJia_Protocol())即可启用。在某智能插座项目中工程师基于此框架在 3 天内完成米家协议对接较传统IRremote库节省 70% 调试时间。