尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于51单片机的红外遥控器设计:NEC协议解码与发射实现

基于51单片机的红外遥控器设计:NEC协议解码与发射实现 简介基于51单片机的红外遥控器设计文档面向电子类专业学生、嵌入式初学者及毕业设计开发者解决不同品牌家电遥控编码互不兼容、需要多个遥控器的问题。整套方案以STC89C52RC单片机为核心集成IR01红外学习模块、LCD1602液晶显示屏与4×4矩阵键盘实现红外编码的捕获、解码、学习和重发遥控距离覆盖4至6米家庭场景。文档内容涵盖系统方案与功能指标、单片机最小系统构建、红外学习模块工作原理、人机交互界面设计以及Keil uVision5和Altium Designer的具体应用可直接用于毕业设计撰写、电路制作和程序调试参考。包内为1个docx文件压缩包大小约2.8MB结构完整、条理清晰。已有4527人学习下载适用于单片机应用、红外通信及智能家居控制方向的课程设计与论文研究。1. 基于51单片机的红外遥控器设计在解决什么问题把HS0038B接收头接到P3.2按下电视遥控器的音量键串口打印出的数据如果是乱码多半不是协议理解错了而是下降沿之间的时间测量方式不对。基于51单片机的红外遥控器设计正是围绕这个问题展开的一方面要从NEC协议的引导码、地址码、命令码和反码中还原按键值另一方面要能用同一套时序把按键值重新调制到38kHz载波上发出去。这个题目同时覆盖硬件电路搭接、定时器配置、外部中断响应和抗干扰容错适合课程设计也适合做智能家居遥控开关的入门原型。把接收和发射两条链路打通之后剩下的技术动作都围绕一个核心精确测量和生成微秒级脉冲宽度。2. NEC红外协议与51单片机中断解码时序原理2.1 NEC协议帧结构与位定时判定参数家用遥控器里最常见的底层协议是NEC协议日系遥控器和绝大多数学习型遥控器都兼容它。一个完整NEC帧由引导码、4字节数据组成4字节分别是地址码、地址反码、命令码、命令反码全部按LSB先行的顺序发送。引导码固定为9ms低电平加4.5ms高电平长度比任何一个数据位都容易识别接收端通常把引导码当作一帧数据的起点。NEC对数据位的编码方式很统一逻辑1定义为560us低电平加1690us高电平逻辑0定义为560us低电平加560us高电平。两段都从低电平开始因此从接收端的下降沿看前后两个下降沿之间的时间差就是“低电平宽度加高电平宽度”逻辑1的周期约2.25ms逻辑0约1.12ms。这个时间差是解码时最直接、最可靠的判据比单独测量高电平宽度更省中断资源。下面是NEC协议各级时序与下降沿间隔的对应关系帧段发射端原始时序接收头输出端观察到的下降沿间隔引导码9ms低 4.5ms高约13.5ms数据“0”560us低 560us高约1.12ms数据“1”560us低 1690us高约2.25ms重复码9ms低 2.25ms高约11.25ms帧间隔高电平保持40ms以上不产生新的下降沿这套对应关系直接决定了后续中断服务函数里的判断分支。用一个简单的伪代码也能表达if (gap 12000 gap 15000) // 引导码13.5ms左右 state HEAD; else if (gap 800 gap 1500) // 逻辑01.12ms左右 bit_value 0; else if (gap 2000 gap 2600) // 逻辑12.25ms左右 bit_value 1;注意阈值不是取中心值而是留了上下十几的余量。原因是市售遥控器用的陶瓷谐振器精度各不同发射端本身就可能偏离标称时序百分之几到百分之十几再加上51单片机晶振误差和中断响应延迟把判定窗口放宽到15%到20%是常规做法。窗口也不能无限放宽否则引导码、重复码和逻辑1的区间会互相重叠。2.2 一体化接收头把38kHz载波解调成TTL脉冲这里要区分两个信号红外发射管发出的是38kHz载波而不是裸的TTL脉冲。38kHz只是运载工具给人耳听不到、给环境光滤波器区分用的真正携带信息的是载波“有”和“没有”的时间长短。一体化接收头内部集成了光电二极管、自动增益控制放大器和带通滤波器它把收到的38kHz信号解调后输出。没有收到38kHz载波时输出高电平收到时输出低电平所以NEC协议里9ms低电平、560us低电平这些段到接收头输出端都原样变成低电平高电平段则变成输出高电平。从51单片机的P3.2引脚看进去整个数据帧是一串周期不等的低电平脉冲脉冲宽度从几毫秒到几百微秒都有。接收头的带通特性也决定了发射端载波不能随意漂移标准38kHz附近的36kHz到40kHz范围能正常接收偏离太远会造成距离缩短甚至完全无响应。部分廉价遥控器为了省电把载波做得偏低接收距离会肉眼可见地下降这就是为什么换遥控器调试时要先确认时序而不是先怀疑单片机。2.3 外部中断0加定时器051单片机解码的组合选择51单片机的外部中断0默认接在P3.2配置为下降沿触发后接收头输出引脚直接连过来即可。定时器0配置为16位定时模式在12MHz晶振、12分频下计数器每加1正好是1us足以分辨NEC协议里1.12ms和2.25ms两种位周期也覆盖13.5ms的引导码。选择外部中断加定时器而不是主循环轮询IO口是因为一帧数据从引导码到最后一个数据位总共要持续约67ms其间最短的位周期只有1.12ms轮询循环如果再去刷新数码管或扫描按键很容易漏掉一个边沿。中断服务函数在51上的执行时间只有几个微秒远小于1.12ms的最小位周期。按最坏情况估算12MHz下每个机器周期1us中断响应加读出定时器再加清零大约20个机器周期左右CPU占用率不到百分之几。这种方案在51单片机硬件上是兼顾精度的默认选择。如果定时器0已经被温度采集或脉冲计数占用可以换用定时器1注意把TMOD的高四位和中断号改成interrupt 3其余逻辑不变但两个定时器同时运行时要避免在中断里做长延时。3. 51单片机红外遥控器的硬件设计与电路连接3.1 元件选型与关键参数主控选STC89C52RC或AT89S52标准40脚DIP封装开发板上一般标配12MHz晶振和两个30pF负载电容。接收头选HS0038B或1838B两者引脚兼容核心参数都是38kHz中心频率、集电极开路输出供电范围2.7V到5.5V。发射管选5mm圆形940nm红外LED正向压降约1.2V到1.5V持续工作电流上限100mA。驱动部分用S8050三极管放大倍数在120到250之间足够驱动百毫安级红外LED。这组选型在51课程设计里出现在元器件的BOM表中器件推荐型号关键参数项目中的作用主控单片机STC89C52RC12MHz晶振8KB Flash解码、发射、业务控制一体化接收头HS0038B38kHz输出端默认高接收解调红外发射管5mm 940nm正向电流20~100mA发射NEC调制信号NPN三极管S8050Ic500mA扩展IO驱动能力退耦电容10uF电解 0.1uF陶瓷并联在接收头VCC和GND抑制电源纹波接收头如果选用TSOP38238这类长条形封装引脚排列和HS0038B略有差别接线上要以器件数据手册为准不要照抄开发板的丝印。发射管必须确认是940nm红外波段不能用红色LED替代两者的光谱响应不匹配接收头的滤波窗口。3.2 接收端接线VCC滤波和P3.2直连HS0038B的三个引脚从左到右依次是OUT、GND、VCC不同封装的引脚间距可能不同接线时先找实物标识。VCC接开发板的5VGND共地OUT直接连P3.2。需要在接收头电源两端并一个10uF电解电容和0.1uF陶瓷电容电解电容滤低频纹波陶瓷电容滤高频干扰。这一步不能省因为红外接收头的AGC增益很高电源上的毛刺会被当成载波信号造成输出端乱跳轻则误码重则完全无法解码。OUT到P3.2之间可以串一个100欧到1k的电阻主要作用是在P3.2被误配置成推挽输出时限制电流正常准双向口模式下不用这个电阻也能工作。初始化时把P3整口置1让P3.2保持高电平等待下降沿触发// 接收头OUT接P3.2先保证P3.2处于输入高状态 sbit IR_IN P3^2; void IR_GPIO_Init(void) { P3 0xFF; // 准双向口内部上拉 IR_IN 1; }P3默认就是准双向口内部有上拉电阻不需要额外外部上拉。如果开发板上P3.2已经被其他外设占用要么换外部中断1对应P3.3要么割线优先选择换引脚而不是改电路因为外部中断1的寄存器配置只是把IT0/EX0改成IT1/EX1中断号改成2代码改动量更小。3.3 发射端电路直接驱动和三极管驱动直接用单片机IO口驱动红外LED是最简做法原理图上是P3.4串一个200欧电阻到LED阳极LED阴极接地IO输出高电平时LED点亮。这种方式驱动电流被IO引脚限制在20mA左右实测遥控距离通常在1米到3米房间内短距离演示够用。想要获得更远的控制距离要改成S8050三极管驱动。三极管驱动电路的接法是P3.4接1k基极电阻到S8050基极发射极接地集电极接红外LED阴极LED阳极经47欧限流电阻到5V。当P3.4输出高电平时三极管导通电流约等于5V减去LED压降和饱和压降再除以47欧估算约68mA。NEC协议里载波是脉冲式信号单个数据位中低电平只有560us发射管以低占空比工作瞬间68mA是安全的但不要改成长时间直流点亮。发射电路还有另一种常见位置LED阳极接IO经限流电阻到集电极利用低电平导通。这种接法和上述高电平导通区别在于电平逻辑相反发送函数里的载波使能条件要跟着反转。我一般用高电平导通因为和NEC协议“有载波为低电平”的表述配套调试时更不容易绕晕。4. 51单片机红外遥控器解码软件设计从脉宽测量到反码校验4.1 定时器0与外部中断0的初始化把定时器0配置成16位自由运行模式启动后一直计数不参与其他任务。外部中断0配置为下降沿触发中断服务函数每次被调用时先读出当前定时器值再清零重新计时。由于两次相邻下降沿的最大间隔只有13.5ms定时器0在12MHz下最大计数65535us不存在计数溢出的问题除非信号完全丢失。#include reg52.h unsigned char result[4]; // 地址、地址反码、命令、命令反码 unsigned char bitbuf[32]; // 暂存32个数据位 unsigned char bitcnt; // 当前有效位数 unsigned char frame_ready; // 一帧接收完成标志 unsigned char repeat_flag; // 重复码标志 unsigned int gap; // 相邻下降沿时间差 void IR_Init(void) { TMOD 0xF0; // 保留高四位避免影响定时器1 TMOD | 0x01; // 定时器0模式116位 TH0 0x00; // 初始值为0 TL0 0x00; TR0 1; // 启动定时器0 IT0 1; // INT0下降沿触发 EX0 1; // 使能外部中断0 EA 1; // 开总中断 }TMOD的低四位是定时器0的配置高四位是定时器1的配置。先TMOD 0xF0再TMOD | 0x01可以保证不会把定时器1的模式一并改掉。定时器初值从0开始中断服务函数每次读出后清零这样读出的gap就是相邻两次下降沿的实际间隔单位是us。4.2 中断服务函数读出间隔并识别数据位中断服务函数要做的事很集中读出gap、清零定时器、判断区间、写入位缓冲。判断顺序先引导码再重复码再逻辑1和逻辑0因为引导码和重复码的区间互不重叠数据位又明显短于这两种码顺序没有严格约束但先判断引导码可以让后续分支少做几次比较。void IR_ISR(void) interrupt 0 { gap (TH0 8) | TL0; // 读出上一次下降沿到此刻的间隔 TH0 0; // 清零下一轮从头计时 TL0 0; if (gap 12500 gap 14800) { bitcnt 0; // 引导码约13.5ms开始新一帧 } else if (gap 10500 gap 11900) { repeat_flag 1; // 重复码约11.25ms } else if (gap 2000 gap 2550) { bitbuf[bitcnt] 1; // 逻辑1约2.25ms } else if (gap 850 gap 1500) { bitbuf[bitcnt] 0; // 逻辑0约1.12ms } if (bitcnt 32) { bitcnt 0; frame_ready 1; // 集齐32位 } }读TH0和TL0的顺序有个细节应该先读TH0再读TL0读低字节的瞬间高字节可能刚好进位导致读数偏差。实际偏差只有1us到2us对1.12ms和2.25ms的判定影响可以忽略所以这个顺序问题在中断里一般不用特殊处理。读出之后立刻清零计数器比先判断再清零更保险因为中断服务函数执行期间定时器还在走晚清零就相当于把中断执行时间也算进下一段时间里了。阈值区间为什么不取对称以逻辑1为例标称2.25ms下限2000us、上限2550us整体偏向标称值两侧各百分之十几。遥控器发射端时序偏短的常见原因是用RC振荡器做载波时钟偏长的常见原因是MCU中断处理有延迟所以把窗口中心略向上抬一点。真正生产环境里更合理的方式是把这些阈值定义成宏按遥控器一个品牌一个品牌地标定课程设计阶段用固定值就够了。4.3 位组包、反码校验与命令提取32位数据在bitbuf里按接收顺序存放NEC协议先发LSB所以bitbuf[0]对应地址码的第0位bitbuf[8]对应地址反码的第0位。组包时用位移公式把每个字节的8个位拼起来第一位进入result的第0位void ParseFrame(void) { unsigned char i, j; if (frame_ready 0) return; for (i 0; i 4; i) { result[i] 0; for (j 0; j 8; j) { if (bitbuf[i * 8 j]) { result[i] | (unsigned char)(1 j); // LSB先发 } } } // 地址反码校验 if (result[1] ! (unsigned char)~result[0]) { frame_ready 0; return; } // 命令反码校验 if (result[3] ! (unsigned char)~result[2]) { frame_ready 0; return; } frame_ready 0; // 到这里result[0]是地址result[2]是命令码 }反码校验能拦截一部分干扰造成的错误帧但它不是万能的如果某个位同时被干扰且对应反码位也翻转校验照样通过。51单片机做红外遥控器时更重要的防护是时间窗口本身因为NEC协议的干扰大多来自环境光误触这些噪声脉冲宽度要么远短于850us要么不规则到根本落不进任何判定区间大多数噪声在这层就被过滤了。repeat_flag的使用方式要单独说。它只在重复码出现时置1同一个按键一直按住接收端会每隔110ms左右收到一次重复码主循环里可以利用这个标志做连续操作。要注意的是repeat_flag置1后不要随手清零应该由业务代码自己处理否则长按过程中主循环没来得及读下一次重复码来了又置1业务侧会丢掉一次长按状态。4.4 主循环里消费解码结果并避免中断竞争主循环的写法决定了解码结果能不能被可靠消费。最直接的做法是轮询frame_ready在总中断开启的状态下做业务处理void main(void) { IR_Init(); while (1) { if (frame_ready) { ParseFrame(); // 按result[2]的值分发按键事件 switch (result[2]) { case 0x45: break; // 某个遥控器的“播放/暂停”键 case 0x46: break; // “停止”键 default: break; } } if (repeat_flag) { // 长按分支业务处理完后再清标志 repeat_flag 0; } } }不要在中断服务函数里做数据解析也不要中断里调用ParseFrame。32位数据按位从bitbuf搬运到result需要几十个机器周期这在中断里执行会拉长对下一次下降沿的响应时间如果搬运期间下一个下降沿到达中断会嵌套或丢失。把解析放在主循环里中断只负责测量和存位两者职责清晰后期调试也容易定位问题。5. 自制51单片机红外发射端38kHz载波与NEC发送时序5.1 定时器1在12MHz晶振下的38kHz重载值发射端的核心是产生38kHz载波并且在特定时间段内让它出现或消失。51单片机用定时器1模式2实现最简单模式2是8位自动重载装载值写入TH1后每次溢出自动把TH1复制到TL1不需要在中断里手动重装定时误差最小。12MHz晶振下机器周期是1us38kHz方波的半周期约13.16us取整为13us对应定时器计数13次翻转一次IO。自动重载值计算如下参数计算过程结果机器周期12MHz / 121us载波半周期1 / (2 × 38kHz)约13.16us定时器计数13us / 1us13重载值256 - 13243这个结果产生的实际载波频率是1 / (2 × 13us) ≈ 38.46kHz与标准38kHz偏差约1.2%在一体化接收头带通范围内正常通信没有问题。5.2 基于定时器1中断翻转IO的发射代码发射IO用P3.4配合S8050三极管驱动红外LED。定时器1中断只负责翻转引脚由carr_en变量控制当前是否需要输出载波主流程通过置位或清除carr_en来决定引导码、数据位和空闲期间IO的最终电平。sbit IR_TX P3^4; bit carr_en 0; // 1输出38kHz载波0空闲低电平 void Timer1_Init(void) { TMOD 0x0F; // 只改高四位 TMOD | 0x20; // 定时器1模式28位自动重载 TH1 256 - 13; // 半周期13us TL1 256 - 13; ET1 1; // 允许定时器1中断 TR1 0; // 平时不启动 } void Timer1_ISR(void) interrupt 3 { if (carr_en) { IR_TX ~IR_TX; // 翻转得到38kHz方波 } else { IR_TX 0; // 空闲保持低电平 } }发送NEC帧时引导码、数据位和反码的生成方式完全一致只差高电平段延时不同。下面是一个完整的发送函数addr和cmd分别是要发送的地址码和命令码void delay_us(unsigned int us) { while (us--) { _nop_(); _nop_(); } } void SendNecByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { carr_en 1; TR1 1; // 打开载波统一发560us低电平 delay_us(560); carr_en 0; TR1 0; IR_TX 0; if (dat 0x01) { delay_us(1690); // 逻辑1持续1690us高电平 } else { delay_us(560); // 逻辑0持续560us高电平 } dat 1; } } void SendNecFrame(unsigned char addr, unsigned char cmd) { carr_en 1; TR1 1; // 引导码9ms载波 delay_us(9000); carr_en 0; TR1 0; IR_TX 0; delay_us(4500); // 引导码4.5ms空闲 SendNecByte(addr); // 地址码 SendNecByte((unsigned char)~addr); // 地址反码 SendNecByte(cmd); // 命令码 SendNecByte((unsigned char)~cmd); // 命令反码 }这套代码的时序精度主要取决于delay_us的循环开销。Keil C51在默认优化级别下while(us--)循环体加判断大约3到4个机器周期循环结束时还会多出几us的收尾开销所以delay_us(560)实际延时会比560us多出几us整体比例误差在1%以内对接收端判定没有影响。如果想做到us级精确可以把延时改成定时器计数但一般情况下软件延时已经能满足NEC协议容差。5.3 发射接收联调用两块开发板互相验证手头只有一块51开发板时先用遥控器配合上一章的解码程序把result[2]的值打印到串口确认地址码和命令码的对应关系。拿到一套确认无误的地址和命令后再把SendNecFrame填上同样的参数让开发板发射用红外接收头配合示波器观察发射端信号。没有示波器时可以用一台带红外接收头的51板子直接接收另一台51板子的发射输出看看解码结果是否一致。发射距离不够通常不是协议问题而是驱动电路和光路问题。IR LED的发射角度一般只有十几度到几十度正对接收头距离最远偏离角度越大距离衰减越快。用手指遮挡或者隔着塑料外壳测试时透光率会明显降低不要因为测试环境的原因误判电路故障。三极管驱动方案如果距离仍然不足首先检查限流电阻是否偏大其次确认IR LED是940nm波长而不是850nm的监控红外灯。6. 红外遥控器调试三板斧容差调整、波形核对与Proteus验证6.1 把时间阈值改成可调宏逐品牌标定NEC协议的时序在不同品牌遥控器上差异可以大到正负20%统一用固定值会漏掉一部分遥控器。把第4章的中断服务函数里的阈值提取成宏定义例如#define GAP_0_MIN 850、#define GAP_0_MAX 1500、#define GAP_1_MIN 2000、#define GAP_1_MAX 2600换一个遥控器时只改这一组数值。判断依据是近距离按下按键如果偶发性丢码先把对应区间的下限降低或上限抬高如果出现串键说明两个判定区间重叠了要反向收紧。对51单片机而言阈值调整是最常用的容差优化手段代价只是中断里多做几次立即数比较。6.2 用逻辑分析仪同时看接收头输出和发送波形检查点逻辑分析仪接法预期波形接收头OUTCH1接P3.2引导码约13.5ms低电平随后32个宽度不同的脉冲发射管驱动端CH2接IR LED两端数据位内能看到连续的38kHz方波载波质量单通道触发下降沿高电平和低电平宽度应接近接收头的判定窗口接收头OUT最好用下降沿触发一帧数据的起点就是引导码的下降沿。发送波形建议接在三极管集电极这个点的电平幅度大逻辑分析仪更容易稳定触发。如果接收正常但发送波形里看不到38kHz先确认TR1是否被打开再看carr_en在延时期间有没有被其他中断意外清零。6.3 用Proteus先调逻辑再用实物测距离Proteus元件库里的红外接收模型可以配合虚拟遥控器完成协议层面的调试比如确认帧组包顺序、反码校验逻辑和重复码处理是否正确。但仿真模型不会真实模拟红外发射角度、接收灵敏度、环境光干扰和电源噪声所以出现“仿真通过、实物乱码”时优先检查实物电路中的滤波电容、P3.2上拉状态和晶振频率而不是怀疑协议代码。按这个顺序排查能省掉大量对着示波器猜原因的时间。最后把result[2]当作switch分支条件灯、继电器和风扇就都能用一把遥控器接管了。本文还有配套的精品资源点击获取
返回列表