简介:《虚拟仪器技术》实验报告面向测控技术与仪器及相关专业本科生,也适合学习LabVIEW与虚拟仪器开发的读者参考。内容围绕LabVIEW编程平台及DAQ数据采集板、Demo Box等硬件,覆盖温度计、面板控制、鼠标属性、Tab控件、Event语句、帕斯瓦尔定理、双通道信号显示、函数波形发生器、信号处理、脉冲计数、串行通信以及基于TCP、UDP协议的波形传输和数据套接字技术等十四个实验。资源包为1个PDF文件,大小4.22MB,按各实验组织要求、目的、步骤、参考面板与实验记录,并附相关硬件配置和配套演示盒使用说明。已有96人学习。读者可据此熟悉LabVIEW开发环境、数据采集与信号处理流程,理解通信协议实现,并对照前面板、程序框图和实验记录复现关键环节,适合课程实验、自学补强与工程入门训练。
1. 虚拟仪器技术实验报告:从温度计到 DataSocket 的一条完整数据通路
第一次打开 LabVIEW 的人,十个里有八个会先去找输入框和按钮,然后被后面板那片连线白板劝退。更反直觉的一点是:虚拟仪器技术真正难的地方不在画前面板,而在判断哪一段运算该放进循环、哪一路数据该做成子 VI、哪一个值必须靠移位寄存器记住。一份《虚拟仪器技术》实验报告如果只贴两张前面板截图,交上去能过,答辩时被问一句“你这个温度值为什么在跳”就露底了。
这套以 LabVIEW 为平台、以多功能数据采集板 PCI-6024E 和 Demo Box 为硬件基础的实验体系,把课程拆成十四级台阶:温度计、温度分析与温度控制、密码对话框、鼠标属性、Tab 控件、Event 结构,再往后是帕斯瓦尔定理验证、双通道显示、函数波形发生器、滤波器、脉冲上升沿计数、串行通信、TCP 波形传输、UDP 信号传输与 DataSocket。它要回答的是同一件事——数据从传感器引脚一路走到屏幕和另一台机器,每一步都复现得出来、解释得清楚。测控技术与仪器及相关专业做课设、毕设、实验报告的人,可以按这套结构直接改。
2. 实验体系总体设计:十四项实验的三层划分与硬件边界
十四项实验如果按“一个一个做”的顺序推进,很容易做成十四个互不相干的 Demo,交报告时也没法解释为什么先做温度计再做 TCP。我一般会先把硬件边界钉死,再按数据流向把实验归层,最后统一子 VI 的接口约定。这三件事定完,后面每个实验都只是往骨架里填肉。
2.1 硬件边界:DAQ 板卡、Demo Box 与一条“无硬件路径”
PCI-6024E 是一块插在机箱 PCI 槽里的多功能数据采集板,经由端子板引出模拟输入、模拟输出、数字 IO 和计数器。虚拟仪器的分工很清楚:板卡只负责把物理量变成数字,换算、判断、显示、传输全在软件里完成。这条分工决定了实验顺序——先用 Demo Read Voltage 或者随机数把软件逻辑跑通,再接真实通道。原因很实际:通道名写错、接线方式选错、量程超限,这些问题的报错信息全在 MAX 里,跟 VI 逻辑的错误混在一起排查,成本翻倍。
Demo Box 在这个体系里的作用是提供“已知输入”。它给出函数波形、可调直流电平、数字开关量和模拟温度信号,学生手上有一个确定的标准源,才分得清是采集端错了还是处理端错了。没接 Demo Box 时,实验一里那个随机数 × 100就是同一个思路的兜底做法:用可控的假数据验证换算和选择逻辑,等真板卡接上再换回 Demo Read Voltage。
软件环境通常是 LabVIEW 加 DAQ 驱动和 MAX。在 MAX 里先建任务、命名通道(比如 Dev1/ai0)、设量程和接线方式,用测试面板确认能读到数,再回 LabVIEW 调用。这一步不做,后面所有实验都会在“读到 0”和“读到满量程”之间反复折腾。
2.2 三层划分:交互层、数据层、通信层
按数据流向把十四项实验归成三层,好处是每一层的验收标准完全不同,自检的时候不会拿错尺子。
| 层次 | 对应实验 | 核心机制 | 验收看什么 |
|---|---|---|---|
| 交互层 | 温度计、密码对话框、鼠标属性、Tab 控件、Event 结构 | 前面板控件、属性节点、Case 结构、事件结构 | 操作后反馈是否即时、状态是否可复位 |
| 数据层 | 温度分析、温度控制、帕斯瓦尔定理、双通道显示、函数波形发生器、信号处理、脉冲计数 | While 循环、移位寄存器、子 VI 调用、FFT、比较与报警 | 数值是否落在误差范围内、循环能否正常退出 |
| 通信层 | 串行通信、TCP 波形传输、UDP 信号传输、DataSocket | VISA、TCP/UDP 节点、DataSocket 连接 | 丢包与乱序有没有统计、断线能否重连 |
交互层的难点在状态,一个开关的机械动作设错,程序就会“按了没反应”;数据层的难点在时序,采样率和循环速率不匹配,波形图表上就是一条锯齿;通信层的难点在边界,发出去多少字节、收到多少字节,没有长度头就只能靠猜。三层各有一个典型的翻车场景,第 5 章会逐条拆。
2.3 子 VI 的接口约定与文件组织
实验一里创建 Thermometer.vi 的过程,其实是整个课程里最重要的一次接口设计练习:编辑图标、点右上角图标框选 Show Connector、把左侧端口连到竖直开关、右侧端口连到温度指示。LabVIEW 的惯例是左输入右输出,这条惯例不是形式主义,它决定了以后任何人调用这个子 VI 时,数据往哪边流。
我一般会额外加两条约定。第一条是每个子 VI 都串 error in / error out 簇,放在连接器最下方一左一右,错误沿着链传回主 VI,不会在某一层悄悄断掉。第二条是单位写进名字,比如 Thermometer_C_SubVI.vi 表示内部以摄氏度为单位的温度子 VI,界面要华氏再换算。文件本身按实验一的做法存进 LLB 库(Seminar.LLB),好处是拷贝给别人时只带一个文件,不会出现子 VI 路径丢失。
连线端口的模式选择也有讲究。控件少的时候让 LabVIEW 自动选两端口模式最省事,控件多了(比如后面密码对话框要传字符串、布尔、错误簇)就得手工指定 4-2-2-4 之类的模式,否则端口对不上,子 VI 的输出永远是默认值。
3. 前面板与事件层实现:控件属性、子 VI 对话框与事件结构的落地
这一层的实验看起来最“轻”,实际最容易做成一堆散装 Demo。温度计、密码对话框、鼠标属性、Tab、Event 这五项如果各写各的,学生会觉得学了五个互不相关的技巧;如果按“控件 → 属性 → 事件”的顺序串起来,它们其实是一条完整的交互链:控件负责输入输出,属性节点负责在运行时改控件的外观和行为,事件结构负责把“用户做了什么”变成程序的分支。
3.1 温度计子 VI:换算顺序和 Select 的接法
实验一的前面板只有两个东西:一个温度计指示件、一个竖直开关。用标签工具把温度计的标尺范围改到合理区间,这一步别省,标尺默认范围跟你的数据量程不一致时,指针会一直顶在满量程上。框图里的顺序是 Demo Read Voltage 取电压,乘 100 映射成温度量纲,然后进 Select 函数,s 端接竖直开关,t 和 f 两端分别接华氏支路和摄氏支路。
# 与温度计子 VI 框图等价的运算:采集值 -> 摄氏温度 -> 单位选择 def thermometer(raw_voltage, to_fahrenheit=False): celsius = raw_voltage * 100.0 # ×100 系数对应传感器量程,换量程必须同步改 if to_fahrenheit: return celsius * 1.8 + 32.0 # 接 Select 的 t 端 return celsius # 接 Select 的 f 端 def from_fahrenheit(f): return (f - 32.0) / 1.8 # 反向换算:华氏输入转回内部摄氏值这段代码对应的三个参数要记牢:×100的系数是量程映射,1.8和32是单位换算常数,Select 的 s 端是布尔选择器。最常见的错误是把 t/f 两端接反,开关拨到“华氏”,显示的却是摄氏值,而两个数值都在合理区间内,肉眼根本看不出来——实验记录里那个“华氏度”曲线跟“摄氏度”曲线几乎重合,八成就是这个原因。把子 VI 做成可复用的还有一个隐性收益:实验二的温度分析直接调它,不用重写换算。
3.2 密码对话框:字符串属性、机械动作与窗口属性
面板控制实验要求做一个密码输入对话框 SubVI,主程序调用它、密码正确才响一声。三个细节决定成败。
字符串控件要做成密码显示,输入时显示为*,这个属性在控件的显示设置里改,不改的话旁边看的人一眼就看到密码。两个布尔开关的机械动作必须设对:OK 用 Switch Until Released(按下闭合、松开断开),保证主程序读到的是“这一瞬间被按下了”;Cancel 用 Latch When Released,让它在被读取后自动复位。机械动作设错的表现很典型——程序跑起来开关自己弹回原位,或者按一次触发好几次。
# 密码对话框子 VI 的判定逻辑等价写法 CORRECT = "instrument2022" # 实际项目放配置文件,不要写死在框图常量里 def check(pwd: str) -> bool: return pwd == CORRECT # 对应「字符串相等」比较函数,输出布尔给 Case 结构密码本身建议放配置文件而不是常量,改密码不用重新编译 VI,这在多人共用一台实验机时很有用。窗口属性通过 VI Properties 设:窗口类型选对话框、标题固定、运行位置居中、禁止调整大小。日期时间那一半用 Format Date/Time String,格式串写%Y-%m-%d %H:%M:%S,分钟是%M、月份是%m,大小写写反显示出来就是错的月份。
3.3 鼠标属性与 Tab 控件:属性节点怎么选、页怎么放
鼠标属性实验的结构是:两个 Picture 控件盖在两个 Graph 控件上,用调色板把 Picture 背景设成透明,这样既能看到图形又能捕获鼠标。后面板用 Unbundle 把鼠标位置簇拆成 x、y,属性节点读 Mouse Position 和 Mouse Down,Case 结构判断坐标是否落在区域内,落在区域内就改前景色,点击就弹 One Button Dialog。
属性节点的坑在于位置:读属性的节点必须放在循环内部,放在循环外只读一次,界面就永远不变。另外鼠标位置属性返回的是簇,要先 Unbundle 再比较,直接连比较函数会类型不匹配。
Tab 控件实验要求用四个 Digital Control 实现对 Tab 尺寸、颜色、明暗和使能的设置。Tab 的页是容器,控件必须放进对应的页里,浮在 Tab 外面的控件切页时不会跟着走,这是新手最常犯的错。属性设置通常用数组加循环:把四页的引用做成数组,For 循环里逐个设 Tab Caption、Colors、Disabled State,比手写四遍属性节点干净得多。用数组选取页索引时注意索引从 0 开始,跟上位机的习惯别混。
3.4 Event 结构:Table 和 List 什么时候更新
Event 实验要求创建 showgraph.vi 同时显示多道数据,再用主 VI 的 Event 结构完成对子 VI 的调用和 Table 控件的数据更新。事件结构的三个要点:必须放在 While 循环里;必须有一个 Panel Close 或者“停止”按钮的事件分支,否则窗口关不掉;事件分支里不要放长延时,会把整个界面的事件队列堵住。
Table 控件的数据是二维字符串数组,列首单独用列首字符串属性写;List 控件的条目用 Item Names 属性一次写入整个字符串数组,逐条追加会触发多次重绘。事件源的选择也要克制——只给真正需要响应的控件建事件,比如“Table 单元格编辑完成”,不要给每个控件都挂 Value Change,事件分支一多,调试时根本不知道是哪条触发的。
4. 数据通路实现:采集、信号处理与串行/TCP/UDP 传输的模块拆解
从温度分析开始,实验就进入了数据层和通信层,这里每个模块都有明确的输入输出边界,写完能单独测。判断一个模块写得对不对,我一般不问“跑起来了吗”,而问“给它喂一段已知数据,输出对不对”。
4.1 温度分析与温度控制:循环、移位寄存器与报警分支
温度分析要在前面板放两个波形图表,一个叫 Thermometer display 显示原始曲线,一个叫 Thermometer analyze 显示处理后的结果,再用三个 String Indicator 显示平均值、最大值、最小值。框图里是一个 While 循环,循环内调 Thermometer.vi,输出分两路:一路直接进图表,一路进统计。
统计用移位寄存器最稳,四个寄存器分别存计数、累加和、当前最大、当前最小,初始化端子接 0、0、-inf、+inf。
# While 循环 + 移位寄存器的等价写法:一次遍历同时出均值与极值 def analyze(stream): n, total = 0, 0.0 vmax, vmin = float("-inf"), float("inf") for v in stream: n += 1 total += v vmax = max(vmax, v) vmin = min(vmin, v) yield v, vmax, vmin, total / n # 依次送 display 图表、三个字符串指示件四个寄存器的初始值不能省,最大值的初值给 0 就会出现“所有温度都是负数时最大值显示 0”的笑话。循环里还必须加 Wait (ms),通常 100~200 ms,不加的话 CPU 直接跑满,波形图表刷不出来,看起来像程序卡死。
温度控制在分析的基础上加一层比较:超过阈值就点亮报警灯并调用声音子模板发声。阈值做成前面板输入而不是常量,答辩时现场改一个值演示报警,比讲十句都管用。
4.2 帕斯瓦尔定理与滤波器:归一化不对,误差就是几十倍
帕斯瓦尔定理验证的是时域能量等于频域能量。做法很直接:生成一段正弦加噪声,做 FFT,分别算两边能量,比较相对误差。看起来简单,翻车点全在归一化上。
import numpy as np N, fs, f0 = 1024, 1000.0, 50.0 t = np.arange(N) / fs x = np.sin(2 * np.pi * f0 * t) X = np.fft.fft(x) e_time = np.sum(x ** 2) # 时域能量 e_freq = np.sum(np.abs(X) ** 2) / N # 频域能量,必须除以 N print(e_time, e_freq, abs(e_time - e_freq) / e_time)三个参数决定结果对不对:N取 2 的整数次幂,FFT 速度最快;频域求和必须除N,忘了除,误差正好是 1024 倍;要看单边谱的话只取前N/2+1点并把非直流分量乘 2,不然两边永远对不上。滤波器部分用巴特沃斯,阶数和截止频率两个参数要跟采样率对齐——截止频率不能超过采样率的一半,超了滤波器直接失稳,输出全是发散的数。
4.3 双通道显示与函数波形发生器:图表和图形的区别
双通道信号显示的关键是选对显示控件。波形图表(Chart)逐点接收、自带历史缓冲,适合实时采集;波形图(Graph)接收整段数组,适合一次性回放。两路信号频率不同却共用一个 dt,画出来的一路准一路歪,这是时序没对齐的典型表现。函数波形发生器要给的参数是波形类型、幅值、频率、相位、占空比、采样点数、采样率,方波的占空比和三角波的对称性都靠这几个参数调。数据量大以后,把生成参数和波形数据一起打包成簇传递,比拉七八根线清爽。
4.4 脉冲上升沿计数:迟滞比算法更重要
计数实验要求统计脉冲上升沿个数,两条路:用板卡计数器通道做硬件计数,或者软件比较相邻采样点。硬件计数准,但要占用计数器资源;软件法灵活,代价是采样率不够就丢脉冲。
# 软件法数上升沿:带迟滞的比较,high/low 之间不翻转 def count_rising(samples, high=0.55, low=0.45): state, count = False, 0 for v in samples: if not state and v > high: state, count = True, count + 1 elif state and v < low: state = False return counthigh和low之间那段是死区,信号在阈值附近抖动时不会重复计数。没有迟滞,一个带噪声的方波能被数成三倍。采样率至少取脉冲频率的 10 倍,边沿越陡要求越高。
4.5 串行、TCP、UDP 与 DataSocket:按可靠性选,不按新鲜感选
通信四个实验对应四种典型场景,选型依据是可靠性和实时性的取舍。
| 方式 | 连接 | 可靠性 | 配置要点 | 典型用途 |
|---|---|---|---|---|
| 串行 VISA | 点对点 | 中,靠校验 | 波特率、数据位、停止位、流控必须两端一致 | 仪器控制、下位机指令 |
| TCP | 面向连接 | 高,有序不丢 | 先 Listen 后 Connect,读要指定字节数 | 波形上传、命令通道 |
| UDP | 无连接 | 低,可能丢和乱序 | 自己加序号和丢包统计 | 高频小包、实时监视 |
| DataSocket | 发布订阅 | 取决于底层 | URL 写对,服务端先起 | 共享变量式数据发布 |
TCP 传波形最容易踩的是粘包:发端一次写 2000 字节,收端按 1024 读,第二段就错位了。稳妥做法是加长度头。
import struct def pack(payload: bytes) -> bytes: return struct.pack(">I", len(payload)) + payload # 4 字节大端长度头 + 数据体收端先读 4 字节解出长度,再按长度读满。UDP 相反,别指望顺序,序号和丢包率要自己统计并显示在前面板上。串口的 VISA Read 一定要给字节数并设超时,不给字节数它会一直等下去,界面看起来就是死的。
5. 避坑与常见问题排查:五条最容易翻车的地方
实验报告里最值钱的部分往往不是“我做出来了”,而是“我卡在哪、为什么卡”。下面五条是这套实验体系里出现频率最高的,每条都按现象、原因、解决三步走。
第一条:While 循环停不下来,点停止按钮没反应。现象是程序一直跑,前面板点关闭也没用,只能强制中止。原因通常有两个:停止按钮的机械动作设成了 Switch Until Released,程序读到的是瞬时值,循环条件端拿不到持续的 True;或者循环里缺 Wait (ms),界面刷新被计算占满,点击事件排不上队。解决是把按钮改成 Latch When Released,条件端接“停止如果为真”,循环内固定加 100 ms 延时。
第二条:波形图表上是一条直线,或者只剩最后一个点。现象是采集明明在跑,图上看不到波形。原因是图表的历史缓冲长度设得太小,或者每轮循环都重新初始化了波形数据,把上一轮的点覆盖掉。解决是把 Chart History Length 调到采样点数的两倍以上,波形数据在循环外初始化一次,循环内只做追加。
第三条:帕斯瓦尔验证的误差是几百上千倍。现象是时域能量和频域能量差了两个数量级。原因几乎都是频域求和没除 N,或者单边谱没乘 2,或者加了窗函数却没做幅值补偿。解决是先拿纯正弦做基准,误差应小于 1e-12,通过了再换含噪信号。
第四条:TCP 收到的波形是乱码或者只收到一半。现象是解出来的数组长度不对,或者数值完全不合理。原因是字符串和字节数组没转换、发送端没加长度头、大小端不一致。解决是统一用长度头加数据体的帧格式,收端循环读满再解析,前面板上把期望长度和实际长度都显示出来。
第五条:子 VI 调用了,输出一直是 0 或者默认值。现象是主程序里子 VI 的指示件不动。原因是连接器端口没定义,或者定义后没把控件连到端口上,或者输入输出接反了。解决是打开子 VI 右键 Show Connector 逐个核对,左边输入右边输出,接线端口框变黑才表示绑定成功。
这五条的共同点是:它们都不报错。LabVIEW 的报错反而好办,沿 error out 一路追下去就行;真正耗时间的是“程序在跑、结果是错的”。我习惯在每个模块前面板留一个原始值指示件,出问题时先看原始值对不对,能把排查范围砍掉一半。
6. 验收与答辩:用一张对照表把十四项实验自证一遍
交报告前我最怕的不是写不完,而是某一项实验自己都说不清它验证了什么。后来养成的习惯是先做一张验收对照表,逐行过一遍,过不去的项当场补,不带着侥幸心理交。
| 实验 | 自检动作 | 合格标准 |
|---|---|---|
| 温度计 | 拨动开关两次 | 两个单位数值互相吻合,切换无跳变 |
| 温度分析 | 喂一段固定数据 | 均值、最大、最小与手算一致 |
| 温度控制 | 把阈值调到当前值以下 | 报警灯亮且有声音,调回后解除 |
| 密码对话框 | 输错、输对各一次 | 输错无动作,输对有提示音,窗口可关 |
| 鼠标属性 | 移入、点击、移出 | 颜色变化正确,弹窗只弹一次 |
| Tab 控件 | 逐页切换并改属性 | 属性只作用于当前页,控件不串页 |
| Event 结构 | 编辑表格单元格 | Table 与 List 同步更新,界面不卡 |
| 帕斯瓦尔 | 纯正弦输入 | 相对误差小于 1% |
| 双通道 | 两路不同频率 | 两路各自周期正确,时间轴对齐 |
| 函数波形 | 改频率、改占空比 | 波形即时变化,无毛刺 |
| 滤波器 | 输入含噪正弦 | 通带保留、阻带衰减明显 |
| 脉冲计数 | 输入已知个数脉冲 | 计数误差为 0 |
| 串行通信 | 自发自收 | 收发字节数一致 |
| TCP/UDP/DataSocket | 断开一端再连 | 有超时提示,能重新建立连接 |
答辩环节的提问基本围绕三件事:数据从哪来、中间做了什么处理、结果凭什么可信。前两问靠框图回答,第三问靠这张表回答。被问到“你怎么知道滤波器有效”,不要描述界面,直接把输入信号的频谱和处理后的频谱并排显示,让衰减量说话。
还有一个容易被忽略的细节:所有前面板在答辩机器上要能一次跑通。子 VI 路径、配置文件路径、串口名、IP 地址这些环境相关的东西,提前改成相对路径或者做成前面板可输入项,别等到现场才发现换了台机器全部报错。
从那以后我每次交实验报告,都强制先对着这张表把十四项走一遍,任何一项说不清就先补,不补完不提交。希望帮到你。
本文还有配套的精品资源,点击获取