简介:这套基于STC15F104W单片机的学习型433MHz无线遥控解码方案,面向硬件开发者和电子爱好者,解决多遥控器免配对共用同一接收模块的痛点。方案支持315/433MHz频段,兼容PT2262、EV1527等常见编码芯片,上电自动学习振荡电阻并写入EEPROM,不依赖外部晶振,解码结果以高低电平输出,可直接驱动继电器或主控MCU。资源包共44个文件、329KB,以KEIL C51工程源码、HEX固件、AD09原理图及中文使用文档为核心,另含烧录说明、应用电路图及参考PCB布局,便于从原理到实作完整跟练。目前已有123人学习下载。综合来看,这份资料既适合快速制作智能开关、车库门控制器等低成本设备,也适合想深入理解无线解码机制的开发者对照源码与原理图资料上手实践。 前阵子朋友翻出一个十几年前的车库遥控器,外壳碎了但板子没坏,想复制一个,结果打开一看焊盘氧化得没法下手,抄板根本不现实。淘宝成品遥控器不贵,但那种学习型解码模块要么固定协议固定频率,要么是黑盒不给源码,后面想加一路联动逻辑根本无从下手。所以干脆用一颗STC15F104W自己写一套学习型433MHz无线遥控解码方案,硬件画板加调试用了两个晚上。这个方案最终实现了:兼容EV1527等常见编码格式,能学习并保存多把遥控器,解码成功后输出开关信号,整套逻辑跑在8脚芯片上,开发环境就是KEIL C51,工程源码和原理图都整理好了。
1. 项目动机与芯片选型:为什么是STC15F104W
1.1 这颗8脚芯片到底能装下多少东西
STC15F104W是STC 15系列里比较入门的一颗料,1T时钟的8051内核,4K字节Flash,256字节SRAM,内置512字节EEPROM和一组高精度内部RC振荡器,SOP8封装只有6个可用IO。刚开始我也担心资源不够用,毕竟又要测脉宽、又要存码表、又要跑状态机,但实际算下来绰绰有余。解码核心占用大概1.5K代码,EEPROM存一轮遥控码只需要几十字节,剩下的Flash还能放很多扩展逻辑。
这颗芯片最值钱的地方在于内部RC时钟。433遥控解码对时间精度有要求,普通51单片机接12MHz晶振也能做,但STC15F104W可以完全省掉外部晶振,用内部时钟跑在11.0592MHz或者更高频率。内部IRC时钟常温下能做到1%左右的精度,而市面上大多数EV1527遥控器的脉宽公差本身就有10%以上,这个误差完全不构成问题。
1.2 和STM32、ESP8266方案对比后的取舍
手头其实也有STM32F0和ESP8266的板子,但最后没有用它们。
先说STM32,F0系列做解码没有任何问题,定时器输入捕获比51的中断加计数器方案好用得多,但一颗STM32F030F4价格大概是STC15F104W的四到五倍,封装也更大。对一个成本敏感、功能单一的遥控转接板来说,属于杀鸡用牛刀。
ESP8266就更不合适了。它本身能做解码,但上电启动要几百毫秒,还带WiFi射频,做独立遥控设备功耗和体积都压不住。除非要做WiFi联动,否则没必要把问题复杂化。
STC15F104W的优势很直接:价格一两块钱,SOP8封装手指头大,STC-ISP串口下载一根USB转TTL就能烧录,不需要外部晶振和复位芯片。做433解码这种纯IO任务,它是最省事的选择。
2. 433遥控编码到底怎么回事:解码前必须吃透的信号格式
2.1 接收模块输出的是什么信号
433MHz遥控器使用的调制方式大多是OOK/ASK,简单说就是按键时载波"发一阵停一阵",接收模块把这个包络还原成TTL电平,从DATA引脚输出。单片机需要处理的不是433MHz射频信号本身,而是接收模块DATA引脚上一串高低电平变化的方波。
网上很多新手直接在这个引脚上接一个IO然后死循环读电平,结果发现数据乱七八糟。问题在于:遥控器按键后并不是只发一帧信号,而是连续重复发射几十帧,帧与帧之间还有随机噪声。解码的本质是把这一长串电平变化按照编码规则切分成帧,再从帧里提取地址和按键信息。
2.2 EV1527的脉宽编码规则
EV1527是现在最主流的433遥控编码芯片,20位ID加4位按键码,总共24位。它的编码方式说穿了很简单:高电平脉宽基本固定,低电平间隔的长短决定这一位是0还是1。
以最常见的时序为例,逻辑1是高电平约330到440us,后面跟一个约990到1320us的低电平;逻辑0是高电平同样宽,但后面的低电平只有330到440us。这样在接收端看到的现象是:每个bit开头都有一个差不多宽的高脉冲,紧接着的低电平"短的是0,长的是1"。同步码则是一个明显长于数据位的低电平,一般在2.5ms到6ms不等,具体长度因厂家而异。
所以解码的关键不是傻傻地测绝对脉宽,而是要同时测高电平宽度和低电平宽度,然后以高电平宽度为基准去判断低电平的长短比例。不同厂家的EV1527脉宽可能从300us到500us都有,写死任何一组数据都会导致兼容性很差。
2.3 PT2262与EV1527的区别,以及自动适配的本质
PT2262是更老一代的编码芯片,在一些旧设备和电动门里还能看到。它每一个数据位有三种状态:0、1和高阻F,总共12位,同步头是一个特别长的低电平,而且它的位宽节奏和EV1527明显不同。老式PT2262遥控器在淘宝上依然有大量库存。
自动适配要解决的本质问题,不是"认识某一种格式",而是"拿到一帧未知信号后自己判断该按哪种规则解"。我的方案里用了一个很直接的判断方法:捕获同步头的长度。EV1527的同步电平通常在2.5到6ms,PT2262的同步电平更长,通常在10ms以上。首次进入同步状态时先记录同步宽度,如果超过阈值就切到PT2262解析分支,否则走EV1527分支。这个判断在处理大多数遥控器时都有效。
3. 硬件原理图设计:8脚封装怎么把事办完
3.1 引脚功能分配和最小系统
SOP8封装的IO本来就少,每根脚都要精打细算。我的功能分配是这样的:
| 信号 | 引脚 | 说明 |
|---|---|---|
| RF_DATA | P3.2 | 433接收模块DATA输出,接外部中断INT0 |
| LEARN_KEY | P3.3 | 学习按键输入,内部上拉 |
| OUT_CTRL | P3.4 | 解码成功输出,外接三极管驱动继电器 |
| LED_STATUS | P3.5 | 状态指示灯 |
| VCC/GND | 电源脚 | 5V供电,10uF加0.1uF去耦 |
得益于内部IRC时钟,外部晶振的两个引脚省出来了。否则8脚封装根本不够用,这也是我选这颗芯片的另一个原因。STC15F104W的IO上电默认是准双向口,在初始化时建议把RF输入脚设置成高阻输入,把学习按键脚开内部上拉,输出脚设为推挽模式,这样驱动能力和抗干扰都有保证。
3.2 电源和射频接收模块的布板细节
433接收模块对电源纹波非常敏感,尤其是超再生类模块,电源上一丁点毛刺都会直接变成DATA脚上的误脉冲。所以我在原理图上做了两条处理:一是接收模块的VCC从主5V经过一个22欧电阻单独取电,并在模块旁边放一颗100uF电解电容和一颗0.1uF陶瓷电容;二是继电器等感性负载绝对不要和接收模块共用一个过孔走线,否则继电器吸合瞬间的电流冲击会让接收模块瞬间"失聪"。
如果控制的是继电器,线圈两端必须反并联一颗1N4148或者1N4007续流。这个二极管不加,关断瞬间的反向电动势轻则干扰解码,重则把单片机电死。另外继电器驱动用NPN三极管就行,基极串1K电阻,发射极接地,集电极接继电器线圈,线圈另一端接VCC。
3.3 天线区域的注意事项
接收模块通常自带一根螺旋天线或者棒状天线,PCB上要保证天线周围至少5mm净空,正下方不要铺铜。如果自己做板载天线,433MHz的1/4波长天线大约17.3厘米,PCB蛇形天线需要考虑介质常数和地平面,新手直接买带天线的接收模块最省事。
还有一个很容易忽略的点:接收模块的天线方向和车身、墙面的角度会影响接收距离。实测中模块天线竖直放置时效果最好,贴着金属外壳或者被塑料壳包裹得太紧都会明显缩短距离。
4. KEIL工程核心实现:定时器脉冲捕获与解码状态机
4.1 工程结构和时钟配置
KEIL C51工程建好之后,我按模块拆了几个文件:main.c负责初始化和主循环,rf_decode.c负责信号捕获和解码状态机,rf_learn.c负责学习与匹配逻辑,eeprom.c封装IAP操作。这样后面加协议或者改逻辑不用翻一个大文件。
时钟用的是内部IRC 11.0592MHz,烧录时在STC-ISP里直接选。为什么选11.0592而不是12MHz?因为如果以后要开串口打印调试信息,11.0592MHz能分频出准确的9600波特率。如果用12MHz,串口波特率会有误差,调试时容易看到乱码。
4.2 定时器加外部中断的脉宽测量方案
433解码用的比较多的是"外部中断加定时器计数"方案。原理不复杂:外部中断每次触发时,读取定时器当前计数值,和上一次的计数值相减,得到的就是相邻两个跳变之间的时间间隔。为了让中断能同时捕获上升沿和下降沿,我使用了STC15系列外部中断的任意电平变化触发方式,在中断服务程序里通过读IO电平判断这次是上升沿还是下降沿。
关键代码框架如下:
void INT0_Isr(void) interrupt 0 { unsigned int ts, dt; ts = (TH0 << 8) | TL0; dt = ts - time_base; time_base = ts; rx_push_edge(dt, P32); // 当前IO电平,1=上升沿,0=下降沿 }这里有一个非常容易踩的坑:8051读取16位定时器时不能直接一次读完,要先读TL0再读TH0,否则刚好在进位时读取会得到错误数据。用差值计算而不是绝对值,还能自动处理定时器回绕的问题。定时器配置成16位不自动重装模式,不要开定时器中断,只把它当计数器用,中断全部交给INT0。
定时器初始化:
void Timer0_Init(void) { AUXR = 0x80; TMOD = 0x01; TH0 = TL0 = 0; TR0 = 1; }11.0592MHz下1T模式,计数周期约为0.09us,16位溢出约5.9ms。EV1527的同步头极限接近6ms,理论上勉强够,但我在代码里做了一个溢出计数器,一旦定时器溢出就置一个标志,这样即使同步头再长也不会丢失时间信息。
4.3 解码状态机:从电平序列到完整帧
解码状态机是整个工程的核心,我用三段式状态机来实现。空闲状态下等待一个明显的同步低电平;一旦识别同步头,进入数据采集阶段;连续收满24位,校验数据的合理性,然后交给上层处理。
rx_push_edge每次收到一个电平宽度都会调用一次。状态机里最重要的判断逻辑是:当前电平是高电平还是低电平。如果是高电平,只把宽度记录下来;如果是低电平,就根据刚记录的高电平宽度和当前低电平宽度的比例决定这一位是1还是0。
void rx_parse(unsigned int dt, unsigned char level_is_high) { static unsigned char phase = 0; static unsigned int high_width; static unsigned short rx_code; static unsigned char bit_cnt; // 同步低电平 if (!level_is_high && dt > SYNC_MIN && dt < SYNC_MAX) { phase = 1; bit_cnt = 0; rx_code = 0; return; } if (phase == 1) { if (level_is_high) { high_width = dt; } else { // 低电平宽度明显大于高电平宽度 -> 逻辑1 rx_code = (rx_code << 1); if (dt > high_width * 2) { rx_code |= 1; } bit_cnt++; if (bit_cnt >= 24) { phase = 0; rx_frame.valid = 1; rx_frame.code = rx_code; } } } }这个写法有一个隐含假设:每个bit都是从高电平开始的。EV1527的帧格式确实如此,同步头之后每个bit都是一段高电平加一段低电平。如果遇到PT2262分支,则把位长规则换成三态判断,总体流程一致。
5. 学习模式与EEPROM存储:让固件记住遥控器
5.1 学习流程和按键交互
学习型的价值在于不需要改硬件就能匹配新遥控器。我的交互逻辑很简单:上电处于运行模式,短按学习键进入学习模式,此时指示灯快闪;在10秒内按一下待学习的遥控器,解到一帧有效数据后连续校验三帧一致,就存入EEPROM空槽,指示灯常亮2秒表示成功。
为什么要求连续三帧一致?因为433遥控器按键时本身会重复发射几十帧,单帧偶然受干扰解错的可能性不低。三帧一致基本能确保不是噪声。实测下来,一些劣质遥控器偶尔会有单帧错误,三帧一致机制能挡掉大部分误学。
5.2 EEPROM数据布局与读写
STC15F104W内置EEPROM按扇区管理,一个扇区512字节。我的存储布局很简单:
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0x0000 | 1 | 表头有效标志,固定0xA5 |
| 0x0001 | 1 | 协议类型,0x01=EV1527,0x02=PT2262 |
| 0x0002 | 3 | 遥控器码值,高、中、低字节 |
| 0x0005 | 2 | 参考高电平宽度 |
| 0x0007 | 2 | 参考低电平宽度 |
| 0x0009 | 1 | 槽位状态,0x55=已用 |
一个遥控器占用10字节,512字节的EEPROM可以存下几十个槽位,实际项目里完全够用。STC15的EEPROM需要IAP方式操作,写入前必须先擦除整个扇区。所以保存数据时我是先读回原数据,修改对应槽位,再整扇区擦除重新写。频繁擦写会影响EEPROM寿命,但正常使用场景一天学不了几次,完全没问题。
Eeprom读写函数封装如下:
void EEPROM_WriteByte(unsigned int addr, unsigned char dat) { IAP_CONTR = 0x80; IAP_CMD = 0x02; IAP_ADDRH = addr >> 8; IAP_ADDRL = addr & 0xFF; IAP_DATA = dat; IAP_TRIG = 0x5A; IAP_TRIG = 0xA5; IAP_CONTR = 0x00; }每次进入IAP操作前要关闭中断,防止中断里同时操作IAP导致时序错乱。写完再恢复中断状态。
5.3 匹配判断与重复触发抑制
运行模式收到一帧有效数据后,我会在码表里线性查找匹配的槽位,匹配成功就让P3.4输出一个指定宽度的脉冲,比如500ms高电平。这个时间在整个匹配过程中需要去抖:解码成功后立即设置一个"冷却时间",冷却时间内即使再收到相同码也不触发,避免一次按键在长按或遥控器连续发射时触发多次。
实际测试中,遥控器按下后一秒钟内会收到几十个相同帧,如果没有冷却机制,一个按键会触发十几次继电器吸合。我加的冷却时间是1秒,刚好和遥控器自动重复发射周期匹配。
6. 自动适配的软件设计:一把通吃不同遥控器
6.1 同步头宽度区分协议
前面提到PT2262和EV1527的同步头长度不在一个量级,这是协议自动识别最简单可靠的依据。我在进入同步判断时有两个阈值范围:SYNC_MIN_EV1527到SYNC_MAX_EV1527对应EV1527,SYNC_MIN_PT2262到SYNC_MAX_PT2262对应PT2262。如果同步头落在PT2262范围内,就把协议类型标识设为PT2262,位解析切换到三态模式。
这里要注意的是,某些廉价遥控器芯片的同步头不稳定,不同按键或不同电池电量下宽度会漂移。所以我实际用的是"范围判断加首次学习记录"的组合:平时运行只按已学习槽位的协议类型做解析,学习新遥控器时才做协议自动探测。这样避免了一帧不稳定的信号导致协议误判。
6.2 脉宽自适应的核心:学习中位数
很多433解码教程把脉宽参数写死,比如"高电平400us,低电平1200us是1",这样只能匹配同一种遥控器。市面上的EV1527遥控器脉宽各不相同,有的340us有的480us,温差和电池电压还会让它再漂一点。
我的做法是学习时统计多次采样的宽度。短按学习键后,等待遥控器连续发射的若干帧,对每个bit的高电平宽度和低电平宽度分别取样,去掉最大最小值后取中位数作为参考,存入EEPROM。匹配时以参考值做比例判断,并允许正负30%的容差。这样每把遥控器都有自己独立的时序基线,不同厂家的遥控器都能稳定适配。
6.3 实测对比:不同遥控器的适配效果
实际测试用了三把遥控器:一把公版EV1527(脉宽约350us)、一把带加密滚动码的小品牌遥控器(脉宽约420us)、一把老式PT2262电动门遥控器。在开放环境下10米距离内,三把都能学习并触发;隔了一面砖墙后EV1527那两把有少量丢帧,但按两次能稳定触发,PT2262遥控器因为是老芯片发射功率大反而最稳。
滚动码遥控器其实不是真正的EV1527,它的ID每次按键都会变化,这种遥控器学习型方案只能学到一个固定时刻的码,下次按键又变了,所以本质上无法适用。如果项目需求是复制滚动码遥控器,那需要专门的滚动码分析方案,STC15F104W的资源不够,这个要在项目前期向需求方讲清楚。
7. 调试过程中踩过的坑和实测验证
7.1 接收模块"无信号也有脉冲"的干扰问题
调试第一天最头疼的就是接收模块在没有任何遥控按键时,DATA脚也时不时输出一堆随机脉冲,超再生模块尤其严重。刚开始以为是程序死循环,后来用逻辑分析仪挂在DATA脚上一看,确实是有脉冲,不是软件问题。
处理办法是软件和硬件两头一起加。硬件上给接收模块电源加强滤波,减少电源纹波串扰;软件上规定只有完整收齐一帧且匹配同步头宽度才算有效,单靠几个脉冲根本进不了数据采集状态。另外还有一个细节,遥控器按下瞬间的第一个下降沿前面可能有毛刺,我在同步判断前加了一个简单的IO电平采样滤波:连续读到3次相同电平才认为边沿有效。
7.2 KEIL环境下不方便调试的替代方案
STC15F104W没有SWD/JTAG,不能用KEIL直接在线仿真,只能串口下载。我开发阶段用了一个很土但有效的调试方法:把P3.0和P3.1串口打开,把解析到的脉宽值、同步头宽度、数据位状态等关键信息通过串口打到电脑上,用串口助手观察。
void DebugPrint(const char *s) { while (*s) { SBUF = *s++; while (!TI); TI = 0; } }串口打印用9600波特率,足够。等到逻辑稳定之后,把DEBUG宏关掉,串口部分代码就不参与编译,省下不少Flash空间。这个习惯帮我快速定位了多帧去抖、同步头溢出两个问题,建议做类似项目的人一开始就把调试接口留出来。
7.3 最终整机验证和后续扩展思路
整机烧录后测试了大概一周,包括频繁按键、断电重启、距离变化、两把遥控器交替使用,都没出问题。有一次家里路由器放在旁边,2.4G WiFi信号对433接收基本没有干扰,但在那种金属配电箱旁安装时接收距离缩小明显,这属于安装位置的锅,不是解码方案的锅。
当前这套硬件其实还留了余量。如果后续要增加更多输出,可以把P3.3甚至P5.5都利用起来,做成两路或三路独立控制;如果要做485上报,串口引脚已经有了,加一颗485芯片就能把抄表和远程控制功能一起做进去。这些扩展都不需要改核心解码代码,状态机和解码逻辑可以原样复用。
最后再分享一个实际心得:433解码这类项目,很多人一上来就急着写代码,结果在接收模块选型和编码格式理解上栽跟头。我建议先拿逻辑分析仪抓一下自己手头遥控器的波形,把同步头宽度、高低电平时序这些参数摸清楚再动手,后面会顺利很多。毕竟硬件信号长什么样,软件的判断就得跟着长什么样。
本文还有配套的精品资源,点击获取