东方蓄能项目解析

面向从 Qt 上位机转入嵌入式开发的学习笔记。

东方蓄能项目解析

面向从 Qt 上位机转入嵌入式开发的学习笔记。

本文根据源码静态分析整理。当前没有连接实物设备,也没有用串口工具、CAN 分析仪抓取真实报文;“已确认”表示能从代码直接确认,“待验证”表示需要结合原理图、协议文档或实机验证。

1. 项目先看懂什么

这是一个运行在 NXP S32K144 上的热管理控制器(TMS)软件,同时完成:

采集传感器和开关量
        ↓
把输入交给 Simulink 控制算法
        ↓
计算压缩机、水泵、风机、加热器目标
        ↓
通过PWM、GPIO、CAN、Modbus输出
        ↓
读取执行器反馈和外部控制命令
        ↺

最重要的总链路:

硬件输入 → BSW驱动 → APP_IO接口 → Simulink算法
算法输出 → APP_IO接口 → 执行器/通信 → 外部设备
  • BSW(Basic Software)负责调度、驱动和通信;
  • APP 目录主要是 Simulink 自动生成的控制代码;
  • APP_IO.c 是硬件/通信变量和算法变量之间的适配层;
  • Modbus、CAN 是两种并行存在的通信方式。

2. 工程目录和源码副本

当前用于分析的源码副本:

D:\002Jack.cheng\project\index_08_dongfangxuneng\DFXN01\TMS_SW_Master_20260306\script\S32K144_TMS\FreeRTOS_TAD_logs\DFXN01

正式修改前必须确认 S32DS 实际编译的是哪一份 Sources。工程中有日志、备份和其他平行工程,改错目录会出现“编译成功但程序没变化”。

TMS_SW_Master_20260306/
├─ models/                 Simulink模型,例如 TMS_SLM.slx
├─ libraries/              Simulink库
├─ script/                 MATLAB生成、编译、发布脚本
├─ work/                   代码生成中间文件
├─ script/S32K144_TMS/
│  └─ DFXN01/
│     ├─ Sources/APP/      Simulink生成C/H
│     ├─ Sources/BSW/      调度、IO、Modbus、CAN
│     ├─ Sources/CCP/      CCP标定通信
│     ├─ Sources/main.c    程序入口
│     ├─ Generated_Code/   外设配置
│     ├─ driver/           项目底层驱动、诊断支持
│     └─ SDK/              NXP SDK驱动
├─ release/               bin/elf/map/a2l发布物
└─ backup/                带时间戳的归档

3. 启动、主循环和任务

main()的初始化顺序大致是:

TMS_SLM_initialize()
→ 时钟、缓存、电源、内存、GPIO
→ 看门狗、CAN、定时器、队列
→ FlexCAN邮箱
→ BSW_Init(ADC/PWM/LPIT/UART/CAN)
→ APP_Initial()
→ eMB_Init()
→ 进入无限循环

主循环核心:

for (;;)
{
    BSW_Schedule();
    MB_Master_Poll();

    if (machineStat == MACHINE_STAT_CONTROLLED_BY_BMS)
        eMBPoll();
    else
    {
        MBResetTimerTimeOut();
        Modbus_clear();
    }
}

含义:BSW_Schedule()执行周期任务;MB_Master_Poll()处理LPUART2方向的Modbus主机;只有控制权属于BMS时,才处理LPUART1方向的Modbus从机。

LPIT通道0周期为1000 us(1 ms)。LPIT_IRQHandler()只调用BSW_TaskTimer()设置任务标志,复杂工作在主循环执行。

周期函数主要内容
每轮BSW_Task0msCCP后台、CAN接收入口
10msBSW_Task10ms读输入、ADC、Inputs()、TMS_SLM_step()、Outputs()
100msBSW_Task100msCAN_Rx_Tx()、MB_Master_main()、保存标定、CCP采集
1msBSW_Task1ms计时和通信超时维护

最核心的控制周期:

每10ms:采集 → 算法 → 输出
每100ms:外部设备通信轮询
每1ms:计时和任务标志

4. 输入、Simulink、输出

4.1 Inputs

APP_IO.c的Inputs()完成硬件到算法的适配:

Sens_ADOutWaterTemp = ADC1_SE1_PTA3;
Sens_ADInWateTemp   = ADC1_SE0_PTA2;
Sens_ADHiPrs        = ADC1_SE5_PTC7;
Sens_ADLoPrs        = ADC1_SE12_PTA15;

链路是:

ADC通道 → Read_ADValue()
→ ADC1_SE1_PTA3 → Inputs()
→ Sens_ADOutWaterTemp → Simulink SigIn/滤波

Inputs()还读取液位、强制启动等数字量,并包含液位延时和通信故障自动控制。

模型:

models\TMS_SLM.slx

生成代码:

Sources\APP\TMS_SLM.c/.h
Sources\APP\SigIn.c
Sources\APP\TMSCtl.c
Sources\APP\OBD.c
Sources\APP\Global.c/.h

入口:

TMS_SLM_initialize();
TMS_SLM_step();

TMS_SLM_step()内部可理解为:

SigIn()  输入换算和滤波
OBD()    故障诊断和去抖
TMSCtl() 控制决策、转速和占空比

模型配置为固定步长离散求解,FixedStep=0.01,即10ms。不要长期直接修改APP生成文件,应修改.slx后重新生成。

4.3 Outputs

Outputs()顺序:

_deb_val()
→ APP_SetInfoToBMS()
→ APP_SetInfoToHMI()
→ HMI控制权处理
→ 压缩机适配
→ 风机适配
→ GPIO/继电器输出

压缩机目标转速链:

TMS_nCompSpd
→ APP_SetInfoToCompressor_main()
→ mc_fr_set = TMS_nCompSpd / TMS_nCompMax_C * 10000
→ Modbus主机或对应执行器通信

5. 出水温度完整链路

ADC原始值
→ Sens_ADOutWaterTemp
→ SigIn换算和一阶滤波
→ ADC_tOutWaterTempFlt
→ OBD传感器故障去抖
→ _deb_val()
→ _deb_temp_water_out

出水传感器故障且回水正常时,_deb_val()按工作模式用回水温度±2℃替代;高压传感器故障固定3999,压缩机温度故障固定299(代码单位)。

之后分成三条对外链路:

_deb_temp_water_out
├─ Reg6 → BMS Modbus读取
├─ hmiRegs.chushui_wendu → HMI Modbus主机写入
└─ TX_BTMS_Data[1] = 温度 + 40 → BMS CAN上报

通信值不一定等于算法内部值,必须查协议的比例和偏移。

6. 通信总览

对端接口本机角色代码备注
BMSLPUART1 + RS-485Modbus从机cus_modbus.cBMS问,TMS答
压缩机/风机/HMILPUART2 + RS-485Modbus主机cus_modbus_CAN.cTMS主动轮询
BMSCANCOM2CAN节点FlexCAN.c周期8字节报文
DCDC/主从TMS/HMICANCOM1CAN节点FlexCAN.c多套自定义ID
标定工具CANCOM1CCP从设备CCP/DAQ和标定
诊断/刷写工具CAN诊断节点CanTp、UDSISO-TP/UDS

7. Modbus RTU从机:BMS方向

7.1 理论

UART:MCU逐字节收发
RS-485:电气层和半双工总线
Modbus RTU:地址、功能码、寄存器和CRC格式

LPUART1配置为9600、8N1。TMS从机地址为1,支持0x03、0x04、0x06、0x10。

WATERCOOL配置下:

协议地址0x0000,长度90,mode=0:只读区
协议地址200,长度30,mode=1:可读写区

因此地址0~89对应Reg1~Reg90,地址200~229对应Reg91~Reg120;Reg91不是协议地址90。

7.2 一帧读取Reg6

Modbus 常见功能码:

01 → 读线圈 Coil
02 → 读离散输入
03 → 读保持寄存器 Holding Register
04 → 读输入寄存器 Input Register
05 → 写单个线圈
06 → 写单个保持寄存器
10 → 写多个保持寄存器

按当前地址权限代码,Reg6索引为5,属于只读区,使用0x04读取:

请求:01 04 00 05 00 01 21 CB
01       从机地址1
04       读取输入寄存器
00 05    起始地址5,对应Reg6
00 01    读取1个寄存器
21 CB    CRC,低字节在前

接收过程:

LPUART1单字节接收
→ LPUART1_RX_ISR()
→ xMBRTUReceiveFSM()
→ 字节进入Rx_Buf[0..]
→ 每字节重启t3.5
→ 约4.01ms静默
→ LPIT_CH1_IRQHandler()
→ EV_FRAME_RECEIVED
→ 主循环eMBPoll()

Rx_Buf是256字节接收缓冲区。接收状态机从STATE_RX_IDLE进入STATE_RX_RCV,每次回调只安排下一个字节的存储位置。

7.3 CRC、地址、功能码

eMBPoll()收到完整帧后:

  1. 对完整帧计算MB_CRC16(),正确帧重新计算结果为0;
  2. 检查地址是否为广播0或本机地址1;
  3. 校验通过后投递EV_EXECUTE;
  4. MB_Execute()调用MB_Analyze_Execute()。

MB_Parse_Data()解析:

Code = Rx_Buf[1];
Addr = (Rx_Buf[2] << 8) | Rx_Buf[3];
Num  = (Rx_Buf[4] << 8) | Rx_Buf[5];

本例为Code=0x04、Addr=5、Num=1。MID_Adr_judge()检查范围、权限并返回idx=5。

7.4 生成回复

APP_LUART_ReadReg(5,1)先调用APP_SetInfoToBMS()刷新:

Reg6 = _deb_temp_water_out;

假设Reg6=25(0x0019),回复是:

01 04 02 00 19 78 FA

生成:

Tx[0] = 地址
Tx[1] = 功能码
Tx[2] = 数据字节数
Tx[3] = 寄存器高字节
Tx[4] = 寄存器低字节
Tx[5..6] = CRC低、高字节

发送时PTE9=1切RS-485到发送方向,调用LPUART_DRV_SendData(INST_LPUART1,...)。发送完成中断再切回接收。

7.5 BMS写入

0x06:写一个寄存器并回显请求
0x10:解析多个寄存器并回显地址、功能码、起始地址和数量

典型控制链:

BMS写Reg91/Reg92/Reg96
→ APP_LUART_WrtieReg()
→ APP_GetInfoFromBMS()
→ BMS_stModeReq/BMS_stHiVoltgState/BMS_tSetTemp
→ 下一次10ms控制算法读取

非法功能码返回原功能码|0x80 + 异常码01;非法地址/权限返回异常码02;CRC错误或地址不是本机时通常静默丢弃。

8. Modbus RTU主机:LPUART2方向

地址1:设备1/主压缩机
地址2:设备2/副压缩机
地址4:风机
地址64:HMI

MB_Master_main()在STC_IDLE时轮询字段表:

mode=0 → 0x03读取
mode=2且size=1 → 0x06写单寄存器
mode=2且size>1 → 0x10写多寄存器

它只选择命令并调用MB_Master_CMD();真正加CRC、切方向、用LPUART2发送的是MB_Master_Poll()。

STC_IDLE
→ MB_Master_main选择命令
→ STC_FRAME_SENT
→ MB_Master_Poll加CRC并发送
→ STC_WAIT
→ 收到回复后STC_EXECUTE
→ MB_Master_Execute解析
→ STC_IDLE

虽然缓存名叫CAN_Tx_Buf、文件名叫cus_modbus_CAN.c,实际调用的是LPUART_DRV_SendData(INST_LPUART2,...),所以这里是RS-485 Modbus。

HMI Modbus表:

0x1040,30个,WO:TMS写HMI状态
0x105E,1个,RO:读取HMI控制请求
0x105F,4个,WO:下发当前BMS控制参数
0x1063,4个,RO:读取HMI用户参数
0x1067,1个,WO:控制权应答

9. CAN通信

9.1 基础

Modbus:报文中有从机地址,通常一问一答,软件CRC
CAN:报文由ID标识,硬件完成CRC/ACK/仲裁,经典CAN最多8字节

主要ID:

用途发送ID接收ID帧类型
BMS0x18FFC13A0x18FF45F4扩展
DCDC0x102000100x10000010扩展
压缩机(当前逻辑禁用)0x2360x235标准
主从TMS0x1111110x222222/0x333333扩展
HMI0x18FFF5450x18FF45F5扩展
CCPCCP_DTO_IDCCP_CRO_ID扩展

dataInfo_EXT/STD都配置为8字节、非CAN-FD。邮箱MB是硬件缓冲槽,不是Modbus寄存器。

9.2 BMS CAN

BMS接收ID为0x18FF45F4,解析:

Byte0 bit0~1:模式请求
Byte0 bit2~3:高压请求
Byte0 bit6~7:高压继电器状态
Byte1~2:高压,小端16位
Byte3:温度,raw×0.5
Byte4:设定温度,raw-40
Byte5:湿度,raw×0.5

TMS发送0x18FFC13A:

Byte0:工作模式和高压状态
Byte1:出水温度+40
Byte2:回水温度+40
Byte3:高压/20
Byte4:低压×50
Byte7:故障等级高2位+故障码低6位

CAN_Rx_Tx()在100ms任务中被调用,但Can_BMS_cnt>=9才处理一次,实际约每1秒一次;源码注释中的周期需要实机确认。

9.3 主从TMS

TMS向从机发送0x111111,包括模式、高压、电压、设定温度、压力和环境值;从机用0x333333返回温度、转速和压力。解析后的slv_info_comp_spd等变量会进入BMS上报寄存器。

9.4 HMI自定义CAN

HMI → TMS:0x18FF45F5
TMS → HMI:0x18FFF545

应用帧:idx + 6字节data + lrc。

idx bit7:写入控制参数
idx bit6:读取时选择控制数据或状态数据
idx bit0~3:6字节分片编号
lrc:idx和6个data字节相加的低8位

写入链:

HMI帧 → 校验 → tms_ctrl → 限幅 → 回写BMS控制变量

读取链:

tms_info/tms_ctrl → 按idx取6字节 → 加LRC → 回复HMI

9.5 当前启用和禁用分支

  • BMS CAN、主从TMS CAN、HMI CAN代码处于启用路径;
  • 压缩机CAN收发整段位于#if 0,当前主要走Modbus;
  • 水泵CAN位于#if 0;
  • DCDC代码执行,但故障标志随后被强制清零,需核对;
  • CCP使用CANCOM1邮箱8/9;
  • UDS/ISO-TP使用CAN完成诊断和刷写,不要和业务CAN混为一谈。

10. CCP、UDS和后台通信

每轮主循环的BSW_Task0ms()调用:

CAN_receive_msg();
ccpBackground();
ccpBootReceiveCro();
ccpCommand();
ccpSendCallBack();

这是标定、DAQ和CCP命令链。主循环的1ms节拍还调用CanTpMainFun()推进ISO-TP,10ms节拍调用看门狗和UDS处理。它们属于诊断/刷写链路,不是温度和转速业务报文。

11. Simulink到固件的生成流程

script\CodeGenerate.m主要做:

rtwbuild("TMS_SLM")
→ work/code/TMS_SLM_ert_rtw生成C/H/A2L
→ 清空目标工程Sources/APP
→ 复制生成C/H
→ 调用build.bat
→ 复制bin/elf/map/a2l到release和backup

正确修改流程:

修改models/TMS_SLM.slx
→ MATLAB重新生成
→ 检查Sources/APP差异
→ 编译
→ 下载和验证

不要把生成后的APP/TMS_SLM.c当作长期手写源码。

12. 后续修改重点

需求首先看
改制冷/制热/转速算法models/TMS_SLM.slx
改ADC、GPIO到算法变量APP_IO.c、BSW_interface.c
新增BMS Modbus上报量APP_SetInfoToBMS()、cus_modbus.c/.h
新增BMS控制量APP_GetInfoFromBMS()、可写寄存器区
改压缩机/风机Modbuscus_modbus_CAN.c、设备字段表
改HMIhmi.c/.h、HMI字段表
改CAN信号FlexCAN.c的ID、Data字节和位域
改执行周期BSW_Task.c、LPIT配置
改波特率/引脚Generated_Code和Processor Expert
改标定/诊断CCP/、CAN-TP、UDS/Boot相关目录

每次改协议前先写一行协议表:

对端 | 接口 | 方向 | 角色 | 地址/CAN ID | 功能码
字节/位 | 类型 | Factor | Offset | 周期 | 超时行为

13. 没有设备时的验证方法

Modbus BMS方向

用USB转RS-485上位机模拟主机,读取:

地址5/功能码04:Reg6出水温度
地址6/功能码04:Reg7回水温度
地址200以后:控制参数读写

检查CRC、大小端、异常码、控制权条件和超时行为。

Modbus主机方向

让上位机模拟地址1、2、4、64的从机,观察TMS发出的地址、功能码、起始地址、数量、写入值和轮询顺序。

CAN方向

USB-CAN可模拟:

0x18FF45F4:BMS控制帧
0x18FF45F5:HMI请求帧
0x333333:从机反馈帧

同时监听:

0x18FFC13A:TMS给BMS
0x18FFF545:TMS给HMI
0x111111:TMS给从机

14. 已确认、待验证和风险

已从代码确认

  • 10ms任务执行“采集→算法→输出”;
  • BMS有LPUART1 Modbus和CAN两条链路;
  • LPUART2承担压缩机、风机、HMI方向的Modbus主机;
  • Reg6由_deb_temp_water_out提供;
  • BMS CAN出水温度使用“温度+40”;
  • HMI同时存在Modbus和自定义CAN;
  • APP代码由TMS_SLM.slx生成。

待文档、原理图或实机确认

  • 两路CAN实际接到哪些物理设备;
  • CAN实际波特率;
  • RS-485的A/B、共地、PTE方向脚和终端电阻;
  • 正式寄存器地址、单位和符号类型;
  • 注释周期与实际周期;
  • FLEXCAN_DRV_Receive()异步接收的正确使用方式;
  • 当前IDE真正编译的源码副本。

修改前注意

  1. main.c调用了一次FlexCAN_Initial(),BSW_Init()内部又调用一次,存在重复初始化风险。
  2. BMS CAN由Can_BMS_cnt分频,实际约1秒一次,注释写有100ms。
  3. DCDC和从机CAN故障标志在判断后又被直接清零,可能掩盖故障。
  4. FLEXCAN_DRV_Receive()是非阻塞的,调用后立即读取缓冲区可能读到旧帧。
  5. hmi.c中#if 1/#else有重复实现,只有前者生效;旧状态机在#if 0中。
  6. lpuartComm.c也有被#if 0屏蔽的旧回调,不要误读。
  7. 变量和缓存有CAN/Modbus历史命名混用,判断通信类型要看实际驱动调用。

15. 从Qt上位机转嵌入式的排查方法

Qt按钮/定时器 ≈ MCU任务调度
QSerialPort   ≈ LPUART驱动和中断
QTimer        ≈ LPIT定时器和任务标志
信号槽        ≈ 中断回调、事件和状态机
协议解析类    ≈ cus_modbus.c / FlexCAN.c
界面状态      ≈ 控制、故障和通信状态变量

遇到“设备不动作”,按层排查:

任务有没有执行?
→ 输入变量有没有更新?
→ Simulink输出是否合理?
→ APP_IO是否正确映射?
→ 报文是否组对?
→ 驱动是否真正发送?
→ 总线电气是否正常?
→ 对端是否按同一协议解析?
→ 执行器是否有反馈?

16. 术语速查

术语含义
BSW基础软件层,调度、驱动、通信
APP_IO应用与硬件/通信之间的适配层
Rx/Tx接收/发送
Mailbox/MBCAN硬件消息缓冲槽
CAN IDCAN报文标识符和仲裁依据
Factor/Offset比例/偏移
t3.5Modbus RTU判断帧结束的静默时间
CRC报文错误校验
LRCHMI应用层累加校验
#if 0编译时排除,不是当前运行逻辑

17. 一句话总结

10ms任务负责控制算法,100ms任务负责外部设备轮询,
APP_IO负责变量翻译,Modbus负责寄存器通信,CAN负责ID和字节通信,
Simulink模型是控制逻辑源头,Generated Code是编译产物,
实机联调前必须用协议表、抓包和硬件测量把静态分析结论逐项验证。
载入内容