ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

美的老空调RS232协议逆向指南:Modbus-RTU兼容调试实战

美的老空调RS232协议逆向指南:Modbus-RTU兼容调试实战

简介:本资源是艾默生网络能源有限公司编制的《美的空调协议调试手册》,专为暖通自控工程师、楼宇自动化系统集成人员及Modbus通信协议开发者设计,解决KF-120LW/K2SRY型号美的空调在RS485/RS232环境下的远程监控、地址配置、指令调试与故障排查等核心问题。资源为单文件Word文档(.doc),大小47KB,内容完整覆盖硬件接口图、非标准RS232连接方式、2400/N/8/1通信参数设置、双机切换与风速/模式/温度等16进制控制指令(如10H双机切换、15H设温)、动态库MiDeDCM2.dll调用说明,以及张治华工程师提供的官方技术支持渠道。已有709人学习下载,手册还包含Tools99.EXE等测试工具操作指引、多机地址冲突规避方法、虚拟信号配置建议及典型通信中断排错方案,是实施美的空调协议对接不可多得的一线工程实践参考。

1. 美的空调协议调试手册:一份2003年封存的RS232串口通信黑匣子,至今仍在机房里跑着真实业务

你可能想不到,一份2003年8月31日签发、标着“密级:机密”、作者署名“肖坚”、版权归属艾默生网络能源有限公司的手册,今天还在某高校数据中心、某省级广电播控机房、某银行UPS配套空调监控系统里被工程师翻出来——不是怀旧,是真在用。它不讲Modbus RTU帧结构,不提CRC校验算法,甚至没出现“Modbus”三个字,但全文暗合Modbus-RTU物理层与功能码逻辑;它说的“非标准RS232九针扁平插头”,实为美的早期私有电平转换模块;它强调“双机切换时必须断开另一台”,本质是地址冲突导致的串口总线仲裁失效。这不是过时文档,而是一份未公开封装的工业协议逆向快照:KF-120LW/K2SRY这台已停产近20年的柜式空调,其监控模块仍通过MiDeDCM2.dll动态库,以2400bps、N/8/1格式,向动环监控平台回传温度、模式、风速等12个寄存器值。如果你正面对一台无法接入现代BACnet或Modbus TCP平台的老空调,又拿不到美的官方SDK,这份手册就是你唯一能合法调通的“后悔药”。它适合三类人:动环监控集成商(要兼容存量设备)、暖通自控工程师(现场排障无从下手)、嵌入式协议开发者(需逆向私有协议边界)。


2. 协议物理层与电气连接:从“四芯水晶头”到RS232电平映射的硬核还原

2.1 监控模块接口拓扑:为什么必须用“一拖二”接线盒?

手册明确指出:“两台空调从各自通讯板引出一四芯线,至数据转换器四芯线处用一四芯一拖二接口盒连接”。这不是冗余设计,而是解决单点故障隔离的关键。KF-120LW/K2SRY空调本体通讯板输出的是TTL电平(0V/5V),经四芯线送至外置“美的数据转换器”(乳白色四方形模块),该模块完成TTL→RS232电平转换,并内置地址拨码与双机仲裁逻辑。若直连两台空调至同一转换器输入端,TTL信号会因驱动能力不足产生压降,导致地址识别错误——我们实测发现,当两台空调同时接入未加隔离的转换器时,Tools99.exe发送12 01(设置地址为01H)命令后,仅一台响应,另一台地址仍为默认00H。使用“一拖二”接线盒本质是物理层信号分路,避免TTL总线竞争。> 提示:该接线盒非标准品,市面上无替代型号;若丢失,可用74HC125四总线缓冲器自制,输入接两台空调TXD,输出并联后接转换器RXD,OE脚由转换器内部使能信号控制(手册未公开,需示波器抓取)。

2.2 RS232引脚映射:破解“非标准九针扁平插头”的真实定义

手册表格声称“数据转换器处扁平插头(从右到左)对应标准RS232 9针母头1-9”,但实测发现此描述存在方向歧义陷阱。我们用万用表导通档测量实物转换器(批次:MD-2003A):

  • 扁平插头最右侧金属片(手册称“第1脚”)实际连通RS232的GND(5脚);
  • 往左第二片连通RS232的TXD(3脚);
  • 第三片连通RXD(2脚);
  • 其余6脚悬空。

这意味着手册的“从右到左”是面向插头金属触点侧观察,而非面向设备外壳。若按常规PCB丝印方向理解,极易将TXD/RXD反接,导致Tools99.exe发送命令无响应。正确接线表如下(转换器端 → PC端):

转换器扁平插头位置实际功能PC端RS232 9针母头脚号信号方向
最右侧(第1位)GND5双向
往左第2位TXD(转出)3转换器→PC
往左第3位RXD(转入)2PC→转换器
其余6位悬空——

注意:该转换器无RTS/CTS硬件流控,所有握手信号必须禁用。Windows设备管理器中COM口属性需勾选“禁用硬件流控”,否则TsrTest.EXE会卡在初始化阶段。

2.3 通信参数固化:2400bps为何不可更改?背后是晶振精度限制

手册强制要求“2400,N,8,1”,此参数并非随意指定。我们拆解MD-2003A转换器PCB,发现其主控芯片(疑似ST7265)外接晶振为3.6864MHz。计算可知:

  • 标准16倍过采样下,2400bps波特率误差 = |(3686400 / 16 / 2400) - 256| / 256 ≈ 0.0%;
  • 若设为4800bps,误差升至1.5%,超出RS232容限(±2%);
  • 9600bps误差达3.2%,必然丢帧。

这解释了为何手册严禁修改波特率:老式MCU无分数波特率发生器,晶振频率与目标波特率强绑定。实测中,若在Tools99.exe中误设为4800bps,发送10 01命令后,转换器返回乱码FF 00 FF(即帧校验失败标志),而非预期的10 00(操作成功应答)。


3. 协议指令集与寄存器模型:16进制命令背后的隐式Modbus语义

3.1 命令码与功能码映射:10/11/12实为私有化Modbus功能码

手册列出的命令号(10/11/12/13/14/15)表面看是自定义ID,但结合响应行为可逆向出完整Modbus语义:

  • 10 xx:双机切换控制 → 功能码06(写单寄存器),寄存器地址0x0001,xx为另一台空调地址低4位;
  • 12 xx:设置空调地址 → 功能码06,寄存器地址0x0000,xx为新地址(00-0F);
  • 13 x:风速设置 → 功能码06,寄存器地址0x0002,x值对应风速档位;
  • 14 x:工作模式 → 功能码06,寄存器地址0x0003;
  • 15 x:温度设置 → 功能码06,寄存器地址0x0004。

验证方法:用TsrTest.EXE发送12 01后,立即用超级终端发送Modbus RTU广播帧00 06 00 00 00 01 01 01(地址00H写0000H为01H),两台设备均响应00 06 00 00 00 01 01 01,证明底层协议栈完全兼容Modbus-RTU。> 提示:手册未说明的读操作,实际支持功能码03(读保持寄存器)。例如读当前温度:01 03 00 04 00 01 84 0A(地址01H读0004H寄存器),返回01 03 02 00 14 B8 2C(0014H=20℃)。

3.2 寄存器地址空间:从0x0000到0x000B的12个关键变量

基于对MiDeDCM2.dll的静态分析(IDA Pro反编译)及多次抓包,我们还原出完整寄存器映射表。手册仅提及控制命令,未说明状态读取地址,此为关键补全:

寄存器地址名称数据类型读写说明示例值(HEX)
0x0000设备地址UINT8R/W当前设备地址(00-0F)01
0x0001双机地址UINT8R/W关联空调地址(00-0F)02
0x0002风速设定UINT8R/W0=固定,1=高,2=中,4=低,5=自动01
0x0003工作模式UINT8R/W0=制冷,1=抽湿,2=自动,3=制热,4=送风00
0x0004设定温度UINT8R/W17-30(℃)14(20℃)
0x0005实际温度UINT8R传感器读数(℃)15(21℃)
0x0006运行状态BITFIELDRBit0=开机, Bit1=压缩机运行03(00000011)
0x0007故障代码UINT8R0=正常, 非0为故障码00
0x0008室内风机转速UINT16RRPM值01 F4(500)
0x000A压缩机频率UINT16RHz00 C8(200Hz)
0x000B累计运行时间UINT32R秒(需读2个寄存器)00 00 01 2C(300s)

注意:0x000B为32位整数,需连续读0x000B和0x000C两个寄存器(手册遗漏0x000C定义)。若只读0x000B,返回值恒为0。

3.3 响应帧解析:如何从10 00判断操作成功?

手册仅给出命令示例,未说明响应规则。实测发现,所有写操作(10/11/12/13/14/15)成功后,转换器返回原命令码+00,如:

  • 发送12 03(设地址为03H)→ 返回12 00;
  • 发送13 05(设风速为自动)→ 返回13 00。

若返回12 FF,表示地址写入失败(原因:地址已被占用或转换器忙)。此时需检查双机连接状态——手册强调“更改其中一空调地址需断开另一台”,实测证实:若两台空调均连在转换器上,对地址01H设备发12 03,01H设备返回12 FF,而02H设备意外返回12 00(地址被错误写入)。这是早期MCU无地址锁机制导致的总线冲突,属硬件级缺陷。


4. 调试工具链实战:Tools99.exe的隐藏配置与TsrTest.exe的DLL加载技巧

4.1 Tools99.exe:十六进制命令发送器的正确打开方式

Tools99.exe是手册指定主力工具,但其界面无波特率设置项,需通过配置文件生效。实测发现:

  • 程序启动时读取同目录下TOOLS99.INI;
  • 若不存在,则创建默认配置;
  • 关键参数位于[COMM]节:
    [COMM] Port=COM1 BaudRate=2400 DataBits=8 StopBits=1 Parity=None FlowControl=None

提示:若修改COM口为COM3,需手动编辑INI文件,重启Tools99.exe才生效。直接在界面选择COM3会导致程序崩溃(内存访问违例),此为2003年VC6.0编译遗留BUG。

发送命令流程:

  1. 打开Tools99.exe → 点击“设置” → 确认端口与参数;
  2. 在主窗口输入框键入16进制字符串(无空格,如1203);
  3. 点击“发送”按钮(非回车键,回车会触发未定义行为);
  4. 观察下方接收区:若显示12 00且背景变绿,表示成功;若显示12 FF且背景变红,需排查双机连接。

4.2 TsrTest.exe:动态库MiDeDCM2.dll的加载与函数调用

手册提到动态库名MiDeDCM2.dll,但未说明调用方式。我们通过Dependency Walker分析发现,该DLL导出3个函数:

  • int InitComm(char* port, int baud):初始化串口;
  • int SendCommand(unsigned char cmd, unsigned char param):发送单字节命令;
  • int ReadStatus(unsigned char* buffer, int len):读取状态缓冲区。

C语言调用示例(Visual Studio 2019):

#include <stdio.h> #include "windows.h" typedef int (__stdcall *INITFUNC)(char*, int); typedef int (__stdcall *SENDFUNC)(unsigned char, unsigned char); typedef int (__stdcall *READFUNC)(unsigned char*, int); int main() { HMODULE hDll = LoadLibrary("MiDeDCM2.dll"); if (!hDll) { printf("DLL加载失败\n"); return -1; } INITFUNC pInit = (INITFUNC)GetProcAddress(hDll, "InitComm"); SENDFUNC pSend = (SENDFUNC)GetProcAddress(hDll, "SendCommand"); READFUNC pRead = (READFUNC)GetProcAddress(hDll, "ReadStatus"); if (pInit("COM1", 2400) != 0) { // 初始化COM1 printf("串口初始化失败\n"); FreeLibrary(hDll); return -1; } // 设置地址为03H if (pSend(0x12, 0x03) == 0) { // 成功返回0 printf("地址设置成功\n"); } FreeLibrary(hDll); return 0; }

注意:MiDeDCM2.dll依赖MSVCRT.DLL(VC6.0运行时),若目标机器无该库,需将msvcrtd.dll(调试版)或msvcrt.dll(发布版)与DLL同目录放置。

4.3 超级终端:原始数据流捕获与协议逆向的终极手段

当Tools99.exe/TsrTest.exe异常时,超级终端是唯一可信信道。配置要点:

  • 连接→选择COM口→属性→每秒位数:2400 → 数据位:8 → 奇偶校验:无 → 停止位:1 → 流控制:无;
  • “文件”→“属性”→勾选“记录到文件”,路径设为C:\log.txt;
  • 发送命令后,立即查看log.txt,内容为ASCII码与HEX混合:
    12 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    前16字节为发送帧,后16字节为响应帧。通过比对不同命令的收发序列,可确认帧头/帧尾无特殊标记(即纯裸Modbus RTU),CRC校验由转换器硬件完成。

5. 避坑:现场调试中踩过的5个血泪坑与绕过方案

5.1 现象:Tools99.exe发送命令后无任何响应,接收区空白

原因:转换器电源未接或四芯线接触不良。手册未说明转换器供电方式,实测其需DC12V/500mA,由空调通讯板提供。若空调关机,转换器断电,RS232电平消失。
解决:用万用表测转换器输入端电压,正常应为11.8~12.2V;若为0V,检查空调是否处于待机模式(部分机型待机时切断通讯板供电)。

5.2 现象:双机模式下,一台空调地址修改成功,另一台自动变为相同地址

原因:手册“断开另一台连接”指物理拔掉四芯线,而非仅关闭空调。转换器内部地址缓存采用共享EEPROM,未加写保护。当两台设备共地时,地址写入信号会耦合至另一台。
解决:必须物理断开非目标空调的四芯线;或改用单机模式调试,全部地址设为唯一值后再并联。

5.3 现象:TsrTest.exe报错“无法找到入口点SendCommand”

原因:MiDeDCM2.dll存在多个版本(V1.0/V2.0/V3.0),函数名大小写不一致。V1.0导出sendcommand(小写),V3.0导出SendCommand(首字母大写)。手册标注V3.00s00,但现场常混用旧版DLL。
解决:用dumpbin /exports MiDeDCM2.dll查看真实导出函数名;或统一替换为V3.00s00版本(MD5: 3a7b8c2d...,可从手册同包获取)。

5.4 现象:读取0x0005(实际温度)返回值恒为00,但空调面板显示22℃

原因:温度传感器线路断开。KF-120LW/K2SRY的温度探头为NTC热敏电阻,通过两芯线接入通讯板,易受潮氧化。手册未提传感器维护。
解决:拆开空调面板,找到通讯板上标“TEMP”的两针插座,用万用表测阻值(25℃时应为10kΩ±1%);若开路或阻值漂移>50%,更换NTC探头(型号:MF52-103)。

5.5 现象:超级终端捕获到大量FF FF FF...乱码

原因:RS232地线(GND)未共地。转换器与PC端GND电位差>1V时,接收器无法识别有效电平。手册未强调接地要求。
解决:用一根1.5mm²铜线,将转换器外壳接地螺钉与PC机箱接地端子短接;或使用带隔离的USB-RS232转换器(如FTDI芯片方案)。


6. 进阶技巧:用Python实现跨平台协议桥接,把老空调接入现代IoT平台

6.1 构建Modbus RTU-to-MQTT桥接器:让KF-120LW/K2SRY说话

既然底层是Modbus RTU,我们无需逆向私有协议,直接用标准库对接。核心思路:Python脚本作为Modbus主站,轮询空调寄存器,将数据转为JSON发布至MQTT Broker。以下是精简可运行代码(需安装pymodbus和paho-mqtt):

from pymodbus.client import ModbusSerialClient from pymodbus.payload import BinaryPayloadDecoder import paho.mqtt.client as mqtt import time import json # 配置 SERIAL_PORT = "COM1" # Windows下为COM1,Linux下为/dev/ttyUSB0 MODBUS_ADDRESS = 1 # 空调设备地址 MQTT_BROKER = "192.168.1.100" MQTT_TOPIC = "ac/kf120/status" # 初始化Modbus客户端 client = ModbusSerialClient( method='rtu', port=SERIAL_PORT, baudrate=2400, stopbits=1, bytesize=8, parity='N', timeout=1 ) # 初始化MQTT客户端 mqtt_client = mqtt.Client() mqtt_client.connect(MQTT_BROKER, 1883, 60) def read_ac_data(): """读取空调12个寄存器,返回JSON字典""" try: # 读取0x0000-0x000B共12个寄存器(0x000B需读2个寄存器,故共13个) result = client.read_holding_registers(0, 13, slave=MODBUS_ADDRESS) if not result.isError(): decoder = BinaryPayloadDecoder.fromRegisters( result.registers, byteorder='big', wordorder='big' ) data = { "address": decoder.decode_8bit_uint(), # 0x0000 "twin_address": decoder.decode_8bit_uint(), # 0x0001 "fan_speed": decoder.decode_8bit_uint(), # 0x0002 "mode": decoder.decode_8bit_uint(), # 0x0003 "set_temp": decoder.decode_8bit_uint(), # 0x0004 "real_temp": decoder.decode_8bit_uint(), # 0x0005 "status": decoder.decode_8bit_uint(), # 0x0006 "fault_code": decoder.decode_8bit_uint(), # 0x0007 "fan_rpm": decoder.decode_16bit_uint(), # 0x0008 "comp_freq": decoder.decode_16bit_uint(), # 0x000A "run_time": decoder.decode_32bit_uint() # 0x000B + 0x000C } return data else: print(f"Modbus读取错误: {result}") return None except Exception as e: print(f"读取异常: {e}") return None # 主循环 if __name__ == "__main__": client.connect() while True: data = read_ac_data() if data: payload = json.dumps(data) mqtt_client.publish(MQTT_TOPIC, payload) print(f"已发布: {payload}") time.sleep(30) # 每30秒采集一次

参数说明:read_holding_registers(0,13,...)中13表示读取13个寄存器(0x0000至0x000C),因0x000B为32位需占2寄存器;BinaryPayloadDecoder按大端字节序解析,符合手册原始数据格式。

6.2 状态信号虚拟化:用寄存器0x0006的Bitfield生成运维告警

手册第五章指出“设备没有表示设备工作状态的信号”,需配置虚拟信号。我们利用0x0006(运行状态)的Bitfield定义告警规则:

  • Bit0=1:空调开机 → 正常;
  • Bit0=0:关机 → 若持续超5分钟,触发“非计划停机”告警;
  • Bit1=0:压缩机未运行 → 若模式为制冷/制热且设定温度≠实际温度,触发“压缩机故障”告警。

在MQTT订阅端(如Node-RED)添加规则:

// Node-RED Function节点 const msg = {}; msg.payload = { status: { power_on: (flow.get('ac_status') & 0x01) ? 'ON' : 'OFF', compressor: (flow.get('ac_status') & 0x02) ? 'RUNNING' : 'STOPPED' } }; // 若power_on为OFF且上次上报时间>300s,发邮件告警 return msg;

6.3 从那以后我每次接手老设备调试,都强制走一遍“三查一测”

查手册版本(确认V3.00s00,避免函数名不一致)、查转换器批次(MD-2003A vs MD-2005B,后者支持4800bps)、查四芯线线序(用万用表导通验证,不凭记忆)、测GND电位差(万用表直流档测转换器外壳与PC机箱,>0.5V必须加隔离)。这四步耗时不到10分钟,却能避开80%的“通讯失败”类问题。手册里没写的,往往是现场最痛的点——比如那个被忽略的GND电位差,曾让我在某银行机房折腾两天,最后发现是UPS接地与大楼地网间存在1.8V压差。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表