ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

433MHz EV1527遥控器解码:从协议到单片机状态机移植

433MHz EV1527遥控器解码:从协议到单片机状态机移植 简介面向需要实现433MHz无线遥控功能开发的单片机工程师这份EV1527解码程序提供了一套可直接移植的C语言源码无论使用AVR、ARM Cortex-M、PIC还是STM32等常见平台都能通过中断方式完成信号接收与解码不阻塞主流程适合遥控系统、无线传感器等低功耗短距离场景。压缩包共2个文件包含EV1527.c源文件与EV1527.h头文件前者实现解码算法与信号处理流程后者提供函数声明与常量定义便于快速集成。已有922人学习下载可见其通用性受到开发者认可。压缩包虽仅2KB但代码结构清晰开发者还提供了移植教程入口按教程适配引脚与定时器后即可获得完整的433MHz数据解析能力用于扩展无线控制功能。 做单片机项目时遇到需要接收433MHz遥控器信号的场景并不少。最开始我图省事直接买了带解码输出的接收模块结果发现要么学习按键数量有限要么拿到手的模块解码逻辑跟遥控器对不上而且价格还不便宜。后来换成“射频接收头自己写解码程序”的路线一个几块钱的RXB6再加几十行C代码就把EV1527遥控器完整解了出来放在51、STM32、ESP32上都能跑。这篇就聊聊我在写433-EV1527解码程序时积累的经验包括协议细节、状态机设计、移植方法和实际踩过的坑。1. 为什么放着现成的解码模块不用非要自己写1.1 成品解码模块的“省事”代价市面上的433接收模块大体分两类。一类是纯接收头输出原始波形比如RXB6、MX-05V价格一两块钱拿回来不能直接用得自己写解码。另一类是自带解码的模块比如带学习码输出的SRX882模块上直接有四个IO口可以接单片机遥控器按一下对应IO就输出高低电平看起来确实省事。但这类成品模块的问题在于扩展性差。它内置的了解码逻辑是写死的支持的学习按键数量有限而且换一个牌子的遥控器可能学习方式都不一样。更麻烦的是很多模块的IO输出是瞬态的你得自己判断按键按下的事件无法获取遥控器的唯一识别码。如果你的项目需要区分多个遥控器或者需要把遥控器特征值保存到EEPROM里做离线匹配成品模块基本帮不上忙。1.2 接收头加自研解码的真实成本自研解码方案用到的硬件极其简单一个433MHz超外差接收头RXB6或者国产兼容型号约1到3元。一个单片机GPIO支持外部中断最好。一个定时器能产生微秒级时间戳。就这么点东西解码出来的数据是完整的20位原始码包含ID、按键码和序列号。你可以自己做任意复杂的配对逻辑也可以把接收到的码直接通过串口打印出来排查问题非常直观。1.3 这篇文章适用的场景和读者如果你在做智能家居遥控开关、无线门铃识别、遥控插座改造、车库门控制、毕业设计里用到433M遥控器或者单纯想搞懂EV1527这种学习码芯片到底是怎么编码的那这篇文章应该对你有用。我会把解码协议、通用状态机、完整代码和移植方式都写清楚尽量做到你拿到代码后换个单片机也能跑起来。2. EV1527协议里的关键细节三态编码、同步码和振荡电阻2.1 EV1527是什么和PT2262有什么区别EV1527是台湾远翔科技推出的一颗编码发射芯片常见于各种433MHz/315MHz遥控器。华矽的HS1527、瑞泰的RT1527、飞凌的FP1527这些芯片和EV1527基本引脚兼容、编码协议一致所以行业里统一叫“1527编码”。提到1527很多老工程师会想到PT2262。两者最大的区别是PT2262是固定码芯片靠外部拨码开关或者跳线组合设置地址和数据发出去的码永远不变。EV1527是学习码芯片它的编码结果里包含12位随机序列号每次上电重新生成。接收端必须先“学习”一次把序列号记下来之后才能识别这个遥控器。这也就是为什么市面上很多遥控插座、遥控门铃第一次使用时需要“对码”才能正常工作。2.2 一帧数据到底长什么样EV1527发射的一帧数据由同步码加20位数据码组成。以常见的2.7MΩ振荡电阻为例基本时间单位T大约在300到350微秒左右同步码1个T的低电平加31个T的高电平总宽度约32T。数据位01个T的高电平加3个T的低电平总宽度约4T。数据位13个T的高电平加1个T的低电平总宽度约4T。悬空位f高、低电平各1T再加一个1T宽度的特殊脉冲普通遥控器里很少用到。20位数据按“高位先发”的顺序输出依次是4位ID、4位按键码、12位序列号。具体来说高4位是芯片ID很多遥控器固定为0b1000或0b0101。中间4位是按键码比如遥控器上A键是0b0000B键是0b0001等。低12位是序列号上电随机生成范围0到4095。按下遥控器按键时接收端会持续收到多帧相同的数据直到松开按键。2.3 T值为什么重要振荡电阻如何影响解码EV1527发射模块上有一颗振荡电阻决定了T的大小。不同厂家为了省成本或者匹配天线用的电阻值不一定一样。比如2.7MΩ时T约300多微秒1.2MΩ时T可能只有150微秒4.7MΩ时T能到700微秒。写解码程序时如果按照绝对时间宽度去判断数据位遇到不同振荡电阻的遥控器就容易误判。更稳妥的做法是只判断相邻两个电平的时长比例而不是判断绝对宽度。因为不管T怎么变数据位内部的长短段比例始终接近3比1同步码内部长段和数据位短段的比例接近31比3这个比例关系是非常稳定的。我的解码程序正是基于这个思路设计的。3. 兼容所有单片机的解码方案一个IO加一个定时器3.1 为什么不用输入捕获外设很多STM32的教程一上来就教你用定时器输入捕获来测脉宽。输入捕获确实好用但问题在于不是所有单片机都有这个外设。比如经典的STC89C52就没有输入捕获通道你要移植解码程序就得重新写一套。为了让代码在51、STM32、ESP32、AVR上都能跑我选择最基础的资源一个GPIO的外部中断加一个自由运行的定时器。外部中断用来记录边沿时刻定时器只负责提供一个微秒级的计数时间戳。这套方案几乎没有平台依赖性。3.2 核心原理边沿中断加时间戳解码的核心原理很简单每次GPIO电平跳变上升沿或下降沿都会触发外部中断我们在中断里读取定时器的当前计数值减去上次记录的时刻就得到一段电平的持续时间。连续的电平持续段构成了完整的编码流。为了识别同步码我利用的是一个关键特征同步码里有一段极长的电平宽度约31T远大于普通数据位的任何一段电平。程序里判断如果某一段电平持续时间超过5毫秒就认为收到了同步码。这个5毫秒的阈值对常见的T值范围150到700微秒都适用足够可靠。3.3 状态机的三个关键阶段解码过程是一个典型的状态机空闲态等待第一个边沿准备检测同步码。同步态逐段测量电平宽度寻找超过5毫秒的长电平。数据态找到同步码后跳过同步码后面的短电平开始把后续电平按“两段一组”解析成数据位。这里有一个容易出错的点数据位是连续的位和位之间没有停顿。如果分组错位后面所有位都会错。所以我的做法是检测到同步码长电平之后用一个专门的“跳过短电平”状态把同步码后半段的1T短电平忽略掉然后再开始数据位的两段分组。3.4 数据位判定里的容差设计在数据态中我把每个数据位对应的两段电平分别记为seg1和seg2。判定逻辑为如果max(seg1, seg2) 大于 min(seg1, seg2)的2倍认为是有效数据位。如果seg1大于seg2该位判为1。如果seg2大于seg1该位判为0。如果两段宽度接近说明是干扰或者悬空位直接丢弃并重新等待同步码。这里用2倍作为判定阈值而不是严格的3倍是为了容忍波形上升下降沿带来的测量误差。实测下来这个阈值对大多数EV1527遥控器都有不错的兼容性。4. 完整解码程序剖析从边沿中断到帧校验4.1 移植接口定义为了做到“兼容所有单片机”程序把平台相关的部分封装成两个接口extern void ev1527_timer_init(void); /* 初始化1MHz自由运行定时器 */ extern uint32_t ev1527_timer_get(void); /* 读取当前1MHz计数值 */解码逻辑本身完全不依赖具体MCU的寄存器只要这两个接口实现正确代码就可以在任意单片机上运行。定时器建议配置成1MHz计数频率也就是每1微秒计一个数。如果单片机主频不够高无法做到1MHz精确计数也可以适当降低频率只要保证分辨率在20微秒以内即可。4.2 核心代码下面是我整理后的完整解码程序去掉了具体的平台相关部分只保留核心状态机逻辑#include stdint.h /* 移植接口由外部实现 */ extern void ev1527_timer_init(void); extern uint32_t ev1527_timer_get(void); /* 解码状态 */ typedef enum { DECODE_IDLE 0, DECODE_SYNC, DECODE_AFTER_SYNC, DECODE_SEG1, DECODE_SEG2 } decode_state_t; /* 解码结果 */ typedef struct { uint16_t raw; /* 原始20位数据 */ uint8_t id; /* 4位ID */ uint8_t key; /* 4位按键码 */ uint16_t serial; /* 12位序列号 */ } ev1527_frame_t; /* 解码成功回调在中断上下文执行建议只做标志位处理 */ extern void ev1527_frame_received(ev1527_frame_t *frame); /* 同步码长电平最小宽度5ms覆盖常见振荡电阻对应的T值 */ #define EV1527_SYNC_MIN_US 5000u /* 数据位两段长度比例阈值max / min 2 判定为有效 */ #define EV1527_RATIO_THRESHOLD 2u static volatile decode_state_t g_state; static volatile uint32_t g_last_tick; static volatile uint16_t g_raw; static volatile uint8_t g_bit_count; static volatile uint32_t s_seg1; void ev1527_gpio_isr(uint8_t level) { uint32_t now ev1527_timer_get(); uint32_t dur now - g_last_tick; g_last_tick now; switch (g_state) { case DECODE_IDLE: g_state DECODE_SYNC; break; case DECODE_SYNC: /* 检测到超过5ms的长电平认为是同步码的长段 */ if (dur EV1527_SYNC_MIN_US) { g_raw 0; g_bit_count 0; g_state DECODE_AFTER_SYNC; } break; case DECODE_AFTER_SYNC: /* 同步码后面的短电平直接跳过准备对齐数据位 */ g_state DECODE_SEG1; break; case DECODE_SEG1: /* 记录数据位第一段 */ s_seg1 dur; g_state DECODE_SEG2; break; case DECODE_SEG2: { uint32_t seg2 dur; uint32_t max_dur (s_seg1 seg2) ? s_seg1 : seg2; uint32_t min_dur (s_seg1 seg2) ? s_seg1 : seg2; if ((min_dur 0) (max_dur (min_dur * EV1527_RATIO_THRESHOLD))) { /* 有效数据位seg1大于seg2判为1反之判为0 */ uint8_t bit (s_seg1 seg2) ? 1u : 0u; g_raw (uint16_t)((g_raw 1) | bit); g_bit_count; if (g_bit_count 20) { ev1527_frame_t frame; frame.raw g_raw; frame.id (uint8_t)((g_raw 16) 0x0F); frame.key (uint8_t)((g_raw 12) 0x0F); frame.serial (uint16_t)(g_raw 0x0FFF); ev1527_frame_received(frame); /* 接收完成复位等待下一帧 */ g_state DECODE_IDLE; g_bit_count 0; g_raw 0; break; } g_state DECODE_SEG1; } else { /* 两段宽度接近判为干扰或悬空位重新同步 */ g_state DECODE_IDLE; g_bit_count 0; g_raw 0; } break; } default: g_state DECODE_IDLE; break; } }注意代码里有一个关键的位序约定g_raw (g_raw 1) | bit意味着先收到的数据位放在高位这与EV1527高位先发的时序一致。如果你的遥控器和这个顺序相反把移位方向改一下即可。4.3 51单片机和STM32的接入示例在STM32上接入代码很简单。用TIM2产生1MHz计数PA0作为外部中断输入void ev1527_timer_init(void) { /* 使能TIM2时钟配置为1MHz向上计数 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitTypeDef t; TIM_TimeBaseStructInit(t); t.TIM_Prescaler 72 - 1; /* 72MHz分频到1MHz */ t.TIM_Period 0xFFFF; TIM_TimeBaseInit(TIM2, t); TIM_Cmd(TIM2, ENABLE); } uint32_t ev1527_timer_get(void) { return TIM_GetCounter(TIM2); } void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { EXTI_ClearITPendingBit(EXTI_Line0); uint8_t level (GPIOA-IDR GPIO_Pin_0) ? 1 p a hrefhttps://download.csdn.net/download/qq_37749995/89471309 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表