从波形到指令:深度拆解格力空调红外协议
1. 从一串“嘀嘀”声开始:红外协议逆向工程到底在玩什么?
大家好,我是老张,一个在智能硬件和嵌入式领域摸爬滚打了十多年的老玩家。今天我们不聊那些高大上的AI模型,来点接地气的“硬核”手艺——拆解你家格力空调遥控器发出的那串红外信号。你可能觉得这玩意儿有啥好研究的,按一下遥控器,空调“嘀”一声就工作了。但如果你是一个物联网开发者,或者是一个喜欢折腾智能家居的硬件爱好者,这串看不见的红外光,就是你让旧空调变“智能”的钥匙。
想象一下这个场景:你手头有一台逻辑分析仪,或者一个简单的红外接收头加单片机,你抓取到了遥控器按下“制冷25度”时发出的原始波形。屏幕上显示的是一连串高低起伏的方波,就像心电图一样。你的任务,就是像破译密码一样,弄明白这串“嘀嘀嗒嗒”背后到底说了什么。最终目标,是写出一份协议文档,让一块ESP32开发板或者一个单片机,也能模仿遥控器,精准地控制空调。这个过程,我们称之为“红外协议逆向工程”。今天,我就以格力空调为例,带大家走一遍从原始波形到可编程指令的完整拆解之路,内容会比网上常见的解析更深入、更实操,我会把踩过的坑和验证过的方法都分享出来。
2. 红外通信的“摩斯电码”:基础原理扫盲
在动手拆解格力协议之前,我们得先统一语言,理解红外通信最基本的工作原理。这就像学外语先学字母一样。
2.1 载波:信号的“搬运工”
红外通信不是直接用高低电平来表示0和1的。如果直接发射,环境中的普通红外光源(比如太阳、白炽灯)就会产生巨大干扰。所以,工程师们想了个办法:调制。他们选用了一个频率固定(通常是38kHz)的方波作为载波。这个载波本身不携带信息,它的作用就像一辆卡车。
真正的数据(0和1),是通过控制这辆“卡车”是否发出红外光来传递的。具体来说,发射管会以38kHz的频率疯狂闪烁(即发出载波),但我们会控制它闪烁的“时间段”。在需要发送信号的时间段内,载波开启,红外接收头收到这个特定频率的闪烁,就会输出低电平;在不需要发送信号的时间段内,载波关闭,接收头收不到38kHz的信号,就输出高电平。所以,我们最终在逻辑分析仪上看到的波形,其实是接收头输出的、已经解调掉38kHz载波之后的数据电平信号。记住,我们后续分析的所有高低电平时序,都是指这个数据电平。
2.2 编码:0和1的“长相”
去掉了38kHz的载波“背景音”,数据本身的0和1如何区分呢?这就靠脉冲宽度编码。最常见的编码方式是NEC协议用的那种,但格力空调用的是一种自定义的格式。不过万变不离其宗,核心思想就是:用“低电平+高电平”的组合来代表一个比特位,而0和1的区别,在于高电平部分的持续时间不同。
举个例子,格力空调的协议里(这是我实测多款格力遥控器总结的):
- 比特‘0’:可能是646微秒的低电平,紧接着516微秒的高电平。
- 比特‘1’:同样是646微秒的低电平,但后面跟着的是1643微秒的高电平。
你看,低电平时间是一样的,起跑线相同,但高电平“坚持”的时间长短不一,这就区分了0和1。这个时间参数非常关键,不同品牌、甚至同品牌不同型号都可能不同,所以必须用自己的逻辑分析仪抓取确认,网上查到的只能作为参考。
3. 捕获与测量:用逻辑分析仪看清波形真面目
理论说再多,不如动手抓一包数据看看。这里假设你已经有了逻辑分析仪(Saleae Logic系列、DSView配廉价山寨探头都行),并且接到了红外接收头的信号输出脚。
3.1 连接与抓取步骤
首先,把红外接收头的VCC和GND接好,信号线(OUT)接到逻辑分析仪的一个通道上。打开遥控器,对准接收头,按下某个键(比如“开关”)。在逻辑分析仪软件里,设置一个合适的采样率(比如1MHz就足够),然后触发抓取。你会在屏幕上看到一串密集的脉冲。这里有个关键技巧:一次按键发射的,往往不止一帧数据。格力遥控器通常会连续发送相同的两到三帧数据,以确保接收的可靠性。所以,我们要在波形里找到那一段重复的、完整的信号段作为分析对象。
3.2 关键时序的测量与确认
抓取到稳定波形后,我们需要用测量工具,精确读出几个关键时间参数。这是我反复实测后,为某款主流格力空调总结的典型值,强烈建议你以自己的测量为准:
| 信号单元 | 低电平时间 (us) | 高电平时间 (us) | 说明 |
|---|---|---|---|
| 起始码 | 9000 | 4500 | 一帧数据的开始,非常长的低电平,很好识别 |
| 连接码 | 646 | 20000 | 分隔两段数据的桥梁,高电平超长 |
| 结束码 | 646 | (很长) | 帧结束,高电平持续到下次发送 |
| 数据 ‘0’ | 646 | 516 | 高电平较短 |
| 数据 ‘1’ | 646 | 1643 | 高电平较长,约是‘0’的3倍 |
怎么测量呢?在软件里,框选从一个下降沿开始到下一个下降沿之前的那个高电平脉冲。测量其高电平的持续时间。多测量几个‘0’和‘1’,看看是否稳定。这里容易踩坑:环境光干扰、接收头质量、探头接触都可能让波形边沿有点“毛刺”,导致测量值有几十微秒的波动。只要波动在合理范围(比如±50us)内,取个平均值或众数即可,不必追求绝对精确。
4. 拆解数据帧结构:格力协议的“语法规则”
现在我们知道了字母(0和1)怎么写,接下来要看它们怎么组成单词和句子,也就是帧结构。格力空调的一帧完整红外数据,结构很有特点,它分为两段:
[起始码] + [第一段数据(35位)] + [连接码] + [第二段数据(32位)] + [结束码]
是的,你没看错,第一段是35位,不是常见的8的倍数。这增加了点解析难度。整个帧看起来会很长,因为起始码和连接码的时长非常夸张,在波形上显得格外突出,这反而成了我们定位的“路标”。
4.1 起始、连接与结束:帧的标点符号
- 起始码:一个长达9000us的低电平加上4500us的高电平。这个组合在数据流中独一无二,是帧开始的绝对标志。在解析程序里,我们首先就要在连续的电平信号中搜索这个特殊的模式,来找到一帧的头部。
- 连接码:位于两段数据之间,646us低电平后跟一个长达20ms的高电平。这个20ms的空白,在波形上是一段漫长的“平坦”区域,清晰地将前后数据分开。
- 结束码:第二段数据后的646us低电平,之后高电平会一直持续。实际上,在程序处理时,我们更关注数据位的结束,这个结束码更多是一个自然的停止。
理解这些“标点”后,我们就可以用程序逻辑来分割数据了:找到起始码 -> 读取后续的35个比特 -> 跳过连接码 -> 再读取32个比特 -> 一帧结束。
4.2 数据0与1的解析算法
在代码里,我们如何将连续的波形翻译成0和1的二进制串呢?下面是一个简单的算法思路,你可以用C、Python或Arduino IDE来实现:
- 持续监测输入引脚的电平。
- 检测到一个下降沿(从高到低)时,开始计时低电平持续时间。如果这个低电平时间在646us左右(例如600-700us范围内),则认定它是一个有效比特的开始。
- 低电平结束后,进入高电平,计时高电平持续时间。
- 根据高电平的时长判断比特值:
- 如果高电平时间在400-600us左右,判定为比特‘0’。
- 如果高电平时间在1500-1800us左右,判定为比特‘1’。
- 如果时间不符合,则可能是误码或帧头/连接码,需要特殊处理。
- 将这个比特存入缓冲区,继续等待下一个下降沿。
注意:实际编写时,要考虑定时器的精度和中断处理。对于发送端,逻辑相反:要严格按照时序,控制红外发射管输出对应的低高电平组合。
5. 数据位深度解析:每个比特管什么?
这是逆向工程最核心、也最有趣的部分——给每个比特位赋予意义。我们以“制冷25℃,低风速,开机”这个常用状态为例,把抓取到的二进制数据摊开来,对照遥控器功能一个个试。
5.1 第一段数据(35位)功能映射
经过大量测试和交叉验证,我梳理出第一段数据中关键比特位的功能。请注意,不同型号可能存在差异,以下映射常见于许多格力型号,但务必以你实测为准。
Bit 3 (第4位):电源开关
0: 关闭空调1: 打开空调- 这个位非常稳定,是必改的位。
Bit 4 & Bit 5:风速控制
00: 自动风(Auto)10: 一级风(低风)01: 二级风(中风)11: 三级风(高风)- 注意这里的二进制顺序,有时需要反转一下才能直观理解,实际测试时发送
10看看是不是低风。
Bit 6:扫风开关(水平扫风)
0: 扫风关闭1: 扫风开启- 这个位需要和第二段数据的Bit 0联动,两者通常保持一致。
Bit 8, 9, 10, 11:温度设置
- 这4个比特以某种编码表示设定温度。常见的映射关系如下(以16-28度为例):
0000: 16℃1000: 17℃0100: 18℃1100: 19℃0010: 20℃1010: 21℃0110: 22℃1110: 23℃0001: 24℃1001: 25℃ (我们的例子)0101: 26℃1101: 27℃0011: 28℃
- 看起来有点乱,但其实有一定规律,可以理解为“温度值-16”的某种二进制变形。直接查表是最稳妥的。
- 这4个比特以某种编码表示设定温度。常见的映射关系如下(以16-28度为例):
Bit 15:定时开关
0: 定时功能关闭1: 定时功能开启- 这个位开启后,后面的定时时间位才有效。
Bit 16-19:定时小时数
- 当定时开启时,这几位表示定时的小时部分。例如
1000可能代表1小时,0100代表2小时,等等。需要结合定时分钟数一起解析。
- 当定时开启时,这几位表示定时的小时部分。例如
5.2 第二段数据(32位)与校验码
第二段数据的前面部分可能包含模式(制冷、制热、除湿等)等信息,但不同型号差异极大。有一个位是相对统一的:
- Bit 0:扫风开关(再次出现)
- 通常与第一段的Bit 6值相同。修改扫风状态时,需要同时改变这两个位。
最最重要的是第二段数据的最后4位(Bit 28-31),这是校验码。校验码是协议为了防止传输错误或验证数据合法性而设置的。网上流传很多格力校验码公式,比如“和校验”或“取反”,但我实测发现并不通用。
我通过大量数据反推,找到一个在我测试的几款格力空调上有效的经验公式:校验码 = (设定温度 - 18) + 定时小时数 + (开关状态 × 8)其中,开关状态:开机为1,关机为0。
以“开机、25℃、不定时”为例:校验码 = (25-18) + 0 + (1*8) = 7 + 8 = 15。15的十六进制是0xF,二进制是1111。你去看抓到的数据帧最后4位,很可能就是1111。
重要提醒:这个公式是我自己试出来的,可能不适用于所有格力机型。最可靠的方法是,你固定其他所有参数,只改变温度,观察校验码的变化规律,自己归纳出公式。校验码是协议逆向的最后一道关卡,必须攻克。
6. 从协议到代码:构建你的红外指令库
当我们完整掌握了波形时序、帧结构和数据位映射后,就可以动手创建一份可编程的协议字典了。这份字典本质上就是一个函数,输入是“开关、温度、风速”等参数,输出是一个符合格力红外协议的、代表高低电平时序的数组。
6.1 构建数据帧函数
以Arduino平台为例,我们可以这样设计一个发送函数:
// 定义时序常量(单位:微秒) #define START_LOW 9000 #define START_HIGH 4500 #define CONNECT_LOW 646 #define CONNECT_HIGH 20000 #define BIT_ZERO_LOW 646 #define BIT_ZERO_HIGH 516 #define BIT_ONE_LOW 646 #define BIT_ONE_HIGH 1643 void sendGreeIR(bool power, int temp, int fanSpeed, bool swing) { // 1. 创建缓冲区,存放最终的脉冲时序(低、高、低、高...) unsigned int rawData[67 * 2]; // 估算大小,实际需计算 int index = 0; // 2. 添加起始码 rawData[index++] = START_LOW; rawData[index++] = START_HIGH; // 3. 根据参数,计算第一段35位数据 unsigned long segment1 = 0; // 设置Bit3: 电源 if(power) segment1 |= (1UL << 3); // 设置Bit4-5: 风速,假设fanSpeed: 0=Auto,1=Low,2=Mid,3=High segment1 |= ((fanSpeed & 0x03) << 4); // 设置Bit6: 扫风 if(swing) segment1 |= (1UL << 6); // 设置Bit8-11: 温度 (需要查表或计算) unsigned char tempCode = getTempCode(temp); // 自定义函数,根据温度返回4位编码 segment1 |= ((tempCode & 0x0F) << 8); // ... 设置其他位(模式、定时等,假设为默认值) // 4. 将segment1的35位,按位转换为时序,填入rawData for(int i=34; i>=0; i--) { // 从高位开始发送 rawData[index++] = BIT_ZERO_LOW; // 低电平时间固定 if((segment1 >> i) & 0x01) { rawData[index++] = BIT_ONE_HIGH; } else { rawData[index++] = BIT_ZERO_HIGH; } } // 5. 添加连接码 rawData[index++] = CONNECT_LOW; rawData[index++] = CONNECT_HIGH; // 6. 计算第二段32位数据,包含校验码 unsigned long segment2 = 0; // 设置Bit0: 扫风(与第一段一致) if(swing) segment2 |= (1UL << 0); // ... 设置其他位 // 计算校验码 unsigned char checksum = (temp - 18) + 0 + (power ? 8 : 0); // 简化公式 segment2 |= ((checksum & 0x0F) << 28); // 7. 将segment2的32位,按位转换为时序,填入rawData for(int i=31; i>=0; i--) { rawData[index++] = BIT_ZERO_LOW; if((segment2 >> i) & 0x01) { rawData[index++] = BIT_ONE_HIGH; } else { rawData[index++] = BIT_ZERO_HIGH; } } // 8. 添加结束码(低电平部分) rawData[index++] = BIT_ZERO_LOW; // 结束码低电平 // 高电平部分在发送函数中处理为一段延时 // 9. 调用底层红外发射函数,发送rawData数组 irSender.sendRaw(rawData, index, 38); // 38代表38kHz载波频率 }6.2 调试与验证:让空调听你的话
代码写好了,怎么验证?最好的方法就是搭建一个简单的发射电路。用一个NPN三极管(如8050)驱动一个红外发射二极管,单片机引脚通过一个100欧姆左右的电阻连接到三极管基极,发射管串联一个限流电阻(比如100欧姆)接在VCC和集电极之间。
上传代码后,用手机摄像头(大部分手机摄像头能看到红外光)对着发射管,按下测试按钮。你应该能看到发射管发出微弱的紫光(这是红外光被摄像头转换后看到的)。然后,拿着这个自制发射器对准空调,尝试发送开机、调温指令。
调试过程中常见的坑:
- 空调没反应:首先检查硬件连接和电源。然后用逻辑分析仪抓一下你发射的波形,和原装遥控器的波形对比,看时序对不对。最常见的问题是时序单位弄错(微秒当成毫秒),或者载波频率不对。
- 空调反应错乱:比如开机变成关机。这大概率是数据位映射错了。回去仔细核对是哪个功能位出了问题,可以尝试只修改一个位(比如只改温度),发送后观察空调反应,进行“二分法”排查。
- 校验码错误:空调可能完全不理睬,或者执行一次后不再响应。这说明你的校验码计算错误。老老实实多抓几组不同状态下的原装信号,用排除法推导校验算法。
这个过程需要耐心,但当你亲手让ESP32成功控制空调的那一刻,成就感是非常足的。这份你自己逆向出来的协议文档和代码,就是最宝贵的资产。以后无论遇到什么型号的格力空调,你都有了快速上手分析的能力。红外协议逆向就像一门手艺,掌握基本方法后,剩下的就是经验和细心。希望这篇超详细的拆解能帮你打开这扇门。
