简介:这是一份基于STM32设计的万能红外遥控器完整项目文档,面向嵌入式开发者、STM32学习者和智能家居DIY玩家,针对传统家电遥控器兼容性差、无法统一控制等问题,给出了一套硬件加软件的系统级解决方案。文档详细介绍了项目开发背景、设计思路、系统框架图、原理图与实物图,并对ESP8266 Wi-Fi模块、红外线学习模块、矩阵键盘模块、语音识别模块等关键硬件做了深入讲解。同时覆盖Qt上位机开发、STM32主循环程序设计、语音识别固件定制与烧录流程,内容从硬件选型到代码实现环环相扣,读者可据此独立复现一套支持红外控制、红外学习、语音命令、无线远程控制及LCD状态显示的多功能遥控器。资源为单个PDF文件,大小约46.43MB,目录结构完整;目前已有79人学习下载,适合需要系统了解红外遥控与智能家居项目的读者参考。
1. 项目概述与需求拆解
1.1 为什么需要"万能"红外遥控器
先说说做这个项目的动机。我家里有个抽屉,专门用来堆放各种遥控器:空调的、电视的、机顶盒的、风扇的,还有不知道哪台设备留下的。每次想换个设备操作,都得在一堆遥控器里翻找,实在是耽误事。其实市面上早就有万能遥控器卖,但问题在于:一是便宜的型号学习能力很弱,很多新协议的设备根本匹配不上;二是无法自定义按键逻辑,用起来别扭。既然一直在玩STM32,干脆自己动手做一个,把红外发射、接收、学习、存储全部集成在一起,彻底告别遥控器堆积的问题。
这个项目解决的核心痛点很明确:用一套硬件兼容市面上绝大多数红外遥控协议。STM32在这件事上的优势,远不是普通51单片机或者Arduino能比的——定时器精度高、中断响应快、Flash容量足以存储多组码库,关键是开发资源和调试工具非常成熟。我选的是STM32F103C8T6,也就是大家常说的"蓝板"最小系统板,某宝十几块钱一片,对DIY项目来说性能完全够用。
1.2 系统整体架构与模块划分
整个项目的功能可以概括为两个核心模式:学习模式和发射模式。学习模式下,设备通过红外接收头采集目标遥控器的信号,解析出脉冲时序后存入Flash;发射模式下,用户按键选择对应的码型,STM32驱动红外发射管复现该信号,完成对家电的控制。
从硬件结构上看,系统可以拆成五个部分:
| 模块 | 核心器件 | 作用 |
|---|---|---|
| 主控 | STM32F103C8T6 | 时序捕获、PWM生成、存储管理 |
| 红外接收 | VS1838B一体化接收头 | 学习模式下采集遥控器信号 |
| 红外发射 | 红外发射管 + NPN三极管驱动 | 发射模式下输出载波信号 |
| 人机交互 | 4x4矩阵键盘 + OLED显示屏 | 按键选择功能、显示当前状态 |
| 存储 | STM32内部Flash(或外接24C02) | 保存学习到的红外码型 |
可能有人会问,为什么不用现成的红外学习模块?比如那种集成了学习+发射的一体化模块,外围电路几乎不用自己画。我的回答是:自己做才能理解红外通信的底层机制,而且后期想加功能(比如手机蓝牙配码、语音控制)时,自定义方案的扩展空间是模块化方案给不了的。另外,自己掌控接收和发射电路之后,遇到信号弱、误码率高的问题,也有动手优化的余地。
2. 硬件设计思路与电路细节
2.1 红外通信原理:38kHz载波与脉冲编码
在做硬件之前,必须先把红外通信的基本原理交代清楚。红外遥控的本质是用红外光脉冲承载控制信息,但并不是简单地"亮一下表示1、暗一下表示0"。为了提高抗干扰能力,发射端会把要传输的数据信号调制在一个固定频率的载波上,最常用的载波频率是38kHz。
打个比方来解释:载波相当于快递的货车,数据信号是车上的货,货车本身没有意义,但货物必须装在车上才能安全运输。38kHz的载波让接收端可以过滤掉环境光中大量无规律的杂散红外信号,只响应这个频率附近的有效信号。这就是为什么你在阳光很强的窗边遥控电视偶尔会失灵——阳光里含有的红外成分干扰了接收端对有效信号的判别。
接收端用的是VS1838B这类一体化红外接收头,它的内部集成了光敏二极管、放大电路、解调电路和输出驱动电路。在无信号时输出高电平,收到38kHz载波信号时输出低电平。也就是说,接收头输出的是已经去掉载波、还原出来的数字电平信号,这一下就把问题简化成了传统的电平时序测量——只需要测高、低电平各自持续的时间,就能反推出遥控器发出的数据。
2.2 红外发射电路设计与驱动参数计算
发射电路比接收电路简单,但有一个容易踩坑的地方:红外发射管的驱动电流不能直接接在GPIO上拉。STM32的GPIO典型输出电流在20mA左右,而红外发射管要达到有效遥控距离,瞬时脉冲电流往往需要50mA到100mA。拿GPIO直接驱动,要么距离很近(一两米就失灵),要么长期使用影响单片机端口寿命。
这里我用的方案是:GPIO控制一个NPN三极管的基极,通过集电极串联红外发射管到3.3V电源。三极管选的是最常见的S8050,基极串联1kΩ限流电阻,集电极串联10Ω电阻限制发射管电流。实测在3.3V供电下,发射管峰值电流能到100mA左右,遥控距离大约8-10米,家用环境下绰绰有余。
注意:红外发射管是有极性的,长引脚为正极,接集电极;短引脚为负极,接地。装反了不会烧管子,但永远不会发光,容易白白排查半天。
接收头部分的电路相对简单:VS1838B有三只脚,从左到右分别是OUT、GND、VCC,OUT接STM32的一个GPIO(我用的PA6,配合TIM3的输入捕获功能),VCC接3.3V,GND接地。需要注意的是,VS1838B的供电要加一个10μF左右的电解电容做滤波,不然在开关电源供电时容易产生误触发。这个细节我是实际掉进坑里才发现的——一开始没加电容,LED灯一开关,接收端就莫名其妙收到一堆乱码。
3. 红外编码协议详解:如何"听懂"不同遥控器
3.1 NEC协议:最普及的红外编码格式
在拆解协议之前,先了解一下市面上最主流的编码格式。NEC协议是日系厂商(NEC、松下等)电视遥控器最早采用的格式,如今几乎成为红外遥控的事实标准。它的时序特征特别明显,非常适合作为第一个学习和解析的对象。
NEC协议的一帧数据结构是这样的:先是9ms的引导码(AGC脉冲),然后是4.5ms的间隔低电平,紧接着是8位地址码 + 8位地址反码 + 8位命令码 + 8位命令反码。最后一个反码字段是用来做校验的,接收端解码后把地址码和地址反码相加,如果不是0xFF就判定为传输错误。
"0"和"1"的区分,不在于脉冲宽度本身,而在于脉冲之后低电平的持续时间。逻辑"0"由560μs的载波脉冲+560μs的低电平组成,周期1.12ms;逻辑"1"由560μs的载波脉冲+1.69ms的低电平组成,周期2.25ms。相比"占空比调制"的方式,这种"脉宽调制"在抗噪声方面更稳定,协议实现也简单。
3.2 常见协议对比与通用解码策略
NEC再普及,也架不住其他厂商另起炉灶。索尼用SIRC协议(12位或15位短帧格式),飞利浦用RC5协议(曼彻斯特编码),还有大量国产空调品牌使用各家私有的格式,甚至同一品牌不同型号的空调协议都可能不同。如果给每类协议都写一套解析代码,工作量会非常庞大,而且永远追不完。
所以我在实际工程里换了一个思路:不区分协议,全部按"高低电平的时长序列"来存储和复现。原理其实很简单:无论哪种协议,本质都是一串高电平和低电平交替出现的脉冲,高电平是38kHz载波,低电平是静默,各种协议的区别只是高/低电平持续时间不同。既然是万能遥控器,最通用的做法就是"如实记录,原样复现"——我需要关心的只是每一段高/低电平持续了多长时间,而不用理解这些时间代表什么业务含义。
这就引出软件实现的两个核心任务:一是测量接收到的波形中每段电平的持续时间;二是按测得的时长序列反向驱动发射管,生成相同的波形。后文我会详细展开这两部分的实现。
4. 软件实现:从录码到发射的完整流程
4.1 发射模块:PWM与定时器的配合
红外发射的核心有两点:一是38kHz载波的产生,二是让载波在特定时间段出现或停止,也就是脉冲调制的过程。STM32的定时器PWM输出可以一次性解决这两个问题。
我用的是TIM2的CH1(PA0),配置成PWM模式,输出频率38kHz,占空比1/3。你没看错,不是常规的50%——红外发射管在脉冲瞬间电流很大,占空比太高不仅费电,还容易让管子过热。1/3占空比在保证有效辐射强度的前提下,能降低管子的平均功耗。实测下来距离和3/4占空比没什么差别,但管子长时间工作不发烫。
载波有了之后,怎么控制它"断续"呢?我的做法是:GPIO控制三极管通断的思路调整一下,直接利用PWM输出通道的使能开关。需要用载波时打开TIM2的PWM输出,不需要时关闭。关闭状态下引脚输出低电平,三极管截止,发射管不发光。
具体的发射时序用定时器中断来控制。我把要发送的电平序列放在一个数组里,数组元素是微秒级的延时值,奇数位代表高电平(载波)持续时间,偶数位代表低电平(静默)持续时间。发送时启动定时器,在中断服务函数里切换GPIO状态并更新下一个延时值,直到数组遍历完毕。
// 红外发射电平序列结构体定义 typedef struct { uint16_t LevelTime[128]; // 每段电平的持续时间,单位us uint8_t Len; // 有效段数 } IR_Packet; // 发送一帧红外信号 void IR_SendPacket(IR_Packet *pkt) { IR_Index = 0; IR_State = 1; // 先输出高电平 HAL_GPIO_WritePin(IR_TX_GPIO_Port, IR_TX_Pin, GPIO_PIN_SET); HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 开启38kHz载波 TIM3->ARR = pkt->LevelTime[0]; // 设置第一段持续时长 __HAL_TIM_SET_COUNTER(&htim3, 0); HAL_TIM_Base_Start_IT(&htim3); // 启动定时中断 }4.2 学习模块:输入捕获与脉宽测量
学习模式是整个项目最有技术含量的部分——因为解码的准确性直接取决于对脉宽的测量是否精准。这里我用的是TIM3的输入捕获功能,把VS1838B的输出接到PA6上。
输入捕获的思路是这样的:定时器在自由计数,每当引脚发生电平跳变(上升沿或下降沿),硬件自动把当前计数值锁存到捕获寄存器里,同时触发中断。我在中断里读取两次捕获值的差值,再换算成时间,就得到了一段电平的持续时间。这种方式不需要CPU轮询等待引脚变化,精度能达到微秒级,是STM32处理此类任务的推荐做法。
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM3) { uint32_t now = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); uint32_t diff = (now >= IR_LastCount) ? (now - IR_LastCount) : (0xFFFF - IR_LastCount + now); IR_LastCount = now; uint32_t duration_us = diff * 1000000 / 72000000; // 72MHz时钟换算 if (IR_LearnIndex < MAX_LEVELS) { IR_LearnBuffer[IR_LearnIndex++] = duration_us; } // 切换到下一个捕获沿 TIM_RESET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1); TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); // 省略:根据当前状态切换上升/下降沿捕获 } }实际采集一段NEC信号后,缓冲区里大概会记录60~70组电平数据。这里有三个关键点需要说明一下:第一,采完一段后要设置一个超时判定,比如200ms内没有新的电平跳变就认为一帧信号接收完毕,因为引导码之后后面的数据位间隔最长的也不会超过几十毫秒;第二,采集到的数据要做毛刺过滤,如果某段电平时间小于100μs,大概率是干扰噪声,直接丢弃;第三,学习过程中会误触发两次——第一次是接收头从空闲状态刚进入输出时可能产生一个极短的毛刺脉冲,实现时可以加个状态机,只有正确接收完引导码后才认为有效帧开始。
4.3 数据存储:Flash扇区规划与码型管理
学习功能完成后,数据结构是一组时间序列数组,必须想办法持久化保存,否则一断电录制的码就全丢了。STM32F103C8T6有64KB的Flash,存放几百组码型绰绰有余。
Flash的写入有几个硬性约束:必须按扇区擦除(F103每扇区1KB),擦除后所有位变成0xFF,然后才能写入数据;写入时只能把1改成0,不能反向操作。所以存储策略必须做好扇区规划。我的方案是在Flash末尾划出4个扇区(共4KB)作为码库区,每个扇区存32组码型,用循环覆盖的方式管理。每组的存储格式如下:
| 偏移地址 | 含义 |
|---|---|
| 0 | 状态标志(0xA5表示有效) |
| 1 | 码型编号 |
| 2 | 电平段数(最大128) |
| 4~511 | 每2字节存一个电平时长 |
为什么要加状态标志?因为Flash只能从1改成0,删除一条记录时只需要把标志位改成0即可,下次写入直接覆盖,不用频繁擦除整个扇区。这个小技巧能显著延长Flash寿命,也简化了删除逻辑。
关于"电平段数最大128"这个设计,我是在实际测试后定下来的。NEC协议完整帧大约68段电平,SIRC协议15位格式大概40段,而一些空调私有协议可能长一些,但也没超过100段。128段是留了余量的,足以覆盖市面上绝大多数红外遥控协议。
5. 实操调试与踩坑记录
5.1 常见故障速查表
做这个项目的过程中,我踩过不少坑,有些还花了不少时间去排查。把高频问题整理成一张表,给后来人做个参考:
| 现象 | 常见原因 | 排查与解决方案 |
|---|---|---|
| 学习模式采集不到信号 | VS1838B引脚接错或供电不足 | 检查OUT/GND/VCC接线;加10μF滤波电容;测量接收头输出端静态电压应为3.3V |
| 采集到一堆乱码、长度不对 | 供电纹波干扰或环境光干扰 | 接收头供电加RC滤波;测试时远离节能灯和阳光直射 |
| 发射后家电无反应 | 载波频率不对或驱动电流不足 | 用示波器/逻辑分析仪测量38kHz输出频率;检查三极管基极偏置和集电极限流电阻 |
| 部分设备能控、部分不能 | 码型采集时漏掉了引导码后的某段电平 | 加强超时判定逻辑;确认接收数据的首段是否为正常的引导码脉冲 |
| 关机重开后码库丢失 | Flash写入时序或地址错误 | 检查是否在擦除后写入;写入期间是否发生了复位;用ST-Link Utility读取Flash内容核对 |
5.2 解码失败时的排查思路
解码失败是这类项目里最闹心的问题,因为红外信号看不见摸不着。我的建议是:先让逻辑分析仪或者示波器接管。在接收头输出端挂上逻辑分析仪,按下被学习的遥控器按键,观察输出的波形。这个波形就是移植到单片机前的"原始真相"——如果你能看到清晰的引导码和重复码,说明硬件链路正常,问题出在软件过滤或时序换算上;如果波形本身就是一片模糊,那就要回查电路和供电。
实际调试中我发现一个很容易忽略的点:STM32F103的输入捕获定时器分频不要设得太大。如果把定时器时钟分频成1MHz(1μs计数一次),理论上够用,但捕获中断的处理时间(几十个CPU周期)和代码执行时间会带来微小的累计误差。实测72MHz直接计数(分辨率约13.9ns)再在软件里换算成微秒,误差可以控制在±20μs以内,对红外解码完全够用。如果用1MHz分频,累计误差可能到±100μs,NEC协议里560μs的最小脉冲就可能被误判。
5.3 发射距离与稳定性优化
成品做好之后还要解决"遥控距离短"和"按了没反应"的问题。第一次做完测试时,在3米外遥控空调时而有效时而无效,简直崩溃。后来排查出来是发射管工作电流不够稳定导致的。3.3V供电下,S8050的饱和压降再加上10Ω限流电阻的压降,实际流过发射管的电流没有理论计算值那么高,而且电池电压下降后电流也跟着下降,距离就明显缩短。
优化方案有两个方向:如果硬件已经定型,可以通过降低限流电阻阻值到4.7Ω、把发射管单独用5V电源供电来提升峰值电流;如果硬件可以调整,直接把供电改成5V,用NPN三极管做开关,效果会更好。我最后改成5V供电后,室内10米直线遥控毫无压力。
还有一个细节是发射管的朝向和外壳遮挡。如果设备装进不透光的盒子,遥控距离会断崖式下降。发射管尽量靠近外壳开孔处,或者在外壳上留一个透明窗,否则信号被壳子吃掉大半。
6. 项目扩展与个性化改造建议
6.1 增加OLED显示与交互优化
原始版本的交互是矩阵键盘+数码管,信息量有限。把按键换成OLED显示 + 旋转编码器的组合后,体验会提升不少。OLED可以实时显示当前选中的设备类型、码库总数、电池电量,还可以在录制模式里显示实时的脉宽数据——这功能在调试协议时特别实用,能直接在屏上看到NEC引导码是不是9ms。
编码器还有一个好处:按键数量大幅减少,UI可以设计成菜单层级结构,操作逻辑和市售万能遥控器类似。实际上STM32的生态里,OLED驱动库(比如U8g2)移植成本极低,几乎零门槛。
6.2 用HC-05蓝牙模块实现手机配码
如果想把"万能"贯彻到底,可以在串口上挂一个HC-05蓝牙模块,配合手机App使用。手机端做一份"码库云端共享"的概念验证:使用者扫描空调铭牌上的型号,App从服务器拉取对应码型的时序参数,通过蓝牙发给STM32存储。这样一来,新空调不用对着学习键一顿乱按,直接"下载"就完事。
6.3 从红外到射频:多协议融合的想象空间
红外遥控终究是"视线范围"内的工作方式,如果想要更极致的体验,可以考虑外挂一个315MHz/433MHz射频模块。不少智能家居设备(如射频遥控插座、电动窗帘)都工作在433MHz频段。硬件上只需在STM32的串口上挂一个超外差收发模块,软件上参考红外"电平序列"的思路同样处理射频编码,就能实现红外+射频全兼容的万能遥控中心。
我在实际使用中发现,这类项目最有意思的点在于适合反复拆改。最初做成一个最简单的录码-发射工具,后来加OLED、加编码器、加蓝牙,每一轮改动都会逼着你更深入地理解协议解析和状态设计。比直接买一个成品万能遥控器有意思得多。如果你也想做一个练手,建议第一版不要贪多,先把NEC协议学透、发射电路调通,再慢慢往上面叠功能。这样即使后面出了问题,排查范围也小,不容易劝退。
本文还有配套的精品资源,点击获取