EMS 知识库
首页 / knowledge/故障速查

现场排查 SOP

场景:现场设备报故障,手上只有一台设备、一个报错信息,怎么往下查。 需求号:REQ-001(K7.8)| 源码基准:V0.11.676(git 04e72700)| 更新日期:2026-10-15


第 0 步:先分清「谁在报」

这是最容易走错的一步。 同一个现场现象,可能是 EMS 主控自己判定的,也可能是逆变器/BMS 上报后主控转发的,排查方向完全不同。

用故障码的数值直接判断:

码值区间谁报的排查重心
E0001 ~ E0094EMS 主控自己主控与各设备的通信链路、主控外设(Flash/RTC/EEPROM)
E100000 起 / W110000 起逆变器逆变器本体;主控只是转发
E200000 起充电桩充电桩本体
E60000 / W70000 / P75000 起BMS 电池电池本体 + 电池 CAN

完整 336 条见 故障码总表.md。

⚠️ EMS 的 30 个故障位里有 7 个是死位(在现版本固件里全工程零引用,永远不会被置起): E0001 E0008 E0009 E0012 E0013 E0015 E0016。 如果现场看到这 7 个码,说明固件版本与本知识库不一致(或看到的是别的系统的码), 不要在现有代码里找它 —— 见 EMS故障分册.md 的「死位」一节。


第 1 步:拿到完整故障位图(不要只盯一个码)

EMS 的故障是一个 32 位位图(data/data.h 的 EMS_FAULT_U / g_t_ems_info), 多个故障会同时置位。只拿一个码去查,常会漏掉根因(例如「逆变器丢了」会连带触发一串)。

通路 A:Modbus 寄存器(最可靠,推荐)

读两个保持寄存器,拼成 32 位:

寄存器含义
0x0701故障位图 高 16 位(bit16 ~ bit31)
0x0702故障位图 低 16 位(bit0 ~ bit15)

拼法:u32 = (reg_0x0701 << 16) | reg_0x0702

然后用 故障码表.tsv 把置 1 的 bit 反查成故障码(EMS 组 bit 与码一一对应,bit N ↔ E000(N+1),见分册)。

这两个地址来自 midware/interface/modbus/modbus.h 的 REG_EMS_FAULT_H_HHE_E / _L_HHE_E, 由枚举 REG_EMS_INFO_START_HHE_E = 0x06FB 起递增推算得出 (0x06FB 起第 6/7 项;固件在 0x0711 之后跳空 24 个寄存器到 0x0729,属预留)。 代码里的读取实现在 midware/interface/modbus/modbus.c:3060(高字)与 :3064(低字)。

通路 B:设备屏幕的历史故障日志

主控会把每次故障发生和恢复都写进 Flash 故障日志,屏幕上能翻历史记录。

读懂屏幕上那行字(hmi/ui/ui_history_error_log.c:139-165 的 disp_error_describe()):

屏幕上显示含义
E00007 NO. 1故障正在发生(FAULT_OCCURRED)
NO E00007 NO. 1该故障已恢复(FAULT_CLEAR)—— 注意行首的 NO
W70001 NO. 2告警正在发生
NO W70001 NO. 2告警已恢复

两个必须知道的点:

  1. 屏幕上是 5 位数字:故障码 7 显示成 E00007,不是本库写的 E0007。

看到屏幕上 E00007,对应本库的 E0007(去掉补零)。

  1. 行首有没有 NO 决定这条是"当前存在"还是"曾经发生又恢复了"。

排查时只看没有 NO 的行才是当前故障;带 NO 的说明拍下那一刻已经不在故障态了。

⚠️ NO. 后面那个数字不要当成设备号。 实测(data/fault_warn_log/fault_warn_log.c:556/:560): EMS 自身故障写日志时把设备号硬编码成 1,所以 E00007 NO. 1 里的 1 没有任何设备含义。 只有 BMS 上报的码(E6xxxx/W7xxxx/P75xxx)才会按电池序号变化 —— 那里传的是循环下标 i(fault_warn_log.c:603),多机场景下才真的用来区分是哪一台电池。

⚠️ 界面代码 hmi/ui/(77 个文件)尚未纳入本知识库,所以这里给不出"按哪几个键、在第几级菜单"翻到这页。 这是 K4 批次的缺口。但上面这个显示格式已从源码原文核实(hmi/ui/ui_history_error_log.c:139)。

通路 C:云端 / 手机 APP

主控会把故障日志打包上传服务器: net/ems.c:5160 判断发送标志(带 10 秒节流 SENCOND_10S), :6091 通过 handle_message_queue(STATUS_SEND_ERROR_LOG, ...) 投递,单包上限 5 KB。 云端记录更全,但有延迟,不适合现场即时判断。


第 2 步:故障日志里到底存了什么

每条日志记录的结构(data/fault_warn_log/fault_warn_log.h:643):

字段含义
Time故障发生的 RTC 时间
u8_device_type设备类型:ems / inv / bms
u8_fault_or_warn是故障还是告警(255 表示空槽)
u8_fault_location电池簇号
u8_fault_id电池 id,或逆变器通讯地址
u32_fault_code故障码(就是 E/W/P 那个值)
s32_fault_value故障发生那一刻的数值快照 ← 排查时最有用
u16_crc_codeModbus CRC16 校验

s32_fault_value 是关键:很多故障是带阈值判定的,知道当时的值就能立刻判断是"真越限"还是"误判"。

存储参数(fault_warn_log.h:32-41):

项值
日志 Flash 数据区0xF66000 ~ 0xFC8FFF(396 KB)
地址结构 3 个 4K 备份区0xF63000 / 0xF64000 / 0xF65000
内存缓存上限256 条
屏幕历史查询缓存50 条

三个备份区轮流写、带 CRC 校验,掉电时正在写的那条可能损坏,但不会丢整片日志。 所以偶发「日志少一条」通常不是 bug。


第 3 步:USMART 串口调试命令 —— 量产固件里用不了

midware/debug/shell_usmart 提供了一套串口调试命令(读内存、改寄存器、导数据等), 但它受编译开关门控:

// midware/debug/shell_usmart.h:13
#define DEBUG_SUMART                 0

DEBUG_SUMART = 0 ⇒ task_usmart 任务在 user/main.c:321 处整段不编译,量产固件里没有任何 USMART 命令可用。

如果你在别人给的排查文档里看到「输入 xxx 命令导出日志」,那是开发固件的做法,现场量产机做不到。 要向研发要一份 DEBUG_SUMART=1 的固件才能用。


第 4 步:按模块往下走

拿到故障码后,去 EMS故障分册.md 查该码的:

  1. 触发条件 —— 什么判据会置位(哪个标志、哪个阈值、有无消抖)
  2. 置位函数 —— 文件:行,可直接调代码
  3. 上游来源 —— 那个标志本身是谁在什么条件下置的(根因往往在这里)
  4. 排查方向 —— 现场先量什么、先看什么

非 EMS 的码(逆变器/BMS/充电桩)本库只给到「主控怎么映射」,具体含义要看厂商协议文档。


第 5 步:查代码

cd docs/knowledge/代码解析/索引
node 工具/查找.js -F 逆变器通信丢失    # 功能 → 函数 + 行号
node 工具/查找.js -d 故障码            # 全文搜整个知识库
node 工具/查找.js -f bat_heat          # 只查函数名

附:本文事实的出处

本文所有结论都来自源码,未经板级验证:

结论出处
30 个故障位、位域名data/data.h:27-114
故障码 ↔ 位 对应表data/fault_warn_log/fault_warn_log.c:518-549
E/W/P 码的数值data/fault_warn_log/fault_warn_log.h:76-...(枚举)
7 个死位全工程 grep ems_set_fault_bit 与位域名,零引用
Modbus 故障字寄存器midware/interface/modbus/modbus.c:3060,3064;modbus.h:651-659
日志记录结构data/fault_warn_log/fault_warn_log.h:643-654
日志 Flash 布局data/fault_warn_log/fault_warn_log.h:32-41
DEBUG_SUMART = 0midware/debug/shell_usmart.h:13;user/main.c:321
故障日志上传路径net/ems.c:5160(发送标志 + 10 s 节流)、:6091(投递)
屏幕故障行格式 E%05u / NO 前缀hmi/ui/ui_history_error_log.c:139-165(disp_error_describe())