基于STM32的红外遥控解码与发射实战指南 简介一套基于STM32的红外信号解码与遥控应用示例工程面向嵌入式开发者和电子竞赛学生解决红外遥控协议解析、按键识别与模块调试等常见问题。压缩包共含156个文件大小约7.84MB以64个C源文件与63个头文件为主体另有汇编启动文件、hex固件、Keil工程文件、批处理脚本以及PDF/Word说明文档便于从编译、烧录到查阅文档完整使用。工程内已整合LCD显示、触摸控制与USMART调试接口等关联驱动便于在带屏环境下直观验证红外收发效果。这个工程目前已有1921人学习/下载足见其在STM32红外入门方向上的实用价值适合作为红外遥控、嵌入式课设或相关项目的入门参考。整体目录结构按功能模块组织读者可直接打开工程编译也可按需提取红外解码逻辑移植到其他项目。 做嵌入式这几年红外遥控算是遇到最多的需求之一。不管是用STM32做一个智能台灯还是给老空调加一个Wi-Fi转红外的控制器最后都要落到同一个问题上怎么把红外遥控器的代码程序写对。这个项目标题“基于stm32的红外代码程序”看起来简单真正做起来涉及的协议解析、定时器捕获、PWM载波、中断优先级哪一个没处理好都会让你在按键上反复横跳。这篇文章我把整套红外代码程序的思路、硬件接线、NEC解码和发射代码、以及调试中的坑一起讲清楚适合正在做遥控器项目或者想拿红外练手STM32定时的朋友。1. 项目整体设计思路先搞清楚NEC协议再动手写代码1.1 常见的红外协议对比为什么NEC是首选红外遥控不是一个统一标准市面上常见的有NEC、Philips RC-5、Sony SIRC等。NEC因为日系遥控器普及率高资料多成了绝大多数开发板教程的首选。NEC协议用38kHz载波调制数据位由脉冲宽度区分。它的逻辑“1”是560us低电平脉冲加1690us高电平逻辑“0”是560us加560us引导码则是9ms低电平加4.5ms高电平。一次完整帧通常包含地址码、地址反码、数据码、数据反码校验简单到只看反码是否对得上。对比其他协议RC-5用曼彻斯特编码解码时要跟踪时钟周期SIRC有7位和12位两种格式时序随版本变化。NEC的时序在16MHz到72MHz单片机上都能轻松测量用外部中断或者输入捕获都容易实现。对于项目原型照着NEC做几乎不会错。如果你之后要控制某些欧洲品牌电视再回头查对方用的是什么协议即可核心解码思路完全通用。1.2 接收、解码、执行、发射的四段式架构我把整个红外代码程序拆成四段接收、解码、执行、发射。接收是硬件层由一体化接收头完成光电转换和38kHz载波解调输出一串高低电平解码是软件层靠单片机边沿中断或输入捕获测量每个脉冲宽度然后拼出地址和数据执行就是把解出来的按键码对应到业务逻辑上比如按键码0x45就开灯0x46就关灯发射是逆过程用定时器产生38kHz PWM再按协议时序让PWM导通或关断把红外光发出去。这样拆分之后每个模块都能单独测试。我最建议的顺序是先做接收解码用串口打印按键码再做发射把任意一个按键码原样发出去最后接继电器、风扇或灯光。千万别一上来就写业务逻辑否则红外链路没通你根本分不清是解码问题还是业务问题。实际项目里我见过有人花了三天调“遥控开锁”的代码最后发现是接收头供电接错这种时间浪费完全可以靠分层调试避免。1.3 动手前的准备开发环境与工具选型做STM32红外项目开发环境我推荐STM32CubeMX生成初始化代码再用Keil MDK写业务逻辑。CubeMX里把时钟树配好外设引脚一拖GPIO、定时器、串口的中断初始化就自动生成了能省很多低级错误。需要注意的是Keil MDK必须装对应芯片的器件包很多人直接用Keil C51版去建STM32工程编译一堆“Unknown Device”错误这是环境没装对。调试工具方面ST-Link和ST-Link Utility配合用可以看烧录状态也能读取芯片内部Flash。建议再用一个逻辑分析仪或者示波器看红外波形。逻辑分析仪几十块钱就能用能看到接收头输出的每段脉冲宽度比盲猜代码靠谱得多。如果没有也要准备一个串口调试助手把解码出来的间隔数据打印出来对着NEC时序表确认。2. 硬件设计与接线器件选型、供电和几个容易踩的坑2.1 接收头与发射管怎么选、怎么接接收头首选一体化红外接收头比如VS1838B、TSOP38238很多开发板配套的“红外接收模块”本质都是把光敏二极管、放大电路和带通滤波器封装在一起只输出解调后的波形。注意工作电压老型号5V居多新模块很多支持3.3V。STM32的IO是3.3V如果接收头VCC接5V输出高电平可能超过IO耐压稳妥做法是让接收头也吃3.3V大多数接收头在3.3V下能正常工作只是灵敏度略降。引脚定义很容易踩坑。接收头上一般标着OUT、GND、VCC但插入面包板时方向不同实际买到的模块丝印可能和datasheet相反最好拿万用表量一下VCC和GND再上电。发射管方面常用5mm的红外LED波长940nm压降1.2到1.5V串联一个30到100欧限流电阻。如果直接用STM32的IO驱动电流不够发射距离可能只有十几厘米建议加一个S8050三极管IO高电平时驱动LED接收距离能做到8米以上。2.2 供电与信号线布局别让电机干扰了解码红外接收头最怕电源纹波和强光干扰。很多项目里解码乱码第一反应是代码问题最后发现是开发板USB供电不稳电机一启动接收就误触发。解决办法是在接收头VCC和GND之间加一个10uF电解电容和0.1uF瓷片电容并且尽量靠近接收头放置。信号线尽量短不要和电机线、220V线并行否则干扰会直接耦合进信号。引脚选择也要避开调试下载口。PB0、PA0这类带外部中断的引脚都可以用但PA13、PA14是SWDPA15、PB3、PB4默认被JTAG占用。如果实在要用这些脚需要先在代码里禁用JTAG否则调试器可能干扰电平导致红外信号异常。还有一点普通轻触开关不能直接替换成红外接收头。轻触开关输出稳定电平红外接收头输出的是脉宽调制信号必须经过单片机解码后去控制继电器或者MOS不能机械替换。2.3 用逻辑分析仪快速验证硬件接线完成后不要急着写算法先用逻辑分析仪量一下接收头OUT引脚。按住遥控器按键逻辑分析仪应该抓到一个明显的脉冲序列断开时是3.3V高电平有信号时会出现一段一段的负脉冲。如果一直高电平说明接收头没输出查供电和引脚如果一直是低电平大概率是接收头损坏或者输出脚接反。没有逻辑分析仪也可以把OUT引脚接一个带示波器功能的万用表但反应会比较慢还是建议备一个便宜的8通道逻辑分析仪这个工具在后续调发射时也非常有用。3. 软件实现STM32上NEC协议的解码与发射3.1 NEC协议码型细节引导码、数据码、重复码软件层面先要把NEC协议的码型吃透。一个完整帧的经典格式是9ms引导低电平、4.5ms引导高电平然后是8位地址码、8位地址反码、8位数据码、8位数据反码。每个位都以560us低电平开始之后的高电平持续时间来区分0和1高电平560us是0高电平1690us是1。这样算下来一帧数据的总时间约67.5ms所以遥控器数据帧发送速率并不高。另一个容易忽略的是重复码。遥控器按住不放时不会重复发送完整数据帧而是每隔约110ms发送一个9ms低电平、2.25ms高电平、560us结束脉冲的重复码。如果解码代码不处理重复码长按键会表现为按键只触发一次或者连续乱触发。处理策略是解析到重复码时把上一次按键值作为有效值返回同时置一个“按键持续”标志这样长按逻辑就很好做了。这个细节在做智能台灯调光时特别重要因为长按加减亮度需要持续响应。3.2 用外部中断微秒时间戳实现NEC解码解码方法有两种定时器输入捕获和外部中断加微秒时间戳。输入捕获硬件自动记录边沿时间精度高但配置稍绕外部中断方案写起来直观适合初学者。我常用的是外部中断双边沿触发把所有边沿间隔存储到数组里解码函数再统一分析。核心思路是每次进入中断把当前时间戳和上次的差值存下来空闲时红外输出是高电平因此第一个有效边沿是下降沿。微秒时间戳可以用一个定时器自由计数。以STM32F103的72MHz主频为例把TIM6设为72分频计数频率就是1MHz计数器值就是微秒。更省事的方式是用DWT的CYCCNT不占定时器。代码示意void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin IR_GPIO_Pin) { uint32_t now micros(); uint32_t delta now - ir_last_time; ir_last_time now; if (ir_capture_len IR_BUF_LEN) { ir_capture[ir_capture_len] delta; } } }得到间隔数组后按NEC时序把数组转成字节。实际调试时不要追求一次成功先把数组通过串口打印出来和逻辑分析仪对比。按下按键1打印出的间隔应该是类似13500、4500、560、560、560、1690这样一组。对比几次你就能直观理解NEC时序。串口打印建议用DMA或中断发送不要在外部中断回调里直接调用串口阻塞发送否则会丢边沿解码永远是乱的。3.3 用定时器PWM实现38kHz载波发射发射端要做两件事产生38kHz载波以及在正确的时间点让载波通过。用定时器的PWM输出最合适。STM32F103的TIM372MHz时钟设置PSC71、ARR25输出频率就是72000000 / 72 / 26 38461Hz约38.46kHz误差1.2%完全够用。占空比一般设为1/3左右发射管峰值电流舒服也更省电。初始化代码htim3.Init.Prescaler 71; htim3.Init.Period 25; HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 9);发送时把PWM打开再延时对应的脉宽然后关闭就能得到一组NEC脉冲。注意延时函数必须用微秒级HAL_Delay只能到毫秒不行。可以用自旋循环加DWT计数实现delay_us。发送数据位的函数void ir_send_pulse(uint16_t low_us, uint16_t high_us) { __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 9); // PWM开 delay_us(low_us); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 0); // PWM关 delay_us(high_us); } void ir_send_byte(uint8_t data) { for (int i 0; i 8; i) { if (data 0x01) { ir_send_pulse(560, 1690); } else { ir_send_pulse(560, 560); } data 1; } }注意NEC是低位先发。data 0x01判断最低位然后右移顺序反了遥控器收到的数据会整个错乱。发送完整帧时先关PWM 9ms模拟低电平引导再开PWM 4.5ms接着连续发送地址、地址反码、数据、数据反码最后关PWM。发送过程中最好暂时屏蔽其他中断或者把定时器PWM的中断优先级设到最低否则时序被串口或定时器中断打断空调或电视可能没有反应。我实测在STM32上用HAL库发送如果开着多个串口中断偶尔会出现设备没反应基本就是时序抖动超过了对方接收端的容差。4. 常见问题排查实录与实战技巧4.1 按下按键完全没反应先别查代码遇到没反应第一件事是测接收头OUT引脚。用逻辑分析仪或示波器看按键时如果OUT有波形说明硬件链路通了如果一直高电平查供电和焊接如果一直低电平大概率是接收头损坏或者输出脚接错。没有示波器可以用万用表直流档量空闲时约3.3V按键时能看到电压跳动。很多红外接收模块上的指示灯是电源指示不是信号指示别看到灯亮就以为信号正常这个误导很常见。4.2 解码乱码、按键重复触发这个问题的原因大概有四个。一是外部中断优先级配得比内核时钟中断低导致时间戳读取被延迟二是中断回调函数里做了串口打印或复杂运算把下一个边沿丢了三是电源纹波电机或继电器动作瞬间波形畸变四是长按键时的重复码没处理。排查顺序建议是先关掉所有业务逻辑只存边沿数组并串口打印按一下看间隔是否稳定。不稳定就查硬件稳定就查解码逻辑。另外STM32CubeMX生成代码后一定要把外部中断的抢占优先级设为0或1并且启用对应的SYSCFG时钟否则中断可能不触发。4.3 红外避障传感器和遥控接收头不是一回事很多新手把红外避障传感器和红外遥控混在一起。红外避障传感器是自带发射管和接收管的一体模块通过检测反射红外光的强度判断前方是否有障碍物输出的是开关量高或低根本没有载波解调和协议解析。它不能用来接收遥控器信号也不能用来做解码。反过来一体化遥控接收头也不适合做避障检测因为你对漫反射回来的38kHz能量响应很难预估距离和角度都很不可控。如果项目需求里提到红外避障一定要单独选型别想着一个模块通吃。4.4 从Arduino迁移到STM32的思路玩过Arduino的朋友会知道IRremote库封装好了NEC解码一个IRrecv对象就能输出按键值。换到STM32后没有现成库很多人不适应。我的迁移方法是先沿用Arduino的打印格式把解码结果打印成十六进制例如0xFF00FF然后在STM32上定义同样的枚举表这样两边方便对照。STM32的好处是定时器资源多可以同时做接收和发射也能一边解码一边用串口和K210这类AI模块通信把遥控指令传给视觉模块做联动。如果做智能台灯解码成功后控制PWM调光即可顺带把串口控制加进去以后手机或上位机也能控制。红外代码程序本身不算复杂但它是STM32定时器和中断的综合演练值得认真做一遍。我在实际项目中踩过最大的坑是发射时序和接收中断打架后来把所有定时器中断优先级理清楚又用DWT做了微秒延时才算稳定。如果你准备做这个项目我的建议是先花半天把NEC协议吃透再花半小时接线剩下的时间全部用来调试边沿数组。红外这东西不会骗人波形对不上就是协议理解偏了逻辑分析仪是最好用的老师。最后分享一个调试小技巧在发射端和接收端之间放一张白纸遮挡一边按键一边观察串口打印的数据如果距离变化时数据能保持一致说明你的解码代码已经足够健壮可以放心往业务逻辑上堆功能了。本文还有配套的精品资源点击获取