YAOTU INSIGHTS

51单片机红外遥控解码:格力空调遥控码接收程序全解析

51单片机红外遥控解码:格力空调遥控码接收程序全解析
简介基于51单片机的格力空调遥控码接收程序以中断方式捕获空调遥控信号并在1602液晶上实时显示解码结果适合电子爱好者、嵌入式初学者以及家电通信逆向开发人员参考可作为单片机外部中断应用与遥控码解析的入门实例。压缩包共56个文件约5.17兆字节包含C语言源代码、Keil工程文件、HEX烧录文件、PDF说明文档及图片等其中5份C程序涵盖解码与液晶显示逻辑4份PDF提供图文资料多个压缩包分卷存放历史版本Word说明文档则对格力遥控码协议进行了分析。已有1177人学习下载。压缩包完整保留了从早期调试到最终版的多个解码程序版本便于对照学习中断时序设计、码型对比与液晶驱动目录结构清晰反映了典型的单片机开发流程对红外遥控编码、中断接收及1602显示应用均有实践参考意义。1. 格力空调遥控码接收程序51单片机怎么把红外遥控变成可控信号格力空调遥控码接收程序51单片机做的——这份资源我拆过之后的第一感受是它把“红外遥控解码”从一个玄学问题变成了一个可以按图索骥的电平测量问题。你只需要一块51单片机最小系统板、一个几块钱的一体化红外接收头就能把格力空调遥控器发出的38kHz载波信号逐位捕获成0和1的数据帧再通过串口打印或者映射成IO控制。它适合想搞懂红外协议的学生、要给空调做红外转发的DIY玩家、以及准备做空调智能联动却卡在解码环节的从业者。注意它不是一个开箱即用的完整智能空调项目而是一个可靠的前端解码器具体按键对应的码表需要你自己抓取。下面我从协议原理、硬件接线到程序解析把这份资源的落地路径一次性讲透。2. 红外遥控原理与格力空调遥控码协议先搞懂电平宽度再写解码程序2.1 一体化接收头为什么输出的是方波而不是正弦波格力空调遥控器发出来的并不是连续红外光而是被38kHz载波调制过的信号。你可以把38kHz理解成红外LED在极快速地开关每秒钟通断三万八千次。遥控器内部先由MCU把按键信息编码成一系列高低电平序列接着用这个序列去控制载波通断发射载波的时间段对应“有脉冲”停止载波的时间段对应“无脉冲”。红外发射管最终发出的就是这种断续的载波包络。如果直接拿光敏管接收你得到的是一串叠加了38kHz成分的微弱模拟信号根本没有单片机可以直接读取的逻辑电平。一体化红外接收头常见的三脚封装型号很多内部集成了光敏二极管、前置放大器、限幅器、带通滤波器、解调器和输出整形电路。它的职责只有一个识别接收频段内有没有载波。有载波时输出脚拉低没有载波时输出脚保持高。于是你在单片机引脚上看到的是一串干净的TTL方波低电平表示载波正在发射高电平表示载波间隙。这里有一个新手最容易栽跟头的点接收头输出是反相的。也就是说遥控器发射的引导码如果是“9ms载波4.5ms空闲”经过接收头解调后单片机引脚上看到的是“9ms低电平4.5ms高电平”。很多人对照着NEC协议文档写程序脑子里想的是“9ms高电平4.5ms低电平”结果一个码都解不出来。后面避坑章我会专门再讲这个反相细节。2.2 格力空调遥控码的帧结构NEC协议的变体与厂商自定义字段格力空调遥控码并不是某个厂商独创的孤立协议。绝大多数格力遥控器底层用的都是NEC那套脉冲位置调制方式用“载波脉宽空闲脉宽”的组合表示逻辑0和逻辑1。典型NEC定义如下信号段载波宽度空闲宽度接收头输出电平序列含义引导码9ms4.5ms低电平9ms 高电平4.5ms帧起始逻辑00.56ms0.56ms低电平0.56ms 高电平0.56ms二进制0逻辑10.56ms1.69ms低电平0.56ms 高电平1.69ms二进制1空调厂商为了塞进更多控制信息经常在NEC标准帧上做扩展。电视遥控器一般只有32位数据位一次完整帧发完就停空调遥控器则会把开关、模式、风速、温度、扫风等一大堆状态一次性打包进一帧里帧长常常扩大到40位、48位甚至更多。网上能找到的“格力空调遥控码接收程序”很多就是针对这种扩展帧写的。所以如果你拿普通电视遥控器的解码程序去解格力空调遥控器大概率在帧结束判断上翻车。电视遥控器数满32个位就认为一帧结束空调遥控器必须靠“超时”来判断帧结束——连续一段时间没有新的载波脉宽进来才认为当前帧已经完整接收。格力按键按住不放时还会连续重发同一帧接收程序还得有能力区分“一次完整帧”和“重复帧”。这份资源里的51单片机接收程序核心思路正是这样用外部中断的边沿跳变记录每次电平持续时间再按NEC位宽把电平序列切分成引导码和数据位。它没有内置格力码表因为码表和具体空调机型强相关。比如某些机型的温度位编码用反码另一些机型用补码甚至同一品牌不同批次的遥控器按键码都可能不一样。因此接收程序的正确定位是“忠实还原每一帧原始码”而不是“直接翻译成温度数值”。提示如果你拿到的一帧里出现0.4ms、1.2ms这类非标准脉宽说明厂商可能微调过脉冲位置。解码阈值不能完全照搬NEC标准值要留出容差区间。2.3 为什么用外部中断加定时器轮询测量为什么不行51单片机主频不高如果用12MHz晶振一条标准指令周期就是1us。要是用while循环去轮询引脚电平CPU不仅要忙等测量精度还会被循环判断、变量自增等额外指令拖垮。0.56ms这种短脉冲轮询误差轻松超过几十微秒到了1.69ms的长脉冲上更容易误判。正确做法是在硬件层面等信号。把外部中断INT0配置成下降沿触发引脚电平一出现下降沿CPU自动跳进中断服务函数定时器0配置成16位模式专门测量两次下降沿之间的时间间隔。为什么选下降沿而不是上升沿因为接收头输出反相后引导码的低电平是9ms数据位里的低电平均在0.56ms左右下降沿正好是每段低电平的开始。你也可以在下降沿时读取上一个高电平的宽度两种方式都能还原电平序列只要收发双方对“一帧从哪里开始”的定义一致就行。我习惯用一个全局数组存时间值长度取64左右足够容纳40位数据帧产生的80个边沿。每次下降沿中断先把定时器计数值抄出来存进数组再把定时器清零重新计时。这样数组里偶数下标是低电平宽度奇数下标是高电平宽度。解析程序只需要成对处理看高电平宽度就能还原0和1。这个设计把中断服务函数做到极简不容易踩多任务竞争的坑。3. 硬件连接与最小系统从接收头到51单片机的五个关键点3.1 器件清单与接线表搭建这个项目需要的硬件不多清单如下51单片机最小系统板典型配置是STC89C52加11.0592MHz晶振以及两个30pF负载电容。一体化红外接收头三脚直插封装内部自带解调功能。USB转TTL串口模块用于把解码结果输出到电脑。面包板、杜邦线若干。一个LED或蜂鸣器用于硬件自测。接线表非常直接接收头引脚连接目标VCC单片机板5V电源输出GND单片机板GNDOUTP3.2INT0外部中断0输入这里要特别提醒一体化接收头的引脚顺序不同型号不一样常见的是“OUT、GND、VCC”排列但有些模块是“VCC、GND、OUT”。接线前必须看一眼器件手册或丝印把VCC和GND接反接收头当场烧掉。这个翻车成本不高但会浪费一整个下午。3.2 晶振、去耦电容、上拉电阻怎么选晶振频率直接影响定时器测量精度。如果用了11.0592MHz晶振定时器计数周期是12除以11.0592约1.085us不是整数但红外解码只要求微秒级容差完全够用。如果换成12MHz晶振计数周期刚好1us心算方便但串口波特率误差会变大。我的建议是只做红外接收、不打印串口调试信息时用12MHz要同时开串口看解码结果优先选11.0592MHz。去耦电容是很多人忽略的一环。接收头内部解调电路对电源纹波很敏感特别是和38kHz载波同频的纹波会直接串到输出脚造成误触发。我一般在接收头VCC和GND之间并联一个100uF电解电容和一个0.1uF陶瓷电容尽量让电容紧贴接收头引脚。供电直接用单片机板的5V不要为了省事去用USB转TTL模块的3.3V——接收头在3.3V也能工作但接收距离会明显缩短。接收头的OUT脚是否需要上拉电阻取决于具体型号的输出结构。很多一体化接收头内部是集电极开路输出需要一只10k电阻上拉到VCC才能得到稳定高电平。也有一部分推挽输出不需要上拉。稳妥做法是加上这个10k电阻不坏事还能避免引脚悬空时电平漂移。3.3 硬件自测用LED确认接收头有输出写复杂的解码程序之前先做一个两分钟的硬件自测。把P3.2引脚的电平直接送到P1.0的LED上按下遥控器任意键如果LED能跟着按键节奏明显闪烁说明从遥控器到接收头再到单片机引脚的物理链路是通的。#include REGX52.H sbit LED P1^0; sbit IR P3^2; void main(void) { while (1) { LED IR; // P3.2为低时LED亮为高时LED灭 } }这段代码没有任何协议处理作用就是把接收头输出电平实时透传到LED。按下遥控器按键时接收头输出持续翻转LED会出现肉眼可见的快速闪烁。如果LED完全不动先检查遥控器电池、接收头供电、引脚接线顺序如果LED一直亮或者一直灭大概率是OUT引脚接错了位置或者遥控器工作频率不是38kHz。提示不建议用手机摄像头看红外发射管来判断遥控器好坏。现在很多手机摄像头带红外滤光片看不到原本的红紫光。最靠谱的办法是用示波器抓接收头输出没有示波器就用上面的LED自测法。3.4 最小系统上容易被忽略的两个点51单片机的EA引脚必须接VCC。如果EA悬空或接低电平单片机会从外部ROM执行程序代码根本跑不起来表现就是上电后程序乱跳、串口无输出、LED状态随机翻转。我在实验室见过太多人接错这个脚一查原因就是EA没接。复位电路也不能省。典型参数是10k电阻接VCC、10uF电容接地复位引脚经电阻、电容组成的RC网络接到VCC。有些精简最小系统板把复位电路做在板上买回来后要确认它确实存在。晶振的两个负载电容缺一不可没有它们晶振可能起振困难程序运行会时好时坏表现出严重的“玄学”故障。4. 解码程序实现状态机捕获、超时判帧和码值转换4.1 程序框架与全局变量定义解码程序的整体思路是外部中断负责捕获边沿并记录电平时间定时器负责提供时间基准主循环负责等帧结束、解析数据、输出结果。先定义全局变量#include REGX52.H #include string.h #define MAX_EDGE 64 volatile unsigned int edge_time[MAX_EDGE]; volatile unsigned char edge_cnt 0; volatile unsigned char frame_ready 0; volatile unsigned char timeout_cnt 0;edge_time数组保存相邻两个下降沿之间的时间间隔用定时器0的16位计数值表示。假设晶振12MHz定时器一个计数周期1us最大可测量65535us足够覆盖9ms引导码和4.5ms间隙。frame_ready是帧结束标志主循环轮询这个标志就知道有没有完整帧可以解析。timeout_cnt是超时计数器用于判断帧结束。4.2 定时器与外部中断初始化初始化代码分成两部分定时器0配置成16位模式只计数不产生溢出中断外部中断0配置成下降沿触发。void Timer0_Init(void) { TMOD 0xF0; // 保留定时器1的配置 TMOD | 0x01; // 定时器0模式116位不自动重装 TH0 0; TL0 0; ET0 0; // 不使用定时器0溢出中断 TR0 0; } void Int0_Init(void) { IT0 1; // INT0下降沿触发 EX0 1; // 使能INT0中断 EA 1; // 打开总中断 }这里故意不开启定时器0溢出中断因为测量短电平根本用不到溢出。只要在帧内没有超过65535us的空闲段计数值就不会溢出。引导码低电平才9ms离溢出上限远得很。这种方式比“定时器溢出累加溢出次数”简单得多也容易调试。4.3 外部中断服务函数记录每一段电平时间中断服务函数是这份资源最核心的部分写法如下void int0_isr(void) interrupt 0 { static unsigned char first 1; unsigned int val; if (first) { first 0; TH0 0; TL0 0; TR0 1; timeout_cnt 0; // 第一个下降沿作为帧起点清零超时计数 return; } TR0 0; // 先停表避免读取过程中定时器继续跑 val (unsigned int)(TH0 8) | TL0; if (edge_cnt MAX_EDGE) { edge_time[edge_cnt] val; } TH0 0; TL0 0; timeout_cnt 0; // 每来一个边沿重置超时计数 TR0 1; }逻辑是这样的第一个下降沿出现时没有上一个电平宽度可以记录所以只做定时器清空和启动直接返回。之后每来一个下降沿先把定时器停掉读出当前计数值存入数组再清空定时器重新计时。TR00停表这一步很关键能避免在读TH0和TL0时定时器还在跑造成“高8位已进位但低8位还没读完”的错误。4.4 帧结束判定用定时器1做超时检测空调遥控的帧与帧之间有空闲时间这时不再产生下降沿。我们需要一个独立的超时检测距离最后一个下降沿超过15ms就认为一帧结束。选择定时器1做100us节拍。void Timer1_Init(void) { TMOD 0x0F; TMOD | 0x10; // 定时器1模式1 TH1 0xDC; // 11.0592MHz下大约100us TL1 0x00; ET1 1; TR1 1; } void timer1_isr(void) interrupt 3 { timeout_cnt; if (timeout_cnt 150) { // 150 * 100us 15ms if (edge_cnt 34) { // 至少要有引导码2个边沿加32个数据位边沿 frame_ready 1; } edge_cnt 0; // 清空边沿计数准备接收下一帧 timeout_cnt 0; } }注意定时器1中断和外部中断0中断都会访问timeout_cnt。外部中断里清零定时器1中断里累加。这个变量必须定义为全局变量不能做成static局部变量否则外部中断无法清零。怠慢这一点超时判断会失效帧结束标志永远不置位。4.5 数据位解析成对读取低电平和高电平当frame_ready变为1主循环调用解析函数。解析规则是把edge_time数组按“低电平、高电平”成对拆开低电平宽度大约都在0.5~0.6ms不需要严格区分高电平宽度用来区分逻辑0和逻辑1。unsigned char bit_buffer[16]; unsigned char bit_len 0; void parse_frame(void) { unsigned char i; unsigned int low, high; bit_len 0; memset(bit_buffer, 0, sizeof(bit_buffer)); if (edge_cnt 34) { return; } // 校验引导码edge_time[0]应在7ms~11ms之间 if (edge_time[0] 7000 || edge_time[0] 11000) { return; } for (i 2; i 1 edge_cnt; i 2) { low edge_time[i]; high edge_time[i 1]; // 低电平宽度不在合理范围则跳过 if (low 300 || low 900) { continue; } // 高电平宽度大于1.2ms判为逻辑1否则逻辑0 if (high 1200) { bit_buffer[bit_len 3] | (1 (7 - (bit_len 0x07))); } bit_len; } }bit_buffer按位存储bit_len记录实际接收的bit数。每8个bit组成一个字节最终得到一帧的原始码。这里没有做格力厂商的校验和验证因为我建议先把原始码打印出来用离线工具分析不同按键之间的规律等你摸清校验规则后再写进单片机也不迟。4.6 串口输出把原始码变成人类可读的十六进制最后通过串口把bit_buffer发送到电脑用串口助手查看。我习惯按十六进制输出一帧的字节数组而不是二进制位流这样便于肉眼比对不同按键的差异。void Uart_Init(void) { SCON 0x50; // 模式18位UART TMOD 0x0F; TMOD | 0x20; // 定时器1模式28位自动重装 TH1 0xFD; // 9600波特率晶振11.0592MHz TL1 0xFD; TR1 1; } void send_frame(void) { unsigned char i; for (i 0; i ((bit_len 7) 3); i) { SBUF bit_buffer[i]; while (!TI); // 等待发送完成 TI 0; } }主循环里一旦检测到frame_ready就调用parse_frame再调用send_frame输出。串口打印出的帧就是当前遥控器按键对应的原始码。把温度加、温度减、模式切换等按键依次按一遍记录下每个按键对应的字节序列你就得到了一张属于自己空调的码表。5. 避坑与常见问题排查五条实战踩坑记录5.1 现象一接收距离只有几厘米遥控器必须怼着接收头明明电路都接对了程序也能解出码但遥控器稍微离远一点就没反应接收距离不到十厘米。我排查过好几次最典型的原因是接收头电源去耦不到位。接收头内部增益很高电源上的纹波会直接影响解调灵敏度尤其是手摸到单片机板或者电源线时距离变化特别明显。解决方法是在接收头VCC和GND脚之间加一个100uF电解电容和0.1uF陶瓷电容两个电容都要尽量靠近接收头。同时检查供电来源不要让接收头和蜂鸣器、继电器共用一根电源线那些元件启动瞬间会把电源拉出毛刺。还有一个容易被忽略的点接收头窗口要避开强光直射尤其是节能灯和劣质LED灯它们的频闪会干扰接收头判断。5.2 现象二解码偶尔错码帧中间多了一个异常高脉冲同一个按键反复按串口打印出来的码大部分时候正常偶尔会多出几个bit或者某个bit从0变成1。用逻辑分析仪抓接收头输出发现波形在正常的下降沿处多了一个很窄的正脉冲毛刺。原因是接收头输出在电平翻转瞬间存在振铃单片机引脚检测到第二次下降沿导致一次电平变化被记录成两次。解决分两步硬件上在接收头OUT与GND之间并联一个100pF电容滤掉高频毛刺软件上在外部中断服务函数里加一个最小间隔判断如果本次下降沿距上次不足100us直接丢弃并且重置定时器。5.3 现象三程序跑飞或上电乱闪LED状态完全不受控代码没有任何问题但上电后LED乱闪串口也不输出像中了邪。这个现象在51最小系统上最常见的原因有两个一是EA引脚没有接VCC单片机从片外ROM执行程序二是复位电路参数不对上电瞬间无法正常复位。解决方法是检查EA引脚是否被强拉到VCC复位电路的电阻用10k、电容用10uF晶振的两个30pF负载电容必须焊上否则晶振起振不稳定。另外要确认P3.2引脚上电时的电平状态如果外部电路在初始化阶段把它拉低会在程序刚启动时制造一次假下降沿干扰第一帧接收。我一般会在主程序初始化时先关总中断配好定时器和外部中断配置最后再打开EA避免启动过程产生误触发。5.4 现象四长按遥控器时解码结果错乱帧数据对不上短按按键时解码正常长按超过两三秒后解码结果开始错乱。原因是我最初的实现里没有处理“重复帧”。空调遥控器在按住按键时会不断重发完整帧每两帧之间有几十毫秒空闲。我的超时判断用15ms作为帧结束条件导致一帧刚结束下一帧的引导码又开始了两帧数据被拼接在一起。解决方法是在超时判断中不仅要把frame_ready置位还要主动丢弃超时之后的所有边沿直到检测到新的引导码。同时可以在主循环里做一个去重如果连续两次解析出的byte数组完全一致只保留一次输出。这个去重逻辑在后续做学习型遥控器时特别有用。5.5 现象五串口打印全是乱码波特率设置怎么调都不对解码程序逻辑正确LED自测也正常但串口助手显示的全是乱码。排查后发现单片机板上焊的是12MHz晶振而程序里按11.0592MHz晶振计算的TH1重装值。12MHz晶振下9600波特率的误差累积很大远超串口容错范围。解决方法是优先换回11.0592MHz晶振这样TH10xFD对应9600波特率是标准的。如果板子上已经焊了12MHz晶振不想改就把波特率降到2400TH1重装值按12MHz计算误差控制在可接受范围内。我的习惯是拿到一块新开发板先用万用表或示波器确认晶振频率再决定初始化参数避免这种低级错误。6. 验证与进阶用逻辑分析仪把解码过程“可视化”再接入发射模块做闭环解码程序写完不能只看串口能打印数据就收工。我每次都会把逻辑分析仪的两个通道分别接在接收头OUT和单片机P3.2引脚上然后按一次遥控器按键。如果波形显示先是9ms低电平、4.5ms高电平接着是成对的0.56ms低电平和两种不同宽度的高电平并且程序串口输出与波形能一一对应这才算真正验证通过。如果波形对不上问题大概率在外部中断触发条件而不是数据解析部分。验证通过后这份资源就可以直接扩展成一个空调遥控码学习转发器。解码得到的原始码先存到EEPROM里然后通过另一个定时器生成38kHz载波用红外发射管把数据重新发出去。发射端的核心思想是反向操作发送逻辑0时先开载波0.56ms再关载波0.56ms发送逻辑1时先开载波0.56ms再关载波1.69ms。引导码则是9ms开、4.5ms关。用51单片机模拟这个时序完全可行唯一要注意的是开载波期间必须保持输出连续翻转不能用普通延时函数硬撑否则载波频率会漂移。保存码表时我有一个坚持了很长时间的习惯每个按键连续采集20帧打印到PC后再做一次过滤。因为空调遥控器在按键期间会连续重发同一帧如果不去重20帧里可能有5帧是同一码的重复。去重后的码表再按“按键名原始码”的格式存成文本文件后续做协议分析时才不会手忙脚乱。从那以后我每次写完接收程序都强制自己走一遍“逻辑分析仪验证20帧去重”的流程再也不凭感觉判断码对不对。希望帮到你。本文还有配套的精品资源点击获取