简介这是一份面向51单片机开发者的433MHz与315MHz无线遥控解码工程包含完整源码、工程配置与编译产物适用于需要实现遥控信号学习、存储与重放的嵌入式场景。压缩包共26个文件涵盖C源文件、头文件、Keil工程文件、汇编启动文件以及编译生成的HEX和OBJ等其中delay.c与rx.c分别承担延时控制与接收解码逻辑main.c用于主流程整合方便直接移植或二次修改。资源整体仅205KB结构精简适合初学者对照学习解码时序也适合有经验者快速提取核心算法。目前已有2363人学习下载评价集中于工程完整、可直接参考。通过该工程可掌握433/315模块的软件解码思路理解学习型遥控器的按键识别与码值存储流程并借助附带的学习功能快速验证解码效果节省从零调试无线协议的时间。1. 51单片机433遥控解码一个“会学习”的万能遥控器值不值得做你抽屉里多半躺着两三把 433MHz 或 315MHz 的遥控器车库门、卷帘门、走廊灯、遥控插座、老式门禁。原装遥控器一旦丢了配一把新的要几十块还得找商家对码。用 51 单片机做一套“433 遥控解码带学习功能”的最小系统成本十几块钱就能把这些遥控器一把把“抄”进来再合体成一个学习型万能遥控器。这件事的本质并不玄接收模块把射频信号解调成一串高/低电平51 单片机量出每个电平的宽度解析成帧再把帧或原始波形存进 EEPROM需要时用发射模块按同样时序重放。适合想复刻门禁遥控、做多设备联动开关、或者单纯想练手中断与状态机的读者。2. 解码前先搞清 433/315 遥控的编码底细同步头、数据位与学习码2.1 为什么 433/315 还能被“解码”ASK/OOK 调制与超再生接收这些遥控器绝大多数用的是 ASK 调制本质就是“有载波发 1、无载波发 0”也叫 OOK 开关调制。按键按下后编码芯片内部产生一串高/低电平脉冲序列控制发射管在 433.92MHz 或 315MHz 附近的频率上把载波一段段“切”出去。接收端用超再生或超外差接收模块把载波解调数据脚直接输出与发射端同步的高/低电平。所以 51 单片机看到的根本不是载波只是一个数字波形——解码的任务从“接收射频”变成了“测量一串高低电平分别持续了多长时间”。这就把问题大大简化了解码精度取决于时钟不取决于射频部分。用 12MHz 晶振的普通 51机器周期正好 1us定时器加一就是 1us量几百微秒的脉冲宽度绰绰有余。这也是为什么这类项目用 51 就够不必上 STM32——虽然 STM32 的输入捕获更方便但 51 的定时器加外部中断同样能干而且代码量小、好调试。2.2 固定码与学习码PT2262 和 EV1527 的数据帧结构市面上常见的遥控编码芯片就几种最需要分清的是固定码和学习码。PT2262 是典型的固定码地址位由芯片引脚上的跳线或焊盘接法决定出厂后不会变接收端比如 PT2272也必须用相同的地址跳线才能配对。这种遥控器“对拷”难度低把地址位和数据位读出来用单片机照着重放就行。EV1527、RT1527 这类是学习码地址位是芯片内部出厂写死的随机码用户看不到也改不了。遥控器能配对的秘密在于接收主机支持“学习”主机先把收到的地址码存下来之后只响应这份码。这正好给“带学习功能”的解码方案留了空间——我们用单片机把 EV1527 的地址码和按键码抄下来复刻出的遥控器对学习型主机同样有效因为主机认的是码不是硬件。数据帧结构上这类芯片大同小异一帧由同步头加一串数据位组成。同步头是一段明显长于普通数据位的电平用来通知接收端“帧开始了”数据位则用两个电平的宽度比表示 0 和 1。比如 EV1527 常见格式是同步头为 1ms 以上的长低电平数据位由一个固定宽度的高电平和一长一短的低电平组成低电平短的判 0长的判 1一帧约 20 位地址加按键码。PT2262 则是同步头为“短高电平加超长低电平”数据位的 0/1 由高电平宽度区分。不同厂家晶振阻值不同具体微秒数有差异但 0 和 1 的宽度比值关系是稳的这正是软件解码能通用的基础。编码芯片常见频率帧长类型特点PT2262315MHz / 433MHz24 位左右固定码地址由引脚跳线设定可对拷EV1527 / RT1527315MHz / 433MHz20 位左右学习码地址出厂写死主机靠学习绑定HS2260315MHz / 433MHz20 位左右学习码与 EV1527 协议接近注意位序实际项目里不必死记芯片参数重点是知道“同步头用来定位数据位靠宽度比判 0/1”。具体阈值要从实测波形来定下面一节就是干这件事的。2.3 手边没有示波器怎么定阈值逻辑分析仪抓一个包没有示波器也能干这活一台几十块钱的逻辑分析仪加一个 Saleae 兼容软件就够了。接线很简单逻辑分析仪的通道 0 接接收模块的 DATA 脚GND 接 GND采样率设 5MHz 或 10MHz采样时长设 50ms然后按一下遥控器。抓到的波形就是接收模块解调后的原始脉冲序列。打开波形后先找最宽的那个低电平或高电平多半是同步头。以 EV1527 为例我实测过的一款 433MHz 遥控器同步头低电平约 1.2ms数据位高电平约 350us低电平短约 320us长约 780us。那判定规则就清楚了低电平小于 500us 判 0大于 500us 判 1超过 900us 就当同步头。注意有些遥控器的位序是从最低位开始发解析完要反转一下这属于常见差异。抓波形的另一个目的是确定帧长数一数同步头之后有多少个高-低电平对20 对就按 20 位处理24 对就按 24 位处理。这个数会直接变成代码里的一个宏。千万别跳过这一步直接抄网上的参数不同牌子遥控器的振荡电阻不同脉宽能差两倍。我一般会抓完波形后用软件自带的光标量出三组数据同步头宽度、最短低电平、最长低电平然后才写代码。3. 搭建最小解码系统接收模块、51单片机与脉冲测量电路3.1 接收模块选型超再生还是超外差315 和 433 模块不能混用接收模块是整套系统的“耳朵”。超再生模块便宜几块钱一片灵敏度也够但缺点是噪声底较高附近有强干扰源时数据脚会抖出毛刺。超外差模块贵一些但抗干扰和稳定性明显更好适合做学习型转发器这种要长期通电的设备。我的建议是调试阶段先用超再生的成本低、坏了不心疼如果发现解码率上不去再换超外差模块硬件接线不用动。频率必须严格匹配315MHz 遥控器配 315MHz 接收模块433MHz 遥控器配 433MHz 接收模块。有些模块标“315/433 兼容”实际带宽有限两个频点都能收到但灵敏度都打折扣建议别在这种模块上省钱。发射模块同理低频段和高频段不能互换。天线长度是个容易被忽略的点433MHz 的 1/4 波长天线约 16.5cm315MHz 约 23.8cm。接收模块焊一根这么长的导线解码距离能明显改善。我见过不少翻车案例解码板放桌上能收到装进盒子里距离缩一半十有八九是天线被压在金属外壳下面或长度不对。3.2 51单片机引脚分配与定时器安排把 INT0 和定时器 0 让给解码MCU 我一般用 STC89C52RC 或 STC15W408AS。前者是传统 12T 单片机资料多跟着江科大51单片机笔记那套入门流程就能跑起来后者是 1T 增强型内部自带 EEPROM省一片 24C02适合做“学习功能”的产品化方案。如果手头只有 AT89C52 这类老片子就在外部接一片 24C02 存码表功能上没差别只是多写一段 I2C 时序。引脚分配要提前规划好因为解码占用的资源比较固定引脚功能说明P3.2 / INT0接收模块 DATA解码的核心输入必须接外部中断 0P1.1发射模块 DATA重放时输出脉冲序列P3.3学习键长按进入学习模式P1.0状态指示灯学习成功或发射时点亮定时器 0脉宽计时方式 116 位12MHz 下 1us 计数解码只占 INT0 和定时器 0定时器 1 还能留给串口打印调试信息或者做 LED 闪烁的时基。分配好之后建议对着 51单片机原理图 核一遍晶振和复位电路晶振一定要是 12MHz因为后面所有时间测量都以“定时器加一次等于 1us”为前提板载晶振如果是 11.0592MHz虽然串口波特率更好算但解码时间基准就变了要么改计算要么换晶振。3.3 中断测脉宽的正确姿势边沿来回切换别被 51 的单边沿限制坑到51 的外部中断 INT0 不像 STM32 那样支持双边沿触发TCON 里的 IT0 位只能让它在下降沿或上升沿二选一触发。这就带来一个问题要同时测量高电平宽度和低电平宽度必须在中断里来回切换触发边沿。做法是进入中断时先停表读出当前计数值得到上一个电平的宽度然后根据“刚才触发的是哪种边沿”判断刚测完的是高还是低再切换 IT0 方向清零定时器重新计时。这段代码是整套解码的地基我一般会花半小时把它调稳再往下写// 433/315 遥控解码: 外部中断 定时器 0 测脉宽 // 晶振 12MHz, 定时器 0 工作方式 1, 计数周期 1us #include REG52.H sbit RF_DATA P3^2; // 接收模块 DATA 接 P3.2 / INT0 volatile unsigned int high_width; // 最近一次高电平宽度, 单位 us volatile unsigned int low_width; // 最近一次低电平宽度, 单位 us volatile bit new_sample; // 新样本标志, 供主循环查询 void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // 定时器 0, 16 位方式 1 TH0 0; TL0 0; TR0 1; // 启动计时 } void INT0_Isr(void) interrupt 0 { unsigned int now; TR0 0; // 先停表, 防止读到一半计数变化 now (TH0 8) | TL0; // 读当前计数值, 12MHz 下 1us 加 1 if (now 10000) { // 超过 10ms 的是空闲低电平或噪声, 丢弃 TH0 0; TL0 0; IT0 0; // 回到上升沿触发, 等待下一帧开始 TR0 1; IE0 0; return; } if (IT0 1) { // 本次是下降沿触发: 刚结束的是高电平 high_width now; IT0 0; // 下一个边沿是上升沿 } else { // 本次是上升沿触发: 刚结束的是低电平 low_width now; IT0 1; // 下一个边沿是下降沿 } TH0 0; // 清零重新计时 TL0 0; TR0 1; IE0 0; // 清除中断标志, 防止边沿切换后产生误触发 new_sample 1; }逻辑说明程序维护了一个“只认识下一个边沿”的触发方向。初始设 IT00上升沿触发按键按下后接收脚从空闲低电平跳到高电平第一个上升沿进入中断此时测到的是“开机到上升沿”的时间必然超过 10ms 被丢弃正好完成一次复位。之后每个中断要么测出高电平宽度要么测出低电平宽度交替进行主循环里读 high_width 和 low_width 就能拼出完整的脉冲序列。参数说明now 10000这个阈值是万能的“空闲过滤器”因为所有遥控数据位的宽度都在 3ms 以内同步头最长也就 1ms 多超过 10ms 只可能是没按键时的空闲低电平。IT0 1和IT0 0的判断用了 TCON 里当前的触发方式作为“刚才是什么边沿”的证据这是在 51 上实现双边沿测量的通用技巧比单独读引脚电平更可靠。IE0 清零那一步不能省否则切换边沿后旧中断标志可能立刻再触发一次造成多测一个无效宽度。4. 用51单片机实现433遥控解码从脉宽到地址码与按键码4.1 把脉宽序列喂给状态机同步头判断、位宽判定与阈值设定拿到 high_width 和 low_width 之后下一步是把这串时间值翻译成 0/1。这块核心逻辑我习惯用一个简单的 51单片机状态机平时停在“空闲态”遇到超长低电平同步头就进入“收帧态”每收到一个高-低电平对就拼出一个数据位拼满规定位数后退回到空闲态。下面这段代码按 EV1527 的典型波形来写同步头用“低电平 900us”识别数据位用低电平宽度区分 0 和 1。如果你抓到的波形是 PT2262 那种高电平分 0/1 的把判断条件里的 low 换成 high阈值按实测改一下即可// 脉宽解析状态机: 识别同步头并拼装数据位 #define SYNC_MIN 900 // 低电平超过 900us 视为同步头, 按实测调整 #define ZERO_MAX 600 // 低电平小于 600us 判为 0 #define ONE_MIN 600 // 低电平大于等于 600us 判为 1 #define FRAME_BITS 20 // 一帧数据位数, EV1527 常见 20 位 unsigned char bit_cache[FRAME_BITS]; // 存放当前帧的 0/1 unsigned char bit_cnt; // 当前已收到的位数 bit in_frame; // 是否在收帧状态 bit frame_ready; // 收满一帧的标志 void Decode_Process(unsigned int high, unsigned int low) { if (low SYNC_MIN) { // 同步头出现, 开始新一帧 bit_cnt 0; in_frame 1; return; } if (!in_frame) return; // 没有同步头之前不拼位 if (low ZERO_MAX) bit_cache[bit_cnt] 0; else bit_cache[bit_cnt] 1; bit_cnt; if (bit_cnt FRAME_BITS) { // 收满一帧 frame_ready 1; in_frame 0; } }逻辑说明主循环里检测到new_sample标志后根据刚刚中断里更新的是高电平还是低电平把 high_width 和 low_width 轮流传入Decode_Process。状态机只看低电平第一次遇到超过同步阈值的低电平就认为帧开始了此后每次收到一个高-低电平对低电平小于阈值判 0、大于等于阈值判 1正好填到 bit_cache 里。EV1527 的按键码一般在固定位置把 bit_cache 按位拆开就能区分“上”“下”“停”等按键。参数说明SYNC_MIN、ZERO_MAX、ONE_MIN必须来自实测波形不能照抄。我曾经把 ZERO_MAX 设成 400us结果一把 315MHz 遥控器的“0”低电平是 450us全部误判成 1折腾半天才发现是阈值卡得太死。比较稳妥的做法是把 ZERO_MAX 放在实测最短低电平和最长低电平的正中间比如实测短 320us、长 780us就取 500us。FRAME_BITS也要和实测帧长一致有些 EV1527 变体是 24 位改这一个宏就行。4.2 解码结果怎么用连续两帧一致才算数遥控器发射时是连续重复发包的每帧之间隔着同步头。单帧解码结果可能因为噪声干扰出错所以工程上必须做“两帧校验”缓存上一帧的 bit_cache和当前帧逐位比较一致才认为解码有效。这比任何滤波算法都管用因为噪声不可能让几十位数据两次都碰巧相同。// 连续两帧校验, 避免单帧误码 unsigned char last_frame[FRAME_BITS]; unsigned char valid_frame[FRAME_BITS]; unsigned char same_cnt; void Verify_Frame(void) { unsigned char i; unsigned char diff 0; if (!frame_ready) return; for (i 0; i FRAME_BITS; i) { if (bit_cache[i] ! last_frame[i]) { diff 1; // 两帧有差异, 判定为噪声或干扰 break; } } if (!diff) { same_cnt; if (same_cnt 2) { // 连续两帧一致, 认为解码有效 for (i 0; i FRAME_BITS; i) valid_frame[i] bit_cache[i]; // 这里就可以把 valid_frame 拿去显示、存储或发射 } } else { same_cnt 0; } for (i 0; i FRAME_BITS; i) last_frame[i] bit_cache[i]; frame_ready 0; }逻辑说明frame_ready 由状态机在收满一帧时置位Verify_Frame 在主循环里被反复调用。它把当前帧和上一帧逐位比较相同则 same_cnt 加一累计到 2 就认定有效并拷贝到 valid_frame。任何一位不同都清零重来。这个方案能滤掉绝大多数随机干扰代价是解码响应慢一帧——对遥控器这种“按下去持续发包”的场景来说完全无感。参数说明same_cnt 2是最小有效值如果发现误码率还是高可以改成 3。但不要设太高否则有些只发两三帧的廉价遥控器可能一次都校验不过。这里没有用复杂算法一个计数器加一次异或比较就够了51 的资源完全扛得住。4.3 学习功能落地把码表写进 EEPROM掉电不丢按下即重放“带学习功能”的关键不是解码而是把解码结果持久化。我一般分两种做法如果已经确认遥控器是 EV1527 或 PT2262就只存解析好的地址位和按键位占用空间最小如果不想赌编码格式就干脆把一帧的脉宽序列原样存下来重放时按序列驱动发射模块。后者更像一个“黑匣子”对任何 ASK 编码都有效是学习型遥控器产品的常见做法。EEPROM 存储结构可以这样设计每个槽位固定占一块区域字段长度说明槽位状态1 字节0x55 表示已写入有效数据帧长度1 字节本帧高/低电平对的数量脉宽序列最多 32 对每对高、低宽度各 2 字节共 4 字节校验和1 字节所有字节求和用于读回时校验STC 单片机内部 EEPROM 的读写要操作 IAP 寄存器以 STC15 系列为例写一个槽位的核心代码如下// STC15 系列内部 EEPROM 写入示例 // 把学习到的 waveform 数组写入指定槽位, 槽位起始地址由 slot_base 决定 #define EEPROM_BASE 0x0000 void EEPROM_Write(unsigned int addr, unsigned char *buf, unsigned char len) { unsigned char i; IAP_CONTR 0x80; // 使能 IAP IAP_TCMD 0x01; // 触发命令位 for (i 0; i len; i) { IAP_ADDRH (addr i) 8; // 目标地址高 8 位 IAP_ADDRL (addr i) 0xff; // 目标地址低 8 位 IAP_DATA buf[i]; // 要写入的字节 IAP_CTRL 0x01; // 执行字节编程 _nop_; _nop_; _nop_; } IAP_CONTR 0x00; // 关闭 IAP }逻辑说明内部 EEPROM 按字节读写但写入前如果目标地址原本不是 0xFF需要先做扇区擦除否则写入结果不可靠。工程上最简单的做法是每个槽位单独占一个扇区学习时先擦除整个扇区再写避免跨扇区管理的复杂度。读回时把存储区的状态字节和校验和检查一遍不合法就当作空槽位处理。参数说明脉宽序列每对占 4 字节32 对应 128 字节一个槽位约 130 字节。STC15 系列内部 EEPROM 通常有 1KB 到 4KB存五六个槽位很宽裕。IAP_TCMD这个寄存器是 STC15 特有老 STC89C52RC 的 IAP 寄存器组略有不同但读写流程基本一致。如果你用外部 24C02I2C 地址一般是 0xA0按页写和页读的时序网上例程很多这里就不再重复。重放就简单了把脉宽序列从 EEPROM 读出来循环控制发射模块的数据脚。发射模块的 DATA 脚是“高电平发载波、低电平关载波”所以直接按原波形驱动即可// 按存储的脉宽序列重放一帧 // RF_TX 接 P1.1, 高电平 发射载波 sbit RF_TX P1^1; void Send_Waveform(unsigned int *wave_high, unsigned int *wave_low, unsigned char len) { unsigned char i; for (i 0; i len; i) { RF_TX 1; DelayU16us(wave_high[i]); // 保持高电平, 发射载波 RF_TX 0; DelayU16us(wave_low[i]); // 保持低电平, 关闭载波 } }这段代码的逻辑是“学什么放什么”学习时存的是原遥控器每一段高/低电平的持续时间重放时只要保证时序误差在几十微秒内接收主机根本分辨不出。DelayU16us建议用定时器 1 做一次性延时不要用 NOP 循环因为 NOP 循环在开中断后会被打断延时长度就不准了。实际发射要连续重复三五帧模仿原遥控器“按住不放”的行为很多接收主机帧数少于三帧会直接忽略。5. 433遥控解码常见问题与避坑信号、晶振和中断的五个坑坑 1同一个按键每次解码结果都不一样时好时坏现象用同一把遥控器按同一个键串口打印出来的地址码忽对忽错或者解码偶发成功大部分时间停在“等待同步头”状态。原因八成是接收模块电源纹波太大。超再生模块工作时会产生周期性电流波动和单片机共用一条电源线时纹波直接窜进模块的供电脚导致数据脚输出抖动另一可能是模块天线附近有金属遮挡信号被反射衰减。解决在接收模块 VCC 和 GND 之间并联 100uF 电解电容加 0.1uF 陶瓷电容电容引脚尽量短天线拉直远离单片机晶振和电源走线。我遇到过一块板子把接收模块放在晶振正下方解码率只有三成挪到板边后变成九成以上。坑 2测出来的脉宽全部是理论值的一半或两倍现象抓波形时明明量到同步头 1.2ms代码里打印出来的 low_width 却只有 600us 或者变成 2.4ms。原因晶振不是 12MHz或者 TMOD 配置把定时器 0 设成了分频模式。11.0592MHz 晶振下定时器加一是 1.085us误差虽然不大但有些板子用的是 6MHz 晶振甚至 24MHz打印出来的脉宽就会系统性翻倍或减半。解决第一件事不是改代码而是看板子上晶振外壳标的频率。如果是 11.0592MHz 又想继续用就把脉宽换算系数从 1 改成 1.085更干净的做法是直接换 12MHz 晶振。第二个检查点是 TMOD0xF0 那行务必保证定时器 0 的方式位是 0116 位不要残留定时器 1 的高 4 位配置。坑 3把解析逻辑写进中断主循环卡死或丢帧现象单独测中断计数正常一加入位拼接代码解码就开始丢帧有时按一下遥控器要两三秒才有反应。原因外部中断服务函数里做了太多事。51 的中断现场保护和恢复很耗时间如果每次边沿中断里都去访问 EEPROM 或者做数组搬运处理完前一帧的边沿时下一帧的数据位已经过去了。解决中断里只更新 high_width/low_width 和 new_sample 标志其余全部放主循环。这是 51 中断编程的铁律中断服务函数越短越好。脉宽测量的实时性由硬件保证位拼接慢一点不影响结果。坑 4同步头阈值写死换一把遥控器就失效现象A 遥控器解码完美换同品牌的 B 遥控器或者换 315MHz 频段的遥控器状态机永远停在等待同步头。原因不同遥控器的振荡电阻不一样脉宽差异很大。有些 315MHz 遥控器同步头低电平只有 500us而你写的 SYNC_MIN 是 900us永远认不出来反过来如果同步头阈值设太低数据位的长低电平又会被误判成同步头导致帧错位。解决用逻辑分析仪重新抓一把波形按实测值改 SYNC_MIN 和 ZERO_MAX。更稳的做法是写成自适应进入学习模式后先抓两帧统计出最短低电平和最长低电平把阈值自动设为两者中点再开始正式解码。这个自适应逻辑多花 30 行代码但能省掉以后所有换遥控器重新标定的麻烦。坑 5板子离遥控器 1 米能解隔 3 米就废现象桌面上测试正常把板子放到货架或墙角解码距离明显变短按遥控器要凑近才行。原因超再生模块灵敏度有限加上接收天线长度不对。433MHz 模块配了根 10cm 短线驻波比不好接收效率打折发射模块那边如果也用短线双重衰减后距离就只剩一两米。解决把接收模块天线补到约 16.5cm433MHz或 23cm315MHz发射模块天线也一样。这招对低成本模块的提升非常明显几乎不用改代码。实在要求更远距离就换超外差接收模块同一天线下接收灵敏度能差出好几米。6. 从解码到“带学习”的完整闭环码表管理与发射调用技巧学习功能要做得像正经产品还得管好码表。我在 EEPROM 里按“槽位号”组织每个槽位对应一个自定义按键。长按学习键进入学习模式指示灯亮起此时对着板子按一次原遥控器解码校验通过后把波形写入当前槽位指示灯闪两下表示完成。之后每次按下自定义键程序查槽位状态字节读到有效数据就从 EEPROM 读出脉宽序列连发三帧。验证方案强烈建议串口加逻辑分析仪双管齐下串口打印地址码和按键码确认解析正确逻辑分析仪双通道同时抓原遥控器和复刻板的发射波形比对每一段高/低电平宽度误差在 50us 以内基本稳过。我吃过一次亏直接把网上抄的 EV1527 参数写了进去没抓波形就烧录结果地址位全反了——有的遥控器是先发低位有的先发高位不看实测波形就是瞎猜。还有一个容易翻车的点如果设备是滚动码遥控每按一次码就变这套方案无效因为滚动码靠算法生成波形复刻只能还原某一次按键主机下一次就拒绝响应。判断方法很简单抓两次同一个按键的波形如果地址位每次都不一样那就是滚动码趁早放弃不要浪费时间。我的习惯是先抓波形再写代码把同步头、最短脉宽、最长脉宽、帧长四个参数用便签贴在调试器上再动键盘。阈值宁可放宽一点靠连续两帧校验来滤噪声也别卡得刚好否则换一把遥控器就得重新标定。希望帮到你。本文还有配套的精品资源点击获取