基于图莫斯的CAN UDS升级上位机-LabVIEW版本(十五):总结篇

前言

在开发基于图莫斯CAN工具的LabVIEW UDS升级上位机的过程中,我们经历了一个完整的开发周期------从项目创建、协议栈实现、界面设计,到最终的打包部署。在这个过程中,遇到了各种类型的问题,也积累了丰富的经验。

本文是这一系列文章的总结篇 ,将整个开发过程中遇到的LabVIEW开发层面的问题点、解决方案和最佳实践系统性地整理出来,形成一份完整的避坑指南与知识库


一、项目架构概览

1.1 技术栈

层级 技术选型 说明
开发平台 LabVIEW 2020(32位) 图形化编程,快速开发
硬件层 图莫斯USB转CAN适配器 支持CAN2.0,提供DLL接口
驱动层 图莫斯USB2XXX库 通过DLL调用实现设备控制
协议层 ISO 14229(UDS) 10/11/27/28/31/34-37/85/2E等服务
应用层 LabVIEW上位机 文件解析、刷写控制、日志记录、进度显示

1.2 核心子VI列表

子VI 功能 依赖
TOOMOSS_OpenDev(CAN).vi 设备打开与句柄管理 图莫斯DLL
TOOMOSS_SendAndWaitResp.vi 核心通信基座 图莫斯DLL
TOOMOSS_SID10_RequestSession.vi 会话切换(0x10) 通信基座
TOOMOSS_SID27_SecurityAccess.vi 安全访问(0x27) 通信基座 + Seed-Key DLL
TOOMOSS_SID34-36-37_DownloadFlow.vi 下载流程(0x34/36/37) 通信基座
TOOMOSS_SID11_EcuReset.vi ECU复位(0x11) 通信基座
TOOMOSS_SID28_CommunicationControl.vi 通信控制(0x28) 通信基座
TOOMOSS_SID85_ControlDTCSetting.vi DTC控制(0x85) 通信基座
TOOMOSS_SID2E_WriteDataByID.vi 写入数据(0x2E) 通信基座
TOOMOSS_SID31_RoutineControl.vi 例程控制(0x31) 通信基座
LogManager.vi 日志管理 文件I/O
Main.vi 主界面与流程编排 所有子VI

二、DLL调用与配置

2.1 DLL加载错误126(找不到依赖模块)

现象:在客户电脑上运行程序,DLL加载失败,错误代码126。

原因

  • 缺少VC++运行库(vcruntime140.dll、msvcp140.dll等)

  • DLL依赖的其他系统库缺失

  • 开发环境有Visual Studio的PATH环境变量,客户机器没有

解决方案

  1. 在客户机上安装 Visual C++ Redistributable(2015-2022)

  2. 或将项目运行库设置为 多线程 (/MT) 进行静态编译(需要重新编译DLL)

  3. 使用Dependency Walker或Dependencies工具检查DLL依赖项

检查方法

在客户机上使用Dependencies工具打开DLL,红色问号标记的就是缺失的依赖项。


2.2 DLL加载错误193(位数不匹配)

现象:加载DLL时报错193。

原因:DLL的位数与调用进程(LabVIEW)的位数不一致。

解决方案

  • 确认LabVIEW位数:帮助 → 关于LabVIEW

  • 32位LabVIEW使用x86的DLL,64位LabVIEW使用x64的DLL

  • 测试时使用对应位数的PowerShell

验证PowerShell位数

powershell

复制代码
[Environment]::Is64BitProcess

返回True为64位,False为32位。

32位PowerShell路径

复制代码
C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe

2.3 引用参数(oSize)第一次返回0

现象 :调用GenerateKeyEx DLL函数时,密钥数据正确输出(4字节),但oSize第一次运行返回0,后续运行正常。

原因unsigned int &oSize 是输入输出参数。DLL期望在调用前传入缓冲区大小(即iKeyArraySize)作为输入值,而LabVIEW控件初始值为0时,DLL认为缓冲区无效,拒绝写入长度。

CLN参数配置

配置项 设置
类型 数值
数据类型 指向无符号32位整数的指针
传递方式 指针

解决方案

  • 在调用DLL前,将oSize的输入值设为iKeyArraySize(如4)

  • 使用属性节点强制将正确的值写入控件,再传递给CLN节点

  • 或将控件的默认值直接设为iKeyArraySize

连线方式 :使用同一个数值控件同时作为输入和输出,不要分别连接到两个不同的控件。


2.4 调用规范不匹配导致崩溃

现象:调用DLL时LabVIEW崩溃或无响应。

原因:CLN节点中的调用规范与DLL的导出约定不一致。

解决方案

  • 在CLN配置的函数 选项卡中确认调用规范

  • 通常Windows DLL使用stdcall (WINAPI)

  • C/C++导出函数使用C

  • 不确定时两种都尝试


2.5 CLN路径配置

现象:CLN节点找不到DLL。

推荐做法

  • 勾选CLN节点的 "在程序框图中指定路径"

  • 使用 "应用程序目录" 函数动态构建路径:

labview

复制代码
[应用程序目录] → [组合路径("Libs\USB2XXX.dll")] → [CLN节点.path]
  • 避免使用硬编码的绝对路径

三、事件结构与数据流

3.1 修改数值后第一次点击按钮读旧值

现象:修改数值输入控件后直接点击按钮,按钮事件中读取到的仍是旧值,第二次点击才更新。

原因:数值输入控件的"值"只有在用户确认输入后才会提交(按Enter键、点击其他控件、Tab切换焦点等)。直接点击按钮时,数值控件尚未失去焦点,新值未写入控件的数据缓冲区。

解决方案 :在按钮事件分支内使用 "值(信号)" 属性节点

  • 该属性强制读取控件的最新值

  • 操作:右键控件 → 创建 → 属性节点 → "值(信号)"


3.2 程序修改控件值不触发事件

现象:通过属性节点或局部变量修改控件值后,事件结构中的"值改变"事件没有触发。

原因 :事件结构只捕获由用户直接操作前面板产生的值改变事件,程序化修改不会触发。

解决方案

  • 使用 "值(信号)" 属性节点代替普通"值"属性节点

  • "值(信号)"会触发该控件的"值改变"事件

  • 注意:避免在事件处理中再次修改该控件导致递归


3.3 修改enum选项不立即生效

现象:下拉列表选择新选项后,依赖该值的代码未更新。

原因:键盘切换选项时值未提交(未按Enter或点击外部)。

解决方案

  • 使用 "鼠标释放" 事件代替"值改变"事件

  • 或勾选控件属性中的 "按回车键后更新值"


四、条件结构与移位寄存器

4.1 隧道未赋值

现象:条件结构报错"隧道未赋值"。

原因 :条件结构的输出隧道必须在所有分支中都被赋值,某个分支缺少连线到该隧道。

解决方案

  • 确保每个分支都为同一个输出隧道提供数据

  • 在不需要输出有效数据的分支中,连接该数据类型的默认值(如空簇、0、空字符串等)

  • 或右键隧道选择 "未连线时使用默认值"


4.2 部分选择器无默认分支

现象:条件结构报错"部分选择器无默认分支"。

原因:条件结构的选择器(如字符串、枚举)没有覆盖所有可能的值。

解决方案

  • 右键条件结构 → "在后面添加分支" → 输入 Default

  • 在Default分支中处理未覆盖的情况(如显示错误提示)


4.3 移位寄存器初始化与清零

现象:移位寄存器在循环多次执行后保留了上一次的值。

解决方案

初始化

  • 在循环外部连线到移位寄存器的左侧输入端口,设置初始值

  • 数值类型初始化为0,数组类型初始化为空数组

运行时清零

  • 在循环内部,将常量(如空数组、0)连接到移位寄存器的右侧输出端口

  • 这样当前值被覆盖,实现清零


4.4 For循环中数组子集连不上

现象:将数组传入For循环后,无法连接到"数组子集"函数的数组输入端。

原因 :For循环的"数据隧道"默认启用了索引功能,将数组的每个元素(标量)逐一传入循环,而非整个数组。

解决方案

  • 右键点击For循环边框上的数据隧道,选择 "禁用索引"

  • 这样进入循环的是完整数组,可以用于"数组子集"等操作

两种模式对比

隧道模式 进入循环的数据 适用场景
启用索引(默认) 数组的单个元素 对每个元素依次处理
禁用索引 整个数组 需要对数组整体操作

4.5 条件结构中子VI的执行顺序

问题:两个子VI在条件结构中上下排列,先执行哪个?

答案不确定。LabVIEW是数据流驱动,位置不决定顺序。

场景 执行顺序
无连线,上下排列 不确定(由编译器调度)
有数据连线 上游先执行,下游后执行(数据流决定)
在不同分支中 取决于条件结构的选择器值
用平铺式顺序结构包裹 按帧的顺序执行

建议 :使用错误线串联平铺式顺序结构明确控制顺序。


五、打包与部署

5.1 生成EXE后DLL找不到

现象:开发环境中运行正常,但生成EXE后报错找不到DLL。

原因 :Application Builder默认将所有"始终包括"的文件复制到data目录,但可能不保留子文件夹结构,导致相对路径失效。

解决方案

方案一(推荐) :在代码中使用 "应用程序目录" 函数动态构建路径

labview

复制代码
[应用程序目录] → [组合路径("Libs\USB2XXX.dll")] → [CLN节点.path]

方案二:在EXE生成配置中手动保留文件夹结构

  • 源文件选项卡中,在右侧目标视图中手动创建文件夹

  • 将DLL文件拖入对应的文件夹

方案三:统一Libs目录管理

  • 将所有DLL放在Libs目录下

  • 打包时将Libs文件夹添加到"始终包括"


5.2 安装后DLL仍找不到

现象:生成的EXE可以正常运行,但安装后的程序却找不到DLL。

原因 :安装程序的"源文件"配置中,文件夹结构未被正确保留,DLL被平铺到了data根目录。

解决方案

  • 在安装程序属性的源文件 选项卡中,将Libs文件夹整体拖拽 到目标视图的data文件夹上

  • 确保文件夹结构在安装后保持不变

  • 检查"附加安装程序"中是否勾选了VC++运行库

验证方法

安装后在客户机上检查目录结构:

text

复制代码
安装目录/
├── 你的程序.exe
└── data/
    └── Libs/
        ├── USB2XXX.dll
        └── 其他DLL...

5.3 EXE界面过大

现象:生成的EXE窗口很大,但前面板编辑时没那么大。

原因:VI属性中的"窗口大小"设置与编辑时的窗口尺寸不一致。

解决方案

  1. 打开主VI前面板,手动拖拽到合适大小

  2. 保存VI

  3. 文件 → VI属性 → 窗口外观 → 自定义

  4. 设置 "窗口大小""调整为大小"

  5. 勾选 "窗口大小与前面板一致"

  6. (可选)取消勾选 "允许用户调整窗口大小"

  7. 重新生成EXE


5.4 EXE窗口标题修改

现象:生成的EXE窗口标题显示为VI文件名或默认名称。

解决方案(推荐):

  1. 打开主VI前面板

  2. 文件 → VI属性 → 窗口外观 → 自定义

  3. "窗口标题" 输入框中输入自定义标题

  4. 保存VI,重新生成EXE

动态修改 :在程序框图中使用 "前面板窗口" → "标题" 属性节点(写入模式)实现运行时修改。


5.5 第三方DLL打包

场景:依赖多个第三方DLL(如图莫斯库、Hex解析库),打包后路径失效。

解决方案

  1. 将所有DLL统一到Libs目录

  2. 在项目中将Libs文件夹添加到"始终包括"

  3. 代码中使用应用程序目录动态构建路径

  4. 打包时确保Libs文件夹结构在目标视图中保留

目录结构示例

text

复制代码
项目根目录/
├── Main.vi
└── Libs/
    ├── USB2XXX.dll
    ├── libusb-1.0.dll
    ├── HexParser.dll
    └── CRC32.dll

六、路径与文件管理

6.1 文件名包含非法字符

现象:文件操作报错"输入参数无效",错误码0x1。

原因 :文件名中包含Windows禁止的字符(\ / : * ? " < > |)。

常见场景 :日志文件名包含时间戳,格式为"%H:%M:%S"时包含冒号:

解决方案

  • 修改时间戳格式,将:替换为-_

  • 例如:"%H:%M:%S""%H-%M-%S""%H_%M_%S"

  • 或在拼接后使用"替换字符串"函数处理


6.2 文件夹不存在导致文件创建失败

现象打开/创建/替换文件报错"文件未找到",错误码7。

原因:目标文件夹不存在,而该函数不会自动创建文件夹。

解决方案

  • 在打开文件前调用 "创建文件夹" 函数

  • 或在使用打开/创建/替换文件时启用 "创建路径" 选项

推荐做法

复制代码
[文件夹路径] → [检查文件和文件夹是否存在]
                   ↓ 不存在
               [创建文件夹]
                   ↓
               [打开/创建/替换文件]

6.3 引用句柄有效性判断

问题:如何判断文件引用(Refnum)是否有效?

正确方法

  • 使用 "非数字/路径/引用句柄?" 函数(编程 → 比较

  • 输入为文件引用,输出为布尔值:True=无效,False=有效

错误方法

  • 使用等于?比较无效文件引用常量(不可靠,无效引用有多种形式)

应用场景:在日志管理子VI中判断文件是否已打开,决定是否需要创建新文件。


七、控件与界面

7.1 进度条颜色修改不起作用

现象:使用"滑块颜色 → 前景色"属性节点修改进度条颜色无效。

原因 :"滑块颜色:前景色"控制的是滑块本身 (滑动块)的颜色,而非进度条的填充色

正确方法

  • 使用 颜色[4] 属性节点控制进度条填充色

  • 输入为包含2个U32颜色值的簇[假状态颜色, 真状态颜色]

  • 元素0 = 进度条为空时的颜色,元素1 = 进度条填充时的颜色

  • 确保属性节点为 "写入" 模式

操作步骤

  1. 右键进度条 → 创建 → 属性节点 → 颜色[4]

  2. 右键属性节点 → "更改为写入"

  3. 创建簇常量,包含2个U32颜色值

  4. 连接到属性节点输入端

颜色值格式0x00RRGGBB(如0x00FF0000为红色)


7.2 布尔指示灯颜色修改

现象:修改布尔指示灯颜色失败或无效。

正确方法

  • 使用 颜色[4] 属性节点

  • 输入为包含2个元素的簇(U32颜色值)

  • 元素0 = "假"状态颜色(关闭)

  • 元素1 = "真"状态颜色(打开)

  • 确保属性节点为 "写入" 模式

类型冲突处理

如果报错"簇元素数量或类型不同",检查:

  • 簇是否恰好包含2个元素

  • 每个元素是否为U32类型(右键元素 → 表示法 → U32)


7.3 前面板标签隐藏但程序框图保留

现象:希望控件标签在前面板不显示,但在程序框图上显示。

解决方案

  • 前面板:右键控件 → 显示项 → 取消勾选 "标签"

  • 程序框图:接线端上的标签名称仍然保留显示

  • 如需显示不同文字,使用 "标题" 代替"标签"显示在前面板

区别

属性 显示位置 用途
标签 程序框图接线端 代码识别
标题 前面板 用户界面显示

7.4 进度条显示文字信息

方法一(最简单) :右键进度条 → 显示项"数字显示" ,在格式中设置如Progress: %d%%

方法二(最灵活) :叠加一个字符串显示控件,设置背景透明,显示自定义文本

方法三(效果最好) :自定义控件,将进度条和文本组合后保存为.ctl文件


八、调试方法

8.1 单步调试查看上一步输出

保留连线值

  • 点击程序框图工具栏的 "保留连线值" 图标(灯泡旁边)

  • 执行后悬停鼠标在连线上,会显示最后流过的数据值

历史探针

  • 右键点击连线 → "探针"

  • 在探针窗口中点击"探针选项" → 选择 "历史探针"

  • 可以查看该连线流过的多个历史数据值

高亮执行

  • 点击工具栏的 "灯泡" 图标

  • 数据以慢速动画形式在连线上流动,便于观察

断点 + 探针

  • 在连线上设置断点,程序执行到该处暂停

  • 使用探针查看当前数据值


九、开发最佳实践总结

9.1 路径管理

最佳实践 说明
统一Libs目录 所有第三方DLL集中放在Libs目录
动态路径 使用应用程序目录函数构建路径
相对路径 开发时用相对路径,打包时保持一致
纯英文路径 避免中文和特殊字符

9.2 事件结构使用

最佳实践 说明
鼠标释放事件 按钮优先使用"鼠标释放"代替"值改变"
值(信号)属性 需要程序触发事件时使用
输出隧道 确保每个分支为输出隧道赋值

9.3 DLL调用检查清单

  • □ 位数匹配(32/64位)

  • □ 调用规范正确(stdcall / C)

  • □ 参数类型匹配(指针/值传递)

  • □ 输入输出参数正确初始化

  • □ VC++运行库已安装或静态编译

  • □ CLN已勾选"在程序框图中指定路径"

  • □ 路径中不含中文或特殊字符

9.4 打包检查清单

  • □ 所有DLL已添加到"始终包括"

  • □ 文件夹结构在目标视图中正确保留

  • □ "附加安装程序"中勾选了LabVIEW运行引擎

  • □ 第三方依赖(VC++运行库)已包含

  • □ 代码中使用相对路径或动态路径

  • □ 安装后验证目录结构正确

9.5 通用编码建议

建议 说明
数据流优先 用数据连线控制顺序,不依赖位置
错误线串联 用错误簇控制顺序并传递错误信息
模块化设计 每个UDS服务独立封装为子VI
日志记录 记录所有通信过程便于调试
进度反馈 耗时操作更新进度条
常量化配置 CAN ID、超时时间等配置为常量便于修改

十、快速参考

10.1 常用函数位置

函数 路径
应用程序目录 编程 → 文件I/O → 应用程序目录
组合路径 编程 → 文件I/O → 组合路径
创建文件夹 编程 → 文件I/O → 创建文件夹
非数字/路径/引用句柄? 编程 → 比较 → 非数字/路径/引用句柄?
格式化日期/时间字符串 编程 → 定时 → 格式化日期/时间字符串
数组至电子表格字符串转换 编程 → 字符串 → 数组/字符串转换
值(信号)属性节点 右键控件 → 创建 → 属性节点 → 值(信号)

10.2 常用格式字符串

用途 格式
日志文件名 "%Y-%m-%d_%H-%M-%S"
日志时间戳 "%H:%M:%S.%3u"
十六进制显示 "%02X"

10.3 常用颜色值(U32格式)

颜色 颜色值
红色 0x00FF0000
绿色 0x0000FF00
蓝色 0x000000FF
黄色 0x00FFFF00
灰色 0x00808080
黑色 0x00000000
白色 0x00FFFFFF

10.4 常见错误码速查

错误码 含义 常见原因
0x1 输入参数无效 路径含非法字符
7 文件未找到 文件夹不存在
126 找不到依赖模块 缺少VC++运行库
193 位数不匹配 32/64位不一致
1097 DLL调用异常 参数配置错误或调用规范不匹配

结语

LabVIEW UDS升级上位机的开发是一个系统工程,涉及硬件通信、协议实现、UI设计、打包部署等多个环节。本文整理了开发过程中遇到的LabVIEW层面的核心问题与解决方案,希望能为同行提供一份实用的参考。

每个问题都是实际开发中"踩过坑"后的经验总结,每一条建议都是经过验证的有效做法。UDS协议本身并不复杂,但实现一个稳定、可靠的刷写工具需要关注大量细节------从数据流控制到路径管理,从事件处理到打包部署,每一个环节都可能成为"坑"。

希望本文能帮助你少走弯路,更高效地完成你的项目。

相关推荐
电气_空空16 小时前
基于 LABVIEW 的虚拟仪器温度检测系统的设计
labview
fanchenxinok1 天前
基于图莫斯的CAN UDS升级上位机-LabVIEW版本(十四):打包与安装应用程序
labview·安装·升级上位机·uds刷写
fanchenxinok3 天前
基于图莫斯的CAN UDS升级上位机-LabVIEW版本(十):TOOMOSS_SID31_RoutineControl.vi — 例程控制
can·labview·uds升级·升级上位机·uds刷写
kingwebo'sZone3 天前
我写的第一个labview 展示页面程序
labview
电气_空空3 天前
基于 LabVIEW的数据采集与信号处理系统的设计
嵌入式硬件·毕业设计·信号处理·labview
无垠的广袤4 天前
【RA-Eco-RA4M2开发板】步进电机的串口控制和 LabVIEW 数据采集
单片机·嵌入式硬件·labview
fanchenxinok4 天前
基于图莫斯的CAN UDS升级上位机-LabVIEW版本(八):TOOMOSS_SID85_ControlDTCSetting.vi — DTC控制
can·labview·uds刷写·图莫斯
fanchenxinok4 天前
基于图莫斯的CAN UDS升级上位机-LabVIEW版本(十一):LogManager.vi — 日志管理
can·labview·uds刷写·图莫斯
fanchenxinok4 天前
基于图莫斯的CAN UDS升级上位机-LabVIEW版本(九):TOOMOSS_SID2E_WriteDataByID.vi — 写入数据
can·labview·uds刷写