EMS 知识库
首页 / knowledge/代码解析/midware/meter

diesel_generator.c

源文件路径:midware/meter/diesel_generator.c 所属模块:midware/meter(电表/油机中间件) 代码行数:337

1. 文件职责

本文件实现油机(柴油发电机组)控制器的 Modbus RTU 数据轮询。油机控制器(Genset Controller,从站地址 99)挂在与电表同一条 RS485 总线上(由 usart1 承载),EMS 通过标准 Modbus 功能码 0x03(读保持寄存器)周期性读取四段寄存器:Page 4 基础仪表、Page 5 扩展仪表、Page 7 累积仪表、Page 154 告警状态。

数据流向:genset_comm_handle() 被 midware/meter/meter.c:5876 的 task_meter() 循环每轮调用一次,函数内部按 500ms 节拍轮流发起一次查询,把响应解析进全局表 g_t_diesel_gen;net/ems.c 的 STATUS_SEND_GENSET_DATA 报文(net/ems.c:1585-1617)再把这些字段上报云端。

本文件只有采集和"通讯丢失清零"两类职责,不参与任何能量调度决策——全工程检索表明 g_t_diesel_gen 只被 net/ems.c 读取用于上报,logic/ 层不引用其数据。油机的启停/带载由硬件或人工控制,EMS 侧只做监视。

2. 关键数据结构与宏

名称类型说明
GENSET_DEBUG_INFO宏(diesel_generator.h:20)0,关闭 printf 调试打印
GENSET_ADDR宏99,油机控制器的 Modbus 从站地址(GSM/GC 控制器常用的固定地址 0x63)
DG_PAGE_4_A0_33_REG_ADDR宏4*256 = 1024(0x0400),Page 4 起始寄存器号;命名含义为"页 4 的 A0 号寄存器",即用寄存器号高字节编码页号
DG_PAGE_5_A10_11_REG_ADDR宏5*256+10 = 1290(0x050A),Page 5 的 A10 号寄存器
DG_PAGE_7_A6_9_REG_ADDR宏7*256+6 = 1798(0x0706),Page 7 的 A6 号寄存器
DG_PAGE_154_ALARM_REG_ADDR宏154*256 = 39424(0x9A00),Page 154 告警页起始寄存器
DG_PAGE_4_A0_33_REG_NUM宏34,Page 4 一次读 34 个寄存器(A0–A33)
DG_PAGE_5_A10_11_REG_NUM宏2,Page 5 一次读 2 个寄存器(A10–A11)
DG_PAGE_7_A6_9_REG_NUM宏4,Page 7 一次读 4 个寄存器(A6–A9)
DG_PAGE_154_ALARM_REG_NUM宏9,Page 154 一次读 9 个寄存器
DG_QUERY_SPEED_VALUE宏500,查询节拍 500ms(两个不同页之间至少间隔 500ms)
DG_COMM_LOST_VALUE宏1000*15 = 15000ms,15 秒内无任何有效响应即判定通讯丢失
Query_DG_E枚举查询页顺序:PAGE_4_BASIC_E=1、PAGE_5_EXTEND_E=2、PAGE_7_ACCUMULATED_E=3、PAGE_154_ALARM_E=4、PAGE_N_MAX_E=5(同时也是"回到起点"的哨兵值)
GENSET_RUN_TABLE_T结构体油机运行数据表,见下节
GENSET_CONTROL_CMD_TABLE_T结构体控制/节拍表:u32_get_data_last_time(最近一次有效响应时刻)、u32_dg_query_tick(上一次发起查询的时刻)
DIESEL_GENERATOR_TABLE_T结构体汇总表:t_dg_run_data + t_dg_ctrl_cmd,全局实例 g_t_diesel_gen
METER_READ_FUN_CODE宏(meter.h:47)0x03,Modbus 读保持寄存器功能码

GENSET_RUN_TABLE_T 字段(diesel_generator.h:50)

字段类型单位/来源页说明
u16_dg_fuel_levelu161%(Page 4)燃油液位
u16_dg_engine_bat_voltu160.1V(Page 4)发动机启动电池电压
u32_dg_fuel_consumptionu320.01 L/h(Page 5 A10–A11)燃油消耗率(32bit 跨 2 寄存器)
u32_dg_volt_a/b/cu320.1V(Page 4)三相电压,每相跨 2 个寄存器
u32_dg_cur_a/b/cu320.1A(Page 4)三相电流
s32_dg_actpower_a/b/cs321W(Page 4)三相有功功率,有符号(发电为正、反相为负)
u32_dg_positive_energyu320.1kWh(Page 7 A6–A9)正向累积发电量
u16_dg_frequ160.1Hz(Page 4)发电机频率
u32_dg_engine_run_timeu321s(Page 7)发动机累计运行时间
u16_alarm[8]u16[8]位域(Page 154)8 个 16bit 告警字,展开成 128 个告警位上报云端

3. 函数清单

函数名作用域说明
ems_query_genset_page4_commandstatic组帧并发送 Page 4(A0–A33,34 寄存器)读请求,解析 73 字节响应中的电压/电流/功率/频率/油位/电池电压
ems_query_genset_page5_commandstatic组帧并发送 Page 5(A10–A11)读请求,解析燃油消耗率
ems_query_genset_page7_commandstatic组帧并发送 Page 7(A6–A9)读请求,解析累计运行时间与正向发电量
ems_query_genset_page154_commandstatic组帧并发送 Page 154(9 寄存器)读请求,解析 8 个告警字
send_query_genset_commandstatic页轮询调度器:用静态游标依次切换 4 个页并调用对应查询函数
genset_run_data_clearstatic把 t_dg_run_data 全部清零,告警字填 4369(0x1111) 表示无效
genset_comm_handle全局对外入口:判断使能、按 500ms 节拍发起轮询、15s 无响应时清数据

本文件另在 :25-30 有 6 行前置声明(4 个页查询 + send_query_genset_command + genset_run_data_clear)。

4. 核心逻辑解析

4.1 调度与状态机(genset_comm_handle(),:296)

每轮 task_meter() 调用一次
      │
      ├─ genset_enable_flag == FALSE ──► u32_get_data_last_time = now
      │                                  (若此前在通讯中,调 genset_run_data_clear())
      │
      └─ TRUE
           ├─ (now - u32_dg_query_tick) >= 500ms ──► tick = now; send_query_genset_command(); s_genset_comm = TRUE
           │   (若 tick > now,说明 u32 节拍回绕 → tick = now)
           │
           └─ (now - u32_get_data_last_time) >= 15000ms ──► last_time = now
                                                            若 s_genset_comm == TRUE → 清数据并置 FALSE

要点:

4.2 帧格式(Modbus RTU,四个查询函数结构相同)

请求帧(8 字节)

偏移内容
0GENSET_ADDR = 0x63 (99)
1METER_READ_FUN_CODE = 0x03
2–3起始寄存器号,大端
4–5寄存器数量,大端
6–7CRC-16/Modbus,低字节在前(:54-55 明确注释"高位在后面")

组帧实现:先在 6 字节的 query_genset_cmd[6] 里拼载荷,memcpy 到 l_u8_tx_buf[8],再 GetCrcCode_Modbus(l_u8_tx_buf, 6) 追加 CRC。

响应帧(标准 Modbus 0x03 响应)

偏移内容
0从站地址
1功能码 0x03
2字节数 = 寄存器数 × 2
3..寄存器数据,每寄存器 大端 2 字节
末尾 2 字节CRC

响应有效性判定(四个函数一致,以 Page 4 为例,:59):

if ((l_u16_recv_num > 0) && (g_u8_recv_buf[2] == DG_PAGE_4_A0_33_REG_NUM*2))

即仅校验"收到了数据"且"字节数字段等于期望值",不校验从站地址、功能码,也不校验帧总长。CRC 校验在 meter_recv_data()(meter.c:66)内部完成。

四个页的请求参数与解析偏移:

页起始寄存器数量期望字节数响应总长解析字段(缓冲区下标,数据区自 buf[3] 起)
Page 40x0400346873油位 [9..10](寄存器 A3)、电池电压 [13..14](A5)、频率 [17..18](A7)、Ua [19..22](A8-9)、Ub [23..26](A10-11)、Uc [27..30](A12-13)、Ia [43..46](A20-21)、Ib [47..50](A22-23)、Ic [51..54](A24-25)、Pa [59..62](A28-29)、Pb [63..66](A30-31)、Pc [67..70](A32-33)
Page 50x050A249燃油消耗率 [3..6](A10–A11 拼成 32bit)
Page 70x07064813累计运行时间 [3..6](A6-7)、正向发电量 [7..10](A8-9)
Page 1540x9A0091823告警字 alarm[0..7] 取 [5..20],即跳过第一个寄存器(A0),使用 A1–A8

所有 32bit 量的拼接方式统一为 (b0<<24)|(b1<<16)|(b2<<8)|b3(大端),存进 u32;有功功率额外做 (s32) 强转保留符号。

4.3 页轮询(send_query_genset_command(),:228)

static u8 g_u8_query_dg_cmd = PAGE_N_MAX_E;   // 静态游标,初值 5
if (g_u8_query_dg_cmd >= PAGE_N_MAX_E)  g_u8_query_dg_cmd = PAGE_4_BASIC_E;  // 回到第 1 页
else                                    g_u8_query_dg_cmd++;
switch (g_u8_query_dg_cmd) { case 1..4: 调对应页查询; default: 游标复位; }

4.4 数据清零(genset_run_data_clear(),:267)

逐字段赋值 0,最后把 8 个告警字都置为 4369:

for (u8 i = 0; i < 8; i++) g_t_diesel_gen.t_dg_run_data.u16_alarm[i] = 4369;

4369 = 0x1111,是"告警数据无效"的魔数标记(云端借此区分"确实无告警(0)"与"通讯丢失(0x1111)")。函数不清 t_dg_ctrl_cmd(节拍字段由调用方维护)。

4.5 与 uCOS-II 的配合

4.6 关键全局变量的读写

变量写方读方触发条件
g_t_diesel_gen.t_dg_run_data.*本文件四个页查询函数、genset_run_data_clear()net/ems.c:1585-1617(组上报 JSON)收到对应页有效响应时写;15s 无响应或油机失能时清零
g_t_diesel_gen.t_dg_ctrl_cmd.u32_get_data_last_time页查询函数、genset_comm_handle()本文件丢失判定每次有效响应刷新;丢失/失能时也刷新
g_t_diesel_gen.t_dg_ctrl_cmd.u32_dg_query_tickgenset_comm_handle()本文件节拍判定每次发起查询时刷新
g_t_meter.t_config_data.u8_genset_enable_flagHMI hmi/key.c:8255、Modbus 从站 midware/interface/modbus/modbus.c:8068本文件 :300用户在 LCD 或上位机写寄存器开启/关闭油机监视
g_u8_recv_buf(meter.c:40,256 字节,extern)meter_recv_data()本文件四个页查询函数共享的总线接收缓冲,与电表查询复用同一块内存

5. 对外接口

导出(供外部调用)

名称声明位置调用方
void genset_comm_handle(void)diesel_generator.h:86midware/meter/meter.c:5876(task_meter() 主循环)
DIESEL_GENERATOR_TABLE_T g_t_diesel_gendiesel_generator.h:83(extern)定义于本文件 :20;被 net/ems.c 读取上报

调用(依赖的外部函数/变量)

名称来源
meter_write_data() / meter_recv_data()midware/meter/meter.h:457-458(实现于 meter.c:51/66,走 usart1)
g_u8_recv_buf[]midware/meter/meter.h:451(大小 METER_RECV_BUFFER_SIZE = 256)
g_t_meter.t_config_data.u8_genset_enable_flagmidware/meter/meter.h:410
METER_READ_FUN_CODEmidware/meter/meter.h:47
GetCrcCode_Modbus()common/crc.h:17
OSTimeGet()uCOS-II(经 includes.h)
memcpy / abs标准 C 库(abs 经 includes.h / stdlib.h)
TRUE / FALSEincludes.h

6. 依赖关系

7. 备注与风险点

  1. 响应长度未校验,存在越界读风险——四个查询函数只检查 g_u8_recv_buf[2] 等于期望字节数,然后直接索引到 g_u8_recv_buf[70](Page 4)。虽然 g_u8_recv_buf 有 256 字节(meter.h:92)不会越界读内存,但会把上一帧的残留数据当成本帧数据解析:meter_recv_data() 只在 CRC 正确时把 *len 设为实际长度,而查询函数传入的 l_u16_recv_num 被置为非 0 后就完全不使用它做长度比对。正确做法是同时校验 l_u16_recv_num >= 3 + 期望字节数 + 2。
  2. 不校验从站地址与功能码——:59 的条件里没有 g_u8_recv_buf[0] == GENSET_ADDR 和 g_u8_recv_buf[1] == 0x03。异常响应帧(addr + 0x83 + 异常码 + CRC)的 buf[2] 是异常码,恰好等于 68/4/8/18 的概率极低,所以实际风险有限,但这是协议实现上的薄弱点。
  3. 拷贝全局接收缓冲导致耦合——四个页查询都直接使用 g_u8_recv_buf(电表模块的全局缓冲)而不做深拷贝。任何在油机查询之后、解析之前插入的其他总线事务都会污染数据(当前调用序列中不存在这种情况,属潜在脆弱点)。
  4. p4 页首寄存器被跳过——Page 154(9 个寄存器)的 8 个告警字取自 buf[5..20],即跳过第 0 个寄存器(A0)。从命名与实现看 A0 可能是"运行状态字"而非告警位,但代码中没有任何注释说明,属寄存器映射未文档化的隐患。
  5. u8_genset_enable_flag 从 HMI/Modbus 写入时无互斥——该字段由其他任务写入(hmi/key.c、modbus.c),本文件在 task_meter() 中读取;理论上存在读到中间态的可能(u8 单字节读写在本平台是原子的,实际安全)。
  6. 失能不清 u32_dg_query_tick——重新使能后,若 u32_dg_query_tick 停留在很久以前,第一次调用会立即发起查询(这是期望行为),但如果停留在"未来"(节拍回绕后残留),会走 tick > now 分支重设,行为正确。
  7. 响应延迟假设——meter_recv_data() 是非阻塞的,因此油机查询在"响应尚未到达"时会静默失败并把该页数据保留旧值。500ms 的 DG_QUERY_SPEED_VALUE 是唯一保证,代码里没有任何显式的"等待响应"或"失败重发"逻辑。若总线负载加重(电表查询与油机查询共用 usart1),这一假设可能不成立。
  8. 魔数——:286 的告警填值 4369(0x1111) 无宏定义、无注释;:154/:175 的寄存器地址用 page*256+n 的算术写法,隐含"页号高字节编码"这一非标准 Modbus 约定,需对照油机控制器手册才能确认。
  9. 代码重复严重——四个页查询函数结构几乎完全相同(组帧 → 发送 → 校验 → 逐字段挑字节),只有寄存器地址/个数/解析偏移不同;单帧接收解析逻辑可用表驱动,现实现使得新增一页必须复制约 50 行代码。
  10. l_u8_tx_buf[8] + query_genset_cmd[6] 冗余——先填 6 字节数组再 memcpy 到 8 字节数组,多一次拷贝,没有实际必要。
  11. printf 调试分支保留但关闭——GENSET_DEBUG_INFO 为 0,printf 会被预处理器剔除,不影响运行;但四个函数各有 12 行的 printf 块,占源码篇幅较大。