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个关键步骤:

  1. 公共控件初始化
    cpp
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);
    InitCtrls.dwICC = ICCWIN95CLASSES;
    InitCommonControlsEx(&InitCtrls);

    注册Windows公共控件(如进度条、按钮),确保MFC控件正常显示与交互。
  1. MFC环境配置
    - 启用控件容器:AfxEnableControlContainer(),支持ActiveX控件与复合控件
    - 创建Shell管理器:CShellManager *pShellManager = new CShellManager;,支持Shell树视图/列表视图控件
    - 设置视觉管理器:CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));,适配Windows原生主题
  1. 注册表配置
    cpp
    SetRegistryKey(_T("应用程序向导生成的本地应用程序"));

    定义应用程序在注册表中的存储路径,用于保存用户配置(如上次升级参数)。
  1. 主对话框创建与显示
    cpp
    CLinUdsUpgradeDlg dlg;
    mpMainWnd = &dlg;
    INT
    PTR nResponse = dlg.DoModal();

    创建主升级对话框并以模态方式显示,阻塞后续代码执行直至对话框关闭。
  1. 对话框关闭处理
    - 若用户点击“确定”(nResponse == IDOK):预留后续业务逻辑扩展(如升级结果统计)
    - 若用户点击“取消”(nResponse == IDCANCEL):直接关闭对话框
    - 若对话框创建失败(nResponse == -1):输出调试日志,提示MFC控件配置错误
  1. 资源清理
    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部分:

  1. 系统菜单配置
    cpp
    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != nullptr) {
    CString strAboutMenu;
    strAboutMenu.LoadString(IDSABOUTBOX);
    if (!strAboutMenu.IsEmpty()) {
    pSysMenu->AppendMenu(MF
    SEPARATOR);
    pSysMenu->AppendMenu(MFSTRING, IDMABOUTBOX, strAboutMenu);
    }
    }

    为系统菜单(窗口右上角“?”)添加“关于”菜单项,点击后弹出CAboutDlg对话框。
  1. 窗口图标设置
    cpp
    SetIcon(mhIcon, TRUE); // 大图标
    SetIcon(m
    hIcon, FALSE); // 小图标

    加载应用程序图标(IDR_MAINFRAME),设置窗口标题栏与任务栏图标。
  1. UI默认值配置
    cpp
    mSoftwareVer.SetWindowTextW(T("未获取")); // 软件版本初始值
    mEditUpgradeNodeAddr.SetWindowTextW(
    T("7f")); // 节点地址默认0x7F
    mBzoneStartAddr.SetWindowTextW(T("00008800")); // B分区起始地址
    mBzoneSize.SetWindowTextW(
    T("00007400")); // B分区大小
    mEditLog.SetLimitText(0); // 日志窗口无长度限制

    初始化控件显示值,确保用户无需手动配置即可启动基础升级流程。
  1. LIN设备连接
    cpp
    int ret = CLin::connecttoomoss(devidx); // 连接图莫斯设备(devidx=0,默认第一个设备)
    if (ret < 0) {
    MessageBox(
    T("请用图莫斯工具连接LIN!"), T("提示"), MBOK | MBICONWARNING);
    SendMessage(WM
    CLOSE);
    } else {
    ret = CLin::initlin(devidx, channelidx, LINSLAVEMODE, 19200);
    if (ret < 0) {
    MessageBox(
    T("LIN 连接失败,请重试!"), T("提示"), MBOK | MBICONWARNING);
    SendMessage(WM
    CLOSE);
    }
    }

    先扫描USB总线获取图莫斯设备句柄,再初始化LIN总线参数(从机模式、19200bps波特率),连接失败则弹窗提示并关闭应用。
2.2.2 升级文件加载(OnBnClickedButtonLoadfile())

用户点击“加载文件”按钮触发该函数,实现.bin格式升级文件的选择与信息解析:

  1. 文件对话框创建
    cpp
    TCHAR szFilter[] = T("BIN file(.bin)|.bin|所有文件(.)|.||");
    CFileDialog fileDlg(TRUE,
    T("bin"), T("*.bin"), OFNHIDEREADONLY, szFilter, this);
    fileDlg.mofn.lpstrTitle = T("打开升级文件");

    创建文件选择对话框,仅显示.bin文件与所有文件,标题为“打开升级文件”。
  1. 文件信息解析与显示
    `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控件中,并更新全局变量mUpgradeFileTotalSizemUpgradeFilePath

2.2.3 软件版本获取(OnBnClickedButtonGetSwVer())

用户点击“获取软件版本”按钮触发该函数,通过UDS协议读取MCU端当前固件版本:

  1. 节点地址校验
    cpp
    CString upgradeNodeAddr;
    mEditUpgradeNodeAddr.GetWindowText(upgradeNodeAddr);
    mUpgradeNodeAddr = HexToDem(upgradeNodeAddr);
    if (mUpgradeNodeAddr <= 0) {
    MessageBox(T("节点地址无效,请输入有效节点地址!"), T("提示"), MBOK | MBICONINFORMATION);
    return;
    }

    读取用户输入的节点地址,通过HexToDem()函数将16进制字符串转为十进制,若结果≤0则提示无效。
  1. 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())

用户点击“开始升级”按钮触发该函数,完成升级前参数校验并启动升级线程:

  1. 参数完整性校验
    - 节点地址校验:同“获取软件版本”逻辑,确保地址有效
    - 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()会弹窗提示
  1. 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; // 升级计时清零

    禁用所有可配置控件,防止升级过程中参数被修改,同时初始化进度条与日志。
  1. 升级线程启动
    cpp
    SetTimer(3, 1000, NULL); // 启动定时器(ID=3,1秒触发一次,用于升级计时)
    pCWinThread = AfxBeginThread(UpgradeProcessThreadFunc, (LPVOID)this);

    启动定时器记录升级耗时,通过AfxBeginThread()创建升级线程(UpgradeProcessThreadFunc),传入当前对话框指针作为参数。
2.2.5 升级核心线程(UpgradeProcessThreadFunc())

该线程是升级流程的核心,实现从“会话切换”到“固件传输”再到“系统重启”的全流程,代码逻辑可拆解为13个关键步骤:

  1. 升级文件加载与预处理
    `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则跳转至资源清理步骤;同时计算升级包总数,后续按包传输。

  1. 切换至扩展会话
    cpp
    pUpgrade->mStaticUpgradeInfo.SetWindowTextW(T("切换到扩展会话..."));
    uint8
    t 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开头),则提示错误并退出。
  1. 停用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存储,避免升级过程中因硬件状态变化触发故障码。
  1. 切换至编程会话
    cpp
    pUpgrade->mStaticUpgradeInfo.SetWindowTextW(_T("切换到编程会话..."));
    reqData[0] = 0x10; reqData[1] = 0x02; // 子功能=0x02,切换至编程会话
    ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 2, respData);
    // 错误处理逻辑同上

    发送UDS指令(0x10 02)切换至编程会话,进入固件更新模式。
  1. 请求安全种子
    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] != NEGATIVE
    RSP) {
    // 提取种子(响应数据第4-7字节为32位种子)
    securityseed = respData[4] << 24 | respData[5] << 16 | respData[6] << 8 | respData[7];
    } else {
    // 处理负响应(如安全访问被拒绝)
    pUpgrade->ShowNCRInfo(respData[3], (enUDS
    NCR)respData[4]);
    goto FINISH;
    }

    发送UDS安全访问指令(0x27 01)请求种子,若成功则提取32位种子值,用于后续密钥计算。
  1. 发送安全密钥
    cpp
    pUpgrade->mStaticUpgradeInfo.SetWindowTextW(T("发送安全秘钥..."));
    uint32
    t securitykey = ~securityseed; // 密钥 = 种子按位取反
    reqData[0] = 0x27; reqData[1] = 0x02; // 子功能=0x02,发送密钥
    reqData[2] = securitykey >> 24; // 密钥第1字节
    reqData[3] = security
    key >> 16; // 密钥第2字节
    reqData[4] = securitykey >> 8; // 密钥第3字节
    reqData[5] = security
    key & 0xff; // 密钥第4字节
    ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 6, respData);
    // 错误处理逻辑同上,若密钥不匹配则提示“密钥无效”

    计算密钥(种子按位取反),发送UDS指令(0x27 02)将密钥传输至MCU,验证通过后解锁敏感操作权限。
  1. 检查升级条件
    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操作要求。
  1. 擦除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分区擦除操作。
  1. 请求下载
    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] != NEGATIVE
    RSP) {
    // 校验升级包大小(响应数据第3字节高4位表示包大小字节数)
    uint8t pkglenbyte = respData[3] >> 4;
    uint8
    t pkglen = 0;
    for(uint8
    t i = 0; i < pkglenbyte; i++) {
    pkglen = (respData[4 + i] << ((pkglenbyte - i - 1) * 8));
    }
    if((pkg
    len-2) != UPGRADEPKGSIZE) {
    CString info;
    info.Format(T("升级包大小不匹配: %d!"), pkglen);
    pUpgrade->mStaticUpgradeInfo.SetWindowTextW(info);
    goto FINISH;
    }
    }

    发送UDS请求下载指令(0x34),协商升级包大小;MCU端返回支持的包大小,需与上位机配置的64字节/包一致,否则升级终止。
  1. 循环发送升级包
    cpp
    ret = pUpgrade->LoopSendPkgData(pFileData, pkgtotalnum);
    if(ret < 0) goto FINISH;

    调用LoopSendPkgData()函数循环发送所有升级包,每包64字节,支持重传机制(最多10次),实时更新进度条。
  1. 请求传输退出
    cpp
    pUpgrade->mStaticUpgradeInfo.SetWindowTextW(_T("请求传输退出..."));
    reqData[0] = 0x37; // UDS请求传输退出指令(SID=0x37)
    ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 1, respData);
    // 错误处理逻辑同上

    发送UDS指令(0x37)结束固件传输,告知MCU端所有包已发送完成。
  1. 发送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] = (crc
    sum >> 8) & 0xff; // CRC高字节
    reqData[5] = crcsum & 0xff; // CRC低字节
    ret = pUpgrade->SendDiagFrame(pUpgrade->mUpgradeNodeAddr, reqData, 6, respData);
    if (ret > 0 && respData[2] != NEGATIVE
    RSP) {
    // 校验通过,更新进度条与状态
    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%。
  1. 系统重启与分区切换
    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] != NEGATIVE
    RSP) {
    upgradeInfo.Format(_T("升级完成,请点击获取软件版本按钮确认软件版本号!"));
    pUpgrade->mStaticUpgradeInfo.SetWindowTextW(upgradeInfo);
    }
    }

    弹窗提示用户是否重启,点击“确定”后发送UDS重启指令(0x11 03),MCU端复位后将B分区固件拷贝至A分区,完成升级。
2.2.6 辅助功能函数
  1. 诊断帧发送(SendDiagFrame())
    `cpp
    int CLinUdsUpgradeDlg::SendDiagFrame(uint8t nodeAddr, uint8t pFrameData, int frameLen, uint8t pRespData)
    {
    // 输出发送日志
    CString send
    log;
    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(send
    log);

// 最多尝试5次发送

int ret = -1, cnt = 5;

do {

ret = CLin::upgradesendresp(nodeAddr, pFrameData, frameLen, pRespData);

} while (ret <= 0 && cnt-- > 0);

return ret;

}

`

封装LIN通信发送逻辑,输出发送日志至mEditLog控件,支持最多5次重发,确保指令可靠送达。

  1. 响应数据显示(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()输出至日志窗口,便于调试。
  1. 负响应解析(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请求-接收响应-解析数据”的完整流程:

  1. 配置UDS地址参数(LINUDSADDR),包括节点地址、请求/响应ID、超时时间
  2. 调用LINUDSRequest()发送UDS指令
  3. 调用LINUDSResponse()接收MCU端响应(超时100ms)
  4. 调用LINUDSGetMsgFromUDSBuffer()从设备缓冲区提取LIN消息
  5. 输出消息详情(同步字节、PID、数据、校验值、时间戳)
  6. 将最后一条消息的Data字段拷贝到pRespData,返回数据长度

三、MCU端核心代码功能解析

3.1 系统初始化(system_fm33fg0xxa.c)

SystemInit()函数是MCU端启动入口,完成时钟配置、外设初始化与安全配置,代码逻辑分为6部分:

  1. PAD时钟使能
    cpp
    CMU->PCLKCR1 |= (0x1U << 7);

    使能PAD(引脚)操作时钟,确保GPIO、LIN等外设引脚正常工作。
  1. 独立看门狗(IWDT)配置
    cpp
    CMU->PCLKCR1 |= (0x1U << 5); // 使能IWDT时钟
    IWDT->SERV = 0x12345A5AU; // 喂狗(防止初始化过程中复位)
    IWDT->CR |= 0x07; // 配置看门狗溢出时间(最长)
    IWDT->SERV = 0x12345A5AU; // 再次喂狗

    使能IWDT时钟,配置溢出时间并喂狗,确保系统初始化过程中不被看门狗复位。
  1. 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),配置引脚并等待时钟稳定,同时定期喂狗。
  1. PDR(电源-down)与BOR(欠压复位)配置
    cpp
    RMU->PDRCR = 0x07; // 使能所有PDR域
    RMU->BORCR = 0x01; // 配置BOR阈值(防止低压工作)

    配置电源管理参数,确保芯片在合理电压范围内工作,避免低压导致的异常。
  1. 调试模式配置
    cpp
    DBG->CR = 0x03; // 调试模式下禁用IWDT、WWDT,使能其他外设

    配置调试模式下的外设行为,方便调试过程中观察升级流程。
  1. 时钟校准与DMA配置
    `cpp
    // 加载RCHF(内部高频RC)与RCLP(内部低频RC)校准值
    CMU->RCHFTR = RCHF8MTRIM;
    CMU->RCLPTR = RCLP
    TRIM;

// 配置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操作通过芯片专用驱动实现,支持以下核心功能:

  1. 分区擦除:接收上位机UDS擦除指令(0x31 01 FF 00)后,按页擦除B分区,确保无残留数据
  2. 数据写入:接收上位机UDS数据传输指令(0x36+包ID)后,将64字节数据写入B分区指定地址
  3. CRC校验:接收上位机UDS校验指令(0x31 01 02 02)后,计算B分区所有字节的累加和,与上位机发送的CRC对比,返回校验结果
  4. 分区切换:接收上位机UDS重启指令(0x11 03)后,复位系统,启动时检测B分区升级标志与CRC结果,若有效则将B分区数据拷贝至A分区,完成升级

四、系统核心技术特性总结

  1. 双分区安全升级:采用物理双分区设计,升级过程不影响A分区运行固件,升级失败可回退,避免设备砖化
  2. 标准化UDS协议:遵循ISO 14229协议,支持会话控制、安全访问、数据传输等核心服务,兼容性强
  3. 可靠通信机制:LIN总线支持重传(最多5次)与超时处理(100ms),升级包传输支持10次重传,确保数据可靠
  4. 完整参数校验:节点地址、分区地址、升级包大小等参数均需通过16进制校验,避免配置错误
  5. 详细日志输出:上位机实时输出发送/接收数据、升级状态、错误原因,便于调试与问题定位
  6. 安全访问控制:通过“种子-密钥”机制解锁敏感操作,密钥采用种子按位取反(实际应用可扩展为AES加密),防止未授权升级
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐