LIN诊断实现基于UDS协议的OTA升级功能代码及资料(支持AB面升级 ) 产品包括: 1.升...
LIN诊断实现基于UDS协议的OTA升级功能代码及资料(支持AB面升级 ) 产品包括: 1.升级上位机源码; 2.MCU端源码(boot和app),包含LIN协议栈+UDS协议框架(包含常用SID服务代码) 3.LIN学习资料和ISO14229资料 开发板硬件(自行淘宝) 5.根据ldf文件生成满足标准2.1协议代码的配置工具 联系付款后联系我百度下载 (开发版价值一百块左右,MCU为复旦微FM33LE015A车规级芯片,方便移植到其他芯片,我还移植过TI芯片) LIN调试工具为图莫斯USB转LIN工具
一、系统整体架构与代码组织
本OTA升级系统基于LIN总线与UDS诊断协议,实现FM33FG0X14A MCU设备的固件远程更新,采用“上位机-从机”架构。代码分为上位机升级工具(Windows环境,MFC框架)与MCU端从机固件(Cortex-M0+内核)两大部分,核心围绕“双分区升级”与“标准化诊断流程”展开,确保升级过程安全、可靠、可追溯。
1.1 上位机代码组织
上位机代码集中于LIN升级上位机目录,共包含14个核心文件,按功能可划分为4类:
- 应用框架文件:
LinUdsUpgrade.cpp/.h,定义MFC应用程序入口与全局配置 - 主对话框文件:
LinUdsUpgradeDlg.cpp/.h,封装升级全流程逻辑、UI交互与参数管理 - LIN通信文件:
Lin/CLin.cpp/.h,实现LIN设备连接、数据收发与协议适配 - 工具与配置文件:
pch.cpp/.h(预编译头)、resource.h(资源定义)、framework.h(MFC框架依赖)等
1.2 MCU端代码组织
MCU端代码针对FM33FG0X14A芯片设计,核心文件位于MCU端从机代码-FM33FG0X14A/mcu_app目录,按层级划分为:
- 底层核心:CMSIS标准库文件(
corecm0plus.h/corecmFunc.h等),提供内核寄存器操作、中断管理接口 - 芯片驱动:
fm33fg0xxa.h/system_fm33fg0xxa.c,定义芯片外设寄存器、时钟配置与系统初始化逻辑 - 外设驱动:
fm33fg0xxafladc.h等,提供ADC、Flash等外设的操作接口 - 链接脚本与启动文件:
fm33fg04xaflash.ld(内存分区定义)、startupfm33fg0xxa.s(中断向量表与复位逻辑)
二、上位机代码功能深度解析
2.1 应用程序初始化(LinUdsUpgrade.cpp)
CLinUdsUpgradeApp类是上位机应用入口,核心功能通过InitInstance()实现,代码逻辑可拆解为6个关键步骤:
- 公共控件初始化:
cpp
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
InitCtrls.dwICC = ICCWIN95CLASSES;
InitCommonControlsEx(&InitCtrls);
注册Windows公共控件(如进度条、按钮),确保MFC控件正常显示与交互。
- MFC环境配置:
- 启用控件容器:AfxEnableControlContainer(),支持ActiveX控件与复合控件
- 创建Shell管理器:CShellManager *pShellManager = new CShellManager;,支持Shell树视图/列表视图控件
- 设置视觉管理器:CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));,适配Windows原生主题
- 注册表配置:
cpp
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
定义应用程序在注册表中的存储路径,用于保存用户配置(如上次升级参数)。
- 主对话框创建与显示:
cpp
CLinUdsUpgradeDlg dlg;
mpMainWnd = &dlg;
INTPTR nResponse = dlg.DoModal();
创建主升级对话框并以模态方式显示,阻塞后续代码执行直至对话框关闭。
- 对话框关闭处理:
- 若用户点击“确定”(nResponse == IDOK):预留后续业务逻辑扩展(如升级结果统计)
- 若用户点击“取消”(nResponse == IDCANCEL):直接关闭对话框
- 若对话框创建失败(nResponse == -1):输出调试日志,提示MFC控件配置错误
- 资源清理:
cpp
if (pShellManager != nullptr) delete pShellManager;
#if !defined(AFXDLL) && !defined(AFXNOMFCCONTROLSIN_DIALOGS)
ControlBarCleanUp();
#endif
释放Shell管理器资源,清理控件栏(ControlBar),避免内存泄漏。
2.2 主对话框核心逻辑(LinUdsUpgradeDlg.cpp)
CLinUdsUpgradeDlg是上位机功能核心,封装了升级参数管理、LIN通信交互、升级流程控制等关键功能,以下按代码执行顺序解析核心函数:
2.2.1 对话框初始化(OnInitDialog())
该函数在对话框创建时执行,完成UI初始化、LIN设备连接与参数默认配置,代码逻辑分为5部分:
- 系统菜单配置:
cpp
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr) {
CString strAboutMenu;
strAboutMenu.LoadString(IDSABOUTBOX);
if (!strAboutMenu.IsEmpty()) {
pSysMenu->AppendMenu(MFSEPARATOR);
pSysMenu->AppendMenu(MFSTRING, IDMABOUTBOX, strAboutMenu);
}
}
为系统菜单(窗口右上角“?”)添加“关于”菜单项,点击后弹出CAboutDlg对话框。
- 窗口图标设置:
cpp
SetIcon(mhIcon, TRUE); // 大图标
SetIcon(mhIcon, FALSE); // 小图标
加载应用程序图标(IDR_MAINFRAME),设置窗口标题栏与任务栏图标。
- UI默认值配置:
cpp
mSoftwareVer.SetWindowTextW(T("未获取")); // 软件版本初始值
mEditUpgradeNodeAddr.SetWindowTextW(T("7f")); // 节点地址默认0x7F
mBzoneStartAddr.SetWindowTextW(T("00008800")); // B分区起始地址
mBzoneSize.SetWindowTextW(T("00007400")); // B分区大小
mEditLog.SetLimitText(0); // 日志窗口无长度限制
初始化控件显示值,确保用户无需手动配置即可启动基础升级流程。
- LIN设备连接:
cpp
int ret = CLin::connecttoomoss(devidx); // 连接图莫斯设备(devidx=0,默认第一个设备)
if (ret < 0) {
MessageBox(T("请用图莫斯工具连接LIN!"), T("提示"), MBOK | MBICONWARNING);
SendMessage(WMCLOSE);
} else {
ret = CLin::initlin(devidx, channelidx, LINSLAVEMODE, 19200);
if (ret < 0) {
MessageBox(T("LIN 连接失败,请重试!"), T("提示"), MBOK | MBICONWARNING);
SendMessage(WMCLOSE);
}
}
先扫描USB总线获取图莫斯设备句柄,再初始化LIN总线参数(从机模式、19200bps波特率),连接失败则弹窗提示并关闭应用。
2.2.2 升级文件加载(OnBnClickedButtonLoadfile())
用户点击“加载文件”按钮触发该函数,实现.bin格式升级文件的选择与信息解析:
- 文件对话框创建:
cpp
TCHAR szFilter[] = T("BIN file(.bin)|.bin|所有文件(.)|.||");
CFileDialog fileDlg(TRUE, T("bin"), T("*.bin"), OFNHIDEREADONLY, szFilter, this);
fileDlg.mofn.lpstrTitle = T("打开升级文件");
创建文件选择对话框,仅显示.bin文件与所有文件,标题为“打开升级文件”。
- 文件信息解析与显示:
`cpp
if (IDOK == fileDlg.DoModal()) {
filePath = fileDlg.GetPathName(); // 获取文件路径
CFileStatus fileStatus;
CFile::GetStatus(filePath, fileStatus); // 获取文件状态
CString fileInfo = _T("升级文件信息:\r\n文件路径:");
fileInfo += filePath;
fileInfo += _T("\r\n文件大小:");
CString size;
size.Format(T("%d Bytes"), fileStatus.msize);

fileInfo += size;
fileInfo += _T("\r\n创建日期:");
CString date = fileStatus.mctime.Format(T("%Y-%m-%d %H:%M:%S"));
fileInfo += date;
mEditUpgradeFilePath.SetWindowTextW(fileInfo); // 显示文件信息

mUpgradeFileTotalSize = _ttoi(size); // 保存文件大小(字节)
mUpgradeFilePath = filePath; // 保存文件路径
}
`
若用户选择文件,通过CFile::GetStatus()获取文件大小、创建时间等信息,显示在mEditUpgradeFilePath控件中,并更新全局变量mUpgradeFileTotalSize与mUpgradeFilePath。
2.2.3 软件版本获取(OnBnClickedButtonGetSwVer())
用户点击“获取软件版本”按钮触发该函数,通过UDS协议读取MCU端当前固件版本:
- 节点地址校验:
cpp
CString upgradeNodeAddr;
mEditUpgradeNodeAddr.GetWindowText(upgradeNodeAddr);
mUpgradeNodeAddr = HexToDem(upgradeNodeAddr);
if (mUpgradeNodeAddr <= 0) {
MessageBox(T("节点地址无效,请输入有效节点地址!"), T("提示"), MBOK | MBICONINFORMATION);
return;
}
读取用户输入的节点地址,通过HexToDem()函数将16进制字符串转为十进制,若结果≤0则提示无效。
- UDS指令发送与响应解析:
cpp
uint8t reqData[] = { 0x22, 0xfb, 0x88 }; // UDS读取数据指令(SID=0x22,数据ID=0xFB88)
int ret = SendDiagFrame(mUpgradeNodeAddr, reqData, 3, respData);
if (ret <= 0) {
MessageBox(T("获取软件版本号命令发送失败,请重试!"), T("提示"), MBOK | MBICONWARNING);
mSoftwareVer.SetWindowTextW(T("获取失败"));
return;
}
// 解析版本号(响应数据第5-7字节分别为主版本、次版本、修订版本)
CString str;
str.Format(_T("%d.%d.%d"), respData[5], respData[6], respData[7]);
mSoftwareVer.SetWindowTextW(str);
ShowRespData(respData, ret); // 显示完整响应数据到日志
发送UDS读取指令(0x22 FB 88),若发送成功,从响应数据中提取版本号并显示在mSoftwareVer控件,同时通过ShowRespData()输出响应日志。
2.2.4 升级启动(OnBnClickedButtonUpgradeStart())
用户点击“开始升级”按钮触发该函数,完成升级前参数校验并启动升级线程:
- 参数完整性校验:
- 节点地址校验:同“获取软件版本”逻辑,确保地址有效
- B分区起始地址校验:cpp
CString editStr;
mBzoneStartAddr.GetWindowText(editStr);
mUpgradeStartAddr = HexToDem(editStr);
if(mUpgradeStartAddr <= 0) {
MessageBox(T("无效的B分区起始地址,请输入有效的16进制数值!"), T("提示"), MBOK | MBICONWARNING);
return;
}
- B分区大小校验:逻辑同上,确保大小>0
- 升级文件校验:需先通过“加载文件”功能配置mUpgradeFilePath,否则后续LoadUpgradeFile()会弹窗提示
- UI控件禁用与初始化:
cpp
mButtonUpgradeStart.EnableWindow(0); // 禁用“开始升级”按钮
mBtnGetSwVer.EnableWindow(0); // 禁用“获取版本”按钮
mBtnLoadFile.EnableWindow(0); // 禁用“加载文件”按钮
mEditUpgradeNodeAddr.EnableWindow(0); // 禁用节点地址编辑
mBzoneStartAddr.EnableWindow(0); // 禁用B分区地址编辑
mBzoneSize.EnableWindow(0); // 禁用B分区大小编辑
mProgressUpgradeStep.SetPos(0); // 进度条复位为0
mEditLog.SetWindowTextW(_T("")); // 清空日志窗口
CLinUdsUpgradeDlg::mUpgradeSecCnt = 0; // 升级计时清零
禁用所有可配置控件,防止升级过程中参数被修改,同时初始化进度条与日志。
- 升级线程启动:
cpp
SetTimer(3, 1000, NULL); // 启动定时器(ID=3,1秒触发一次,用于升级计时)
pCWinThread = AfxBeginThread(UpgradeProcessThreadFunc, (LPVOID)this);
启动定时器记录升级耗时,通过AfxBeginThread()创建升级线程(UpgradeProcessThreadFunc),传入当前对话框指针作为参数。
2.2.5 升级核心线程(UpgradeProcessThreadFunc())
该线程是升级流程的核心,实现从“会话切换”到“固件传输”再到“系统重启”的全流程,代码逻辑可拆解为13个关键步骤:
- 升级文件加载与预处理:
`cpp
uint8_t *pFileData = pUpgrade->LoadUpgradeFile();
if(pFileData == NULL) goto FINISH; // 加载失败则退出线程
// 计算升级包总数(64字节/包,余数需补1包)

uint32t pkgtotalnum = pUpgrade->mUpgradeFileTotalSize / UPGRADEPKG_SIZE;
if((pUpgrade->mUpgradeFileTotalSize % UPGRADEPKGSIZE) != 0) pkgtotalnum++;
LIN诊断实现基于UDS协议的OTA升级功能代码及资料(支持AB面升级 ) 产品包括: 1.升级上位机源码; 2.MCU端源码(boot和app),包含LIN协议栈+UDS协议框架(包含常用SID服务代码) 3.LIN学习资料和ISO14229资料 开发板硬件(自行淘宝) 5.根据ldf文件生成满足标准2.1协议代码的配置工具 联系付款后联系我百度下载 (开发版价值一百块左右,MCU为复旦微FM33LE015A车规级芯片,方便移植到其他芯片,我还移植过TI芯片) LIN调试工具为图莫斯USB转LIN工具
`
调用LoadUpgradeFile()加载升级文件,返回文件数据指针,若为NULL则跳转至资源清理步骤;同时计算升级包总数,后续按包传输。
- 切换至扩展会话:
cpp
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(T("切换到扩展会话..."));
uint8t reqData[256] = { 0x10, 0x03 }; // UDS会话控制指令(SID=0x10,子功能=0x03)
int ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 2, respData);
if (ret <= 0 || respData[2] == NEGATIVERSP) {
// 处理发送失败或负响应(如服务不支持)
CString info;
info.Format(T("切换扩展会话失败,错误码: %d!"), ret);
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(info);
goto FINISH;
}
pUpgrade->ShowRespData(respData, ret);
发送UDS会话切换指令(0x10 03),切换至扩展会话;若发送失败或收到负响应(0x7F开头),则提示错误并退出。
- 停用DTC存储:
cpp
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(_T("停用DTC存储功能..."));
reqData[0] = 0x85; reqData[1] = 0x02; // UDS控制DTC指令(SID=0x85,子功能=0x02)
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 2, respData);
// 错误处理逻辑同上,失败则退出
发送UDS指令(0x85 02)停用DTC存储,避免升级过程中因硬件状态变化触发故障码。
- 切换至编程会话:
cpp
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(_T("切换到编程会话..."));
reqData[0] = 0x10; reqData[1] = 0x02; // 子功能=0x02,切换至编程会话
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 2, respData);
// 错误处理逻辑同上
发送UDS指令(0x10 02)切换至编程会话,进入固件更新模式。
- 请求安全种子:
cpp
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(T("请求安全种子..."));
reqData[0] = 0x27; reqData[1] = 0x01; // UDS安全访问指令(SID=0x27,子功能=0x01)
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 2, respData);
if (ret > 0 && respData[2] != NEGATIVERSP) {
// 提取种子(响应数据第4-7字节为32位种子)
securityseed = respData[4] << 24 | respData[5] << 16 | respData[6] << 8 | respData[7];
} else {
// 处理负响应(如安全访问被拒绝)
pUpgrade->ShowNCRInfo(respData[3], (enUDSNCR)respData[4]);
goto FINISH;
}
发送UDS安全访问指令(0x27 01)请求种子,若成功则提取32位种子值,用于后续密钥计算。
- 发送安全密钥:
cpp
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(T("发送安全秘钥..."));
uint32t securitykey = ~securityseed; // 密钥 = 种子按位取反
reqData[0] = 0x27; reqData[1] = 0x02; // 子功能=0x02,发送密钥
reqData[2] = securitykey >> 24; // 密钥第1字节
reqData[3] = securitykey >> 16; // 密钥第2字节
reqData[4] = securitykey >> 8; // 密钥第3字节
reqData[5] = securitykey & 0xff; // 密钥第4字节
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 6, respData);
// 错误处理逻辑同上,若密钥不匹配则提示“密钥无效”
计算密钥(种子按位取反),发送UDS指令(0x27 02)将密钥传输至MCU,验证通过后解锁敏感操作权限。
- 检查升级条件:
cpp
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(_T("检查升级条件..."));
reqData[0] = 0x31; reqData[1] = 0x01; reqData[2] = 0x02; reqData[3] = 0x03;
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 4, respData);
// 错误处理逻辑同上,检查电压、温度等升级条件是否满足
发送UDS指令(0x31 01 02 03)检查MCU端升级条件(如供电电压、芯片温度),确保满足Flash操作要求。
- 擦除B分区:
cpp
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(_T("擦除升级分区..."));
reqData[0] = 0x31; reqData[1] = 0x01;
reqData[2] = 0xff; reqData[3] = 0x00; // 子功能=0xFF00,擦除分区
reqData[4] = 0x44; // 地址/大小格式:4字节地址+4字节大小
// 填充B分区起始地址(4字节)
reqData[5] = pUpgrade->mUpgradeStartAddr >> 24;
reqData[6] = pUpgrade->mUpgradeStartAddr >> 16;
reqData[7] = pUpgrade->mUpgradeStartAddr >> 8;
reqData[8] = pUpgrade->mUpgradeStartAddr & 0xff;
// 填充B分区大小(4字节)
reqData[9] = pUpgrade->mUpgradeSpaceSize >> 24;
reqData[10] = pUpgrade->mUpgradeSpaceSize >> 16;
reqData[11] = pUpgrade->mUpgradeSpaceSize >> 8;
reqData[12] = pUpgrade->mUpgradeSpaceSize & 0xff;
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 13, respData);
// 错误处理逻辑同上,擦除失败则提示“编程失败”
发送UDS擦除指令,携带B分区起始地址与大小,MCU端接收后执行Flash分区擦除操作。
- 请求下载:
cpp
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(T("请求下载..."));
reqData[0] = 0x34; reqData[1] = 0x00; // UDS请求下载指令(SID=0x34)
reqData[2] = 0x44; // 地址/大小格式:4字节地址+4字节大小
// 填充B分区起始地址与大小(同擦除步骤)
reqData[3] = pUpgrade->mUpgradeStartAddr >> 24;
// ... 省略地址与大小填充代码 ...
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 11, respData);
if (ret > 0 && respData[2] != NEGATIVERSP) {
// 校验升级包大小(响应数据第3字节高4位表示包大小字节数)
uint8t pkglenbyte = respData[3] >> 4;
uint8t pkglen = 0;
for(uint8t i = 0; i < pkglenbyte; i++) {
pkglen = (respData[4 + i] << ((pkglenbyte - i - 1) * 8));
}
if((pkglen-2) != UPGRADEPKGSIZE) {
CString info;
info.Format(T("升级包大小不匹配: %d!"), pkglen);
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(info);
goto FINISH;
}
}
发送UDS请求下载指令(0x34),协商升级包大小;MCU端返回支持的包大小,需与上位机配置的64字节/包一致,否则升级终止。
- 循环发送升级包:
cpp
ret = pUpgrade->LoopSendPkgData(pFileData, pkgtotalnum);
if(ret < 0) goto FINISH;
调用LoopSendPkgData()函数循环发送所有升级包,每包64字节,支持重传机制(最多10次),实时更新进度条。
- 请求传输退出:
cpp
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(_T("请求传输退出..."));
reqData[0] = 0x37; // UDS请求传输退出指令(SID=0x37)
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 1, respData);
// 错误处理逻辑同上
发送UDS指令(0x37)结束固件传输,告知MCU端所有包已发送完成。
- 发送CRC校验码:
cpp
// 计算固件CRC校验和(累加所有字节)
uint16t crcsum = 0;
for(int i = 0; i < UPGRADEPKGSIZE * pkgtotalnum; i++) {
crcsum += pFileData[i];
}
// 发送CRC校验指令
reqData[0] = 0x31; reqData[1] = 0x01; reqData[2] = 0x02; reqData[3] = 0x02;
reqData[4] = (crcsum >> 8) & 0xff; // CRC高字节
reqData[5] = crcsum & 0xff; // CRC低字节
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 6, respData);
if (ret > 0 && respData[2] != NEGATIVERSP) {
// 校验通过,更新进度条与状态
CString upgradeInfo;
upgradeInfo.Format(T("%d个升级包发送完成,校验码: 0x%x!"), pkgtotalnum, crcsum);
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(upgradeInfo);
pUpgrade->mProgressUpgradeStep.SetPos(100);
}
计算固件所有字节的累加和(CRC),发送UDS指令(0x31 01 02 02)至MCU端校验;校验通过则将进度条设为100%。
- 系统重启与分区切换:
cpp
INTPTR res = MessageBox(NULL, upgradeInfo, T("确认是否继续升级"), MBSYSTEMMODAL | MBOKCANCEL | MBICONWARNING);
if(res == IDOK) {
// 发送重启指令(SID=0x11,子功能=0x03)
reqData[0] = 0x11; reqData[1] = 0x03;
ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 2, respData);
if (ret > 0 && respData[2] != NEGATIVERSP) {
upgradeInfo.Format(_T("升级完成,请点击获取软件版本按钮确认软件版本号!"));
pUpgrade->mStaticUpgradeInfo.SetWindowTextW(upgradeInfo);
}
}
弹窗提示用户是否重启,点击“确定”后发送UDS重启指令(0x11 03),MCU端复位后将B分区固件拷贝至A分区,完成升级。
2.2.6 辅助功能函数
- 诊断帧发送(SendDiagFrame()):
`cpp
int CLinUdsUpgradeDlg::SendDiagFrame(uint8t nodeAddr, uint8t pFrameData, int frameLen, uint8t pRespData)
{
// 输出发送日志
CString sendlog;
sendlog.Format(T("-> 0x%02x 0x%02x "), nodeAddr, frameLen);
for(int i = 0; i < frameLen; i++) {
sendlog.Format(T("%s 0x%02x"), sendlog, pFrameData[i]);
}
OutputLog(sendlog);
// 最多尝试5次发送

int ret = -1, cnt = 5;
do {
ret = CLin::upgradesendresp(nodeAddr, pFrameData, frameLen, pRespData);
} while (ret <= 0 && cnt-- > 0);
return ret;

}
`
封装LIN通信发送逻辑,输出发送日志至mEditLog控件,支持最多5次重发,确保指令可靠送达。
- 响应数据显示(ShowRespData()):
cpp
void CLinUdsUpgradeDlg::ShowRespData(uint8t *pData, int dataLength)
{
CString hexStr;
hexStr.Format(T("<-"));
for(int i = 0; i < dataLength; i++) {
hexStr.Format(_T("%s 0x%02x"), hexStr, pData[i]);
}
OutputLog(hexStr);
}
将MCU端响应数据格式化为16进制字符串,通过OutputLog()输出至日志窗口,便于调试。
- 负响应解析(ShowNCRInfo()):
cpp
void CLinUdsUpgradeDlg::ShowNCRInfo(uint8t sid, enUDSNCR ncr)
{
CString info;
switch(ncr) {
case NRCSERVICENOTSUPPORTED:
info.Format(T("SID=0x%x, 协议不支持该服务!"), sid);
break;
case NRCSUBFUNCTIONNOTSUPPORTED:
info.Format(T("SID=0x%x, 不支持当前请求子功能!"), sid);
break;
// ... 省略其他负响应类型处理 ...
default:
info.Format(_T("SID=0x%x, 该服务响应不是协议里已支持的!"), sid);
break;
}
mStaticUpgradeInfo.SetWindowTextW(info);
}
根据UDS负响应码(NCR)解析具体错误原因,显示在mStaticUpgradeInfo控件,指导用户排查问题。
2.3 LIN通信驱动(CLin.cpp)
CLin类封装LIN设备连接与数据收发逻辑,核心函数包括:
2.3.1 设备连接(connect_toomoss())
int CLin::connect_toomoss(int usbDevIdx)
{
// 扫描USB2XX设备(图莫斯设备基于USB2XX芯片)
int ret = USB_ScanDevice(mDeviceHandle);
if (ret <= 0) {
printf("No device connected!\n");
return -1;
}
// 输出设备信息
printf("当前连接的设备数为:%d\n", ret);
for (int i = 0; i < ret; i++) {
printf("DevHandle[%d] = %d\n", i, mDeviceHandle[i]);
}
// 打开指定设备(usbDevIdx=0,默认第一个设备)
bool st = USB_OpenDevice(mDeviceHandle[usbDevIdx]);
if (!st) {
printf("Open device error!\n");
return -1;
}
return 0;
}
通过USBScanDevice()扫描USB总线获取图莫斯设备句柄,存储在mDeviceHandle数组;再通过USBOpenDevice()打开指定设备,返回0表示连接成功。
2.3.2 LIN初始化(init_lin())
int CLin::init_lin(int usbDevIdx, int linChannel, enLIN_MODE mode, int rate)
{
int ret = LIN_EX_Init(mDeviceHandle[usbDevIdx], linChannel, rate, mode);
if (ret != LIN_EX_SUCCESS) {
printf("Config Lin failed!\n");
return -1;
}
return 0;
}
调用LINEXInit()配置LIN总线参数:设备句柄、通道号(默认0)、波特率、工作模式,返回LINEXSUCCESS表示初始化成功。
2.3.3 带响应的发送(upgrade_send_resp())
int CLin::upgrade_send_resp(uint8_t nodeAddr, uint8_t* pSendData, int sendLen, uint8_t* pRespData)
{
int usbDevIdx = 0, linChannel = 0;
LIN_UDS_ADDR UDSAddr;
UDSAddr.CheckType = 0; // 校验类型:默认
UDSAddr.NAD = nodeAddr; // 节点地址
UDSAddr.ReqID = 0x3C; // 请求ID
UDSAddr.ResID = 0x3D; // 响应ID
UDSAddr.STmin = 10; // 最小发送间隔(10ms)
// 发送UDS请求
int ret = LIN_UDS_Request(mDeviceHandle[usbDevIdx], linChannel, &UDSAddr, pSendData, sendLen);
if (ret != LIN_UDS_OK) return -1;
// 接收UDS响应(超时100ms)
uint8_t resData[256];
ret = LIN_UDS_Response(mDeviceHandle[usbDevIdx], linChannel, &UDSAddr, resData, 100);
if (ret <= 0) return -2;
// 从缓冲区提取响应数据
LIN_EX_MSG LINMsg[1000];
ret = LIN_UDS_GetMsgFromUDSBuffer(mDeviceHandle[usbDevIdx], linChannel, LINMsg, 100);
if (ret <= 0) return -3;
// 输出LIN消息详情(同步字节、PID、数据、校验等)
for (int i = 0; i < ret; i++) {
printf("[%d]%s SYNC[%02X] PID[%02X] ", i, MSGTypeStr[LINMsg[i].MsgType], LINMsg[i].Sync, LINMsg[i].PID);
for (int j = 0; j < LINMsg[i].DataLen; j++) {
printf("%02X ", LINMsg[i].Data[j]);
}
printf("[%s][%02X] [%02d:%02d:%02d.%03d]\n",
CKTypeStr[LINMsg[i].CheckType], LINMsg[i].Check,
(LINMsg[i].Timestamp / 3600000) % 60,
(LINMsg[i].Timestamp / 60000) % 60,
(LINMsg[i].Timestamp / 1000) % 60,
(LINMsg[i].Timestamp) % 1000);
}
// 拷贝响应数据到输出缓冲区
memcpy(pRespData, &LINMsg[ret-1].Data[0], LINMsg[ret-1].DataLen);
return LINMsg[ret-1].DataLen;
}
该函数是LIN通信核心,实现“发送UDS请求-接收响应-解析数据”的完整流程:
- 配置UDS地址参数(
LINUDSADDR),包括节点地址、请求/响应ID、超时时间 - 调用
LINUDSRequest()发送UDS指令 - 调用
LINUDSResponse()接收MCU端响应(超时100ms) - 调用
LINUDSGetMsgFromUDSBuffer()从设备缓冲区提取LIN消息 - 输出消息详情(同步字节、PID、数据、校验值、时间戳)
- 将最后一条消息的Data字段拷贝到
pRespData,返回数据长度
三、MCU端核心代码功能解析
3.1 系统初始化(system_fm33fg0xxa.c)
SystemInit()函数是MCU端启动入口,完成时钟配置、外设初始化与安全配置,代码逻辑分为6部分:
- PAD时钟使能:
cpp
CMU->PCLKCR1 |= (0x1U << 7);
使能PAD(引脚)操作时钟,确保GPIO、LIN等外设引脚正常工作。
- 独立看门狗(IWDT)配置:
cpp
CMU->PCLKCR1 |= (0x1U << 5); // 使能IWDT时钟
IWDT->SERV = 0x12345A5AU; // 喂狗(防止初始化过程中复位)
IWDT->CR |= 0x07; // 配置看门狗溢出时间(最长)
IWDT->SERV = 0x12345A5AU; // 再次喂狗
使能IWDT时钟,配置溢出时间并喂狗,确保系统初始化过程中不被看门狗复位。
- LSCLK(低速时钟)配置:
cpp
#ifdef USELSCLKCLOCKSRCXTLF
// 配置XTLF(外部低频晶振)引脚
GPIOI->FCR |= 0x0003C000U;
// 使能XTLF,等待稳定
CMU->XTLFCR = 0xE511UL;
while((CMU->XTLFCR & 0x20UL) != 0x20UL && temp++ < 0x6FFFFFFUL) {
if((IWDT->CNT) > 3000UL) IWDT->SERV = 0x12345A5AU;
}
CMU->LSCLKSEL = 0xAA; // LSCLK源为XTLF
#else
CMU->LSCLKSEL = 0x55; // LSCLK源为RCLP(内部低频RC)
#endif
根据宏定义选择LSCLK源(XTLF或RCLP),配置引脚并等待时钟稳定,同时定期喂狗。
- PDR(电源-down)与BOR(欠压复位)配置:
cpp
RMU->PDRCR = 0x07; // 使能所有PDR域
RMU->BORCR = 0x01; // 配置BOR阈值(防止低压工作)
配置电源管理参数,确保芯片在合理电压范围内工作,避免低压导致的异常。
- 调试模式配置:
cpp
DBG->CR = 0x03; // 调试模式下禁用IWDT、WWDT,使能其他外设
配置调试模式下的外设行为,方便调试过程中观察升级流程。
- 时钟校准与DMA配置:
`cpp
// 加载RCHF(内部高频RC)与RCLP(内部低频RC)校准值
CMU->RCHFTR = RCHF8MTRIM;
CMU->RCLPTR = RCLPTRIM;
// 配置DMA通道7(Flash->RAM传输)
CMU->PCLKCR2 |= (0x1U << 4); // 使能DMA时钟
DMA->CH7CR |= (0x1U << 10); // 配置通道7为Flash到RAM模式

CMU->PCLKCR2 &= ~(0x1U << 4); // 关闭DMA时钟
// 更新系统核心时钟
SystemCoreClockUpdate();
IWDT->SERV = 0x12345A5AU; // 最后一次喂狗
`

加载工厂校准的时钟参数,确保时钟精度;配置DMA通道7用于后续Flash数据传输;更新系统核心时钟值,完成初始化。
3.2 Flash分区与升级支撑
MCU端Flash采用物理双分区设计,A分区为当前运行固件,B分区为升级备用分区,核心参数与上位机配置一致:
- A分区:起始地址0x00000000,大小0x00008800(34KB)
- B分区:起始地址0x00008800,大小0x00007400(29KB)
Flash操作通过芯片专用驱动实现,支持以下核心功能:
- 分区擦除:接收上位机UDS擦除指令(0x31 01 FF 00)后,按页擦除B分区,确保无残留数据
- 数据写入:接收上位机UDS数据传输指令(0x36+包ID)后,将64字节数据写入B分区指定地址
- CRC校验:接收上位机UDS校验指令(0x31 01 02 02)后,计算B分区所有字节的累加和,与上位机发送的CRC对比,返回校验结果
- 分区切换:接收上位机UDS重启指令(0x11 03)后,复位系统,启动时检测B分区升级标志与CRC结果,若有效则将B分区数据拷贝至A分区,完成升级
四、系统核心技术特性总结
- 双分区安全升级:采用物理双分区设计,升级过程不影响A分区运行固件,升级失败可回退,避免设备砖化
- 标准化UDS协议:遵循ISO 14229协议,支持会话控制、安全访问、数据传输等核心服务,兼容性强
- 可靠通信机制:LIN总线支持重传(最多5次)与超时处理(100ms),升级包传输支持10次重传,确保数据可靠
- 完整参数校验:节点地址、分区地址、升级包大小等参数均需通过16进制校验,避免配置错误
- 详细日志输出:上位机实时输出发送/接收数据、升级状态、错误原因,便于调试与问题定位
- 安全访问控制:通过“种子-密钥”机制解锁敏感操作,密钥采用种子按位取反(实际应用可扩展为AES加密),防止未授权升级
更多推荐
所有评论(0)