简介:《虚拟仪器技术》实验报告面向测控技术与仪器及相关专业本科生,也可供学习LabVIEW编程与虚拟仪器开发的读者参考,围绕数据采集、信号处理与通信协议等典型任务组织内容。全包仅含1个PDF文件,约4.22MB,版式完整,便于按实验项目查阅与打印;内容覆盖14个实验,涉及温度计、面板控制、Tab控件与Event语句、双通道信号显示、函数波形发生器、计数器脉冲计数,以及串行通信、TCP/UDP波形传输和DataSocket技术等。每个实验包含要求、目的、步骤、参考面板与记录,并交代DAQ PCI-6024E、Demobox等硬件的配置与使用,附录还说明多功能信号装置Demo Box的功能。已有95人学习,适合按实验流程复现LabVIEW程序、理解帕斯瓦尔定理与滤波器应用,并积累数据采集和网络通信的调试思路。
1. 从一堆打不开的 VI 说起:14 个实验串起的虚拟仪器主线
拿到别人整理好的 LabVIEW 工程,第一道坎往往不是编程,而是双击 VI 之后弹出版本不匹配,或者前面板打开了、程序框图却是一片灰。这份《虚拟仪器技术》实验报告的价值就在这里:它把 14 个实验从温度计、面板控制、鼠标属性和 Tab 控件,一路排到 Event 语句、帕斯瓦尔定理、双通道信号显示、函数波形发生器,再延伸到计数器统计脉冲上升沿、串行通信、基于 TCP 的波形传输、基于 UDP 的信号传输和 DataSocket,配套的是 PCI-6024E 这类多功能数据采集卡和 Demobox 信号箱。
对测控技术与仪器、自动化方向的学生,以及刚被安排接手虚拟仪器项目的工程师来说,这份材料的定位是能照着接线、照着连框图复现的骨架,价值在于把数据采集、信号处理、TCP/IP 通信这三块串成一条能跑通的链,而不是只会念概念的课件。下面按我实际复现的顺序拆:先对齐环境,再做前面板交互,然后上采集和通信,最后是踩坑记录和自检手法。
2. 环境与工程骨架:版本、驱动、通道映射三件先对齐
2.1 安装顺序与版本匹配,比想象中更能决定成败
LabVIEW 的版本策略是“向上兼容、向下不兼容”,也就是说高版本能开低版本 VI,低版本开不了高版本 VI。实验报告里的截图如果不标注年份,直接照抄很可能卡在打开这一步。常见做法是先确认对方用的年份版本(例如 2020、2021 这类),本地装同一年份或更新一年的开发环境;如果本地版本更高,交付前用 File 菜单里的 Save for Previous Version 另存一份,把可打开的版本号降下来,原始文件另存备份,别直接覆盖。
驱动的安装顺序我一般按“操作系统补丁 → 数据采集驱动 → VISA → LabVIEW 开发环境 → 工具包”走。理由是数据采集驱动会注册设备接口,VISA 会注册串口与仪器会话,两者都在 LabVIEW 之前装,能避免装完之后设备列表空白、VISA 资源名找不到的低级问题。如果目标机器只跑不开发,装对应的 Run-Time Engine 就够,但要注意 Run-Time Engine 的年份必须与 VI 保存时使用的版本一致或更高。
| 组件 | 作用 | 装错或漏装的典型表现 |
|---|---|---|
| DAQmx 数据采集驱动 | 识别 PCI-6024E 等采集卡,提供 DAQmx 函数选板 | 函数选板里 DAQmx 一栏为空,MAX 里看不到设备 |
| VISA 运行时 | 串口、GPIB、TCP 资源名管理 | VISA 资源名称下拉框是空的 |
| LabVIEW 开发环境 | 编辑与调试 VI | 只能打开前面板,框图灰色不可编辑 |
| Run-Time Engine | 只运行不开发 | 双击 VI 提示缺少运行引擎 |
提示:老型号采集卡在新系统上经常遇到驱动支持不足,遇到设备管理器能认到卡、但采集软件里打不开通道,优先怀疑驱动版本与系统位数不匹配,而不是程序写错了。
2.2 把 14 个实验拆成可复用的 VI 目录
报告里的实验是线性排列的,实际复现时如果也按“实验一、实验二”平铺,很快就会变成一堆 Thermometer.vi、Untitled1.vi。我一般按“公共子 VI + 单实验主程序 + 数据”三层来组织,每个实验的主程序只负责界面和流程,算法与采集封装成子 VI,这样实验二到实验三能直接复用温度计子 VI,通信实验的收发封装也能在 TCP、UDP 两个实验之间共用。
VirtualInstrument_Lab/ Common/ 公共子 VI:温度换算、报警判断、波形打包 TempConvert.vi AlarmCheck.vi Exp01_Thermometer/ 实验一 温度计系列 Thermometer_Main.vi Exp06_Parseval/ 实验六 帕斯瓦尔定理 Parseval_Main.vi Exp12_TCP_Waveform/ 实验十三 基于 TCP 的波形传输 TCP_Server.vi TCP_Client.vi Data/ 采样数据、参考波形目录建好之后,子 VI 的连接器模式要统一。默认的 4×2×2×4 模式对于“输入一两个参数、输出一个结果”的换算类子 VI 来说端口太多,容易连错,我一般改成 2×2×1×1×1 这种小模式,左侧输入右侧输出,和报告里提到的“左入右出”惯例一致。连接器定义完之后别忘了右键图标 Edit Icon 画一个能认出来的图案,十四五个子 VI 全用默认图标,接线时就是纯靠记忆。
2.3 通道映射先做表,再接线
Demobox 这类教学信号箱通常提供函数波形输出、可调直流、开关量、LED 指示、热敏电阻或电位器模拟、按键输入这几类资源;PCI-6024E 这类 E 系列多功能卡一般提供十几路模拟输入、两路模拟输出、若干路 TTL 数字口和计数器。具体路数以手册为准,但复现前必须先把“物理量 → 信号箱端子 → 采集卡通道 → DAQmx 物理通道名”这张表填出来,否则线接完了程序里写Dev1/ai0还是Dev1/ai3全靠猜。
| 实验 | 信号来源 | 端子 | 采集卡通道 | DAQmx 物理通道 | 量程与接线 |
|---|---|---|---|---|---|
| 温度计系列 | 电位器模拟温度 | AO/电位器输出 | AI0 | Dev1/ai0 | 0~5 V,单端接地 |
| 双通道信号显示 | 函数波形 CH1 | Wave Out 1 | AI1 | Dev1/ai1 | ±5 V,差分接线 |
| 双通道信号显示 | 函数波形 CH2 | Wave Out 2 | AI2 | Dev1/ai2 | ±5 V,差分接线 |
| 函数波形发生器 | 输出到示波器或回采 | AO0 | AO0 | Dev1/ao0 | ±5 V |
| 计数器测脉冲 | 方波或按键消抖脉冲 | PFI 引脚 | CTR0 | Dev1/ctr0 | TTL 电平 |
接线方式上有个容易被忽略的点:信号源是浮地输出时,差分接法要加偏置电阻把共模电压拉到允许范围,否则读数会飘、会随人手触摸变化。单端接法省线,但十几路共地时容易串扰,做双通道波形对比实验时我优先用差分。量程也要按实际信号设置,信号是 ±2 V 却把量程设成 ±10 V,等于白扔两位有效分辨率。
3. 前面板交互层:控件属性、机械动作与 Event 的落地顺序
3.1 控件选型的三步判断
前面板出问题,多数不是逻辑写错,而是控件类型选错。我的判断顺序是:这个量是人看的还是程序读的——人看的用指示器(Indicator),程序读的用控件(Control);这个量的数值范围有多大、需不需要小数——温度用双精度 DBL,计数器计数值用 I32 或 U32;这个交互是一次性的还是保持状态的——按钮类用 Latch when released,开关类用 Switch until released。
机械动作(Mechanical Action)在这份报告里出现频率很高,密码对话框的两个按钮、温度控制的启停开关都依赖它。六种动作里最常用的是三种:Latch when released 按下后自动弹回,适合“点一下触发一次”的按钮;Switch until released 按下保持、松手弹回,适合点动测试;Switch when released 点击切换状态,适合启停。选错了的直接后果是 While 循环里读到的一直是同一个值,程序看着在跑,实际分支永远进不去。
3.2 温度计三连的操作顺序
实验一的温度计要求同时支持摄氏和华氏显示,核心是一个布尔选择加一次换算。我的连线顺序是:先把随机数乘 100 模拟温度输入,再进一个 Select 函数,选择端接竖直开关的布尔值,真分支走“减 32 再除 1.8”的华氏换算,假分支直接透传摄氏值,输出接温度计指示器。选择函数的两个分支数据类型必须一致,都是 DBL,否则连线会变成断线或强制转换点。
实验二在实验一的基础上加 While 循环和波形图表,把子 VI 放进去循环调用。这里有两个关键参数:循环内的延时用 Wait (ms) 设成 200 到 500 之间,太短会占满 CPU,太长波形图表刷新会一顿一顿;波形图表的历史长度默认 1024 点,采样间隔变了要同步改,否则横轴时间标签和实际时间对不上。
实验三加报警,用比较函数判断温度是否超过阈值,超过就进 Case 结构的真分支,里面放 Beep 或声音输出加一个延时。阈值我一般设成 60℃,配合 0~100 的模拟输入,正好在量程上半段,演示效果明显。报警延时不要省,缺了延时报警声会连成一片,听起来像故障。
3.3 密码对话框:机械动作与窗口属性的配合
密码输入的本质是把字符串控件的显示样式改成密码模式,让输入回显成星号。操作路径是右键字符串控件 → Property → Display Style 选 Password,不是简单地改文本颜色。判断逻辑用比较函数把输入串和预设串比对,输出布尔值进 Case 结构。
窗口属性这块,报告里提到了 VI Properties 的设置。要做成真正的对话框,需要把 Window Appearance 设成 Dialog,勾选标题栏、去掉菜单栏和滚动条,再在 Custom 里把窗口大小固定住。这样调用时用户没法拖动改变尺寸,也没法从菜单里乱点。主程序调用它的方式就是把子 VI 直接放到框图里,按下 RUN 按钮时执行一次,密码正确走声音分支,错误就静默返回。
日期时间显示那条支路,用时间函数取当前时间再格式化字符串,格式串里%Y-%m-%d和%H:%M:%S分开写清,别指望默认格式能符合要求。字符串控件的更新时间取决于它所在循环的节奏,放在事件结构里更新比放在 While 循环里轮询更省资源。
3.4 Event 结构:把轮询换成事件驱动
实验五引入 Event 结构,这是从“能跑”到“像个软件”的分水岭。典型配置是一个 While 循环里放一个 Event 结构,事件分支至少配三个:面板关闭(Panel Close?)、关键控件的值改变(Value Change)、以及超时(Timeout)。超时分支的时间设成 100 到 200 ms,用来做界面刷新或状态检查,不设超时会导致没有事件时程序完全卡住。
Filter 事件和 Notify 事件的区别要分清:Filter 事件在控件响应之前触发,可以在事件里修改数据甚至丢弃这次操作,适合做输入校验;Notify 事件在控件已经响应之后触发,只能观察不能拦截。密码框的按键校验用 Filter 合适,波形刷新的触发用 Notify 合适。
注意:事件结构默认勾选“锁定前面板直到事件完成”,事件分支里一旦放了延时或者等待,整个界面就会假死。耗时操作要么挪到循环外,要么用生产者消费者模式另开一个循环。
4. 采集与通信链:DAQmx 参数、波形显示与四种传输方式
4.1 DAQmx 采集链的四个参数
连续采集的骨架是:创建虚拟通道 → 配置采样时钟 → 启动任务 → 循环读取 → 停止并清除。参数里最容易设错的是四个:物理通道、输入量程、采样率、每通道采样数。
| 参数 | 典型取值 | 设错的后果 |
|---|---|---|
| 物理通道 | Dev1/ai0:1 | 通道名写错直接报设备不支持 |
| 最大/最小输入范围 | -5 V ~ +5 V | 范围设大了分辨率浪费,设小了削顶 |
| 采样率 | 1000 Hz | 低于信号频率两倍会混叠,虚高则缓冲区撑不住 |
| 每通道采样数 | 1000 | 与读取方式不匹配会报缓冲区溢出 |
| 读取方式 | 每通道 N 样本 | 用“读取全部”会在连续采样下无限等待 |
循环节拍要和采样率对齐。读 1000 个点、采样率 1000 Hz,意味着一轮读取覆盖 1 秒的数据,循环体里所有处理加起来的耗时必须明显小于 1 秒,否则读得比采得慢,缓冲区迟早溢出。这个账在实验里数据量小看不出问题,一旦把采样率提到 10 kHz、每通道采样数改成 100,循环周期只剩 10 ms,界面上随便画个复杂图表就顶不住了。
4.2 波形显示与信号处理:从双通道到帕斯瓦尔
波形图表(Waveform Chart)和波形图(Waveform Graph)的区别是前者带历史缓存、适合实时滚动,后者一次性画完整数组、适合看频谱。双通道显示实验里,两路信号要么打包成二维数组接图表,要么分别 Build Array 后合并,前者横轴共用一套时间基准,后者容易出现两条曲线时间轴不一致的错觉。
帕斯瓦尔定理那条实验的核心是能量守恒的数值验证:时域采样点平方和对点数取平均,应该等于频域功率谱的积分。翻车点集中在三处:单边谱除了直流和奈奎斯特点之外要乘 2,否则能量差一半;加了窗函数要做能量修正,汉宁窗的修正系数约 1.5;功率谱取的应该是幅值平方而不是幅值。做完这三步,时域和频域两边算出来的差值应该在 1e-12 量级。
我习惯在打开 LabVIEW 之前先用脚本把公式对一遍,这样后面连线只是搬运,不用一边连一边怀疑数学:
import numpy as np fs, N = 1000.0, 1024 t = np.arange(N) / fs x = np.sin(2 * np.pi * 50 * t) + 0.5 * np.sin(2 * np.pi * 120 * t) e_time = np.sum(x ** 2) / N # 时域平均功率 X = np.fft.rfft(x) / N # 归一化到幅值 P = np.abs(X) ** 2 # 幅值平方 P[1:-1] *= 2 # 单边谱:除直流与奈奎斯特点外乘 2 e_freq = P.sum() print(e_time, e_freq, e_time - e_freq) # 差值应在 1e-15 量级这段脚本里,归一化除 N 是关键,不除的话频域结果会整体放大 N² 倍;P[1:-1] *= 2对应单边谱修正;如果加窗,还要再除以窗函数的功率增益。把这段跑通,再去看 LabVIEW 里 FFT Power Spectrum 的输出单位是 Vrms² 还是 V²,就不会被单位换算绕晕。
4.3 串口、TCP、UDP 与 DataSocket 的边界
这四个通信实验放在一起看,选型逻辑其实很清楚:串口适合点对点、低速率、跨设备的老设备对接;TCP 适合要求可靠、有序、不能丢包的波形传输;UDP 适合高频、允许丢几帧的实时刷新;DataSocket 适合同一局域网内共享变量式的发布订阅,省去自己写协议。
串口的关键参数是波特率、数据位、停止位、校验位和终止符。9600 波特率下每秒大约 960 字节,传波形数组要压缩或降采样;115200 则适合传中等长度的字符串。VISA Read 的读取字节数要配合“Bytes at Port”属性用,一次性写死读多少字节,数据不足时会一直等,超时了就报错。
TCP 最容易翻车的是分包。TCP 是字节流,没有消息边界,发送端写一次 100 字节,接收端可能分两次读到 60 和 40。常见做法是加 4 字节长度头,接收端先读头再按长度读体。LabVIEW 的 Flatten To String 默认大端字节序,跨语言对接时这一点必须确认,否则长度头解析出来是个天文数字。下面这段脚本用来在没有 LabVIEW 的机器上先验证通路和字节序:
import socket, struct HOST, PORT = "127.0.0.1", 2055 # 与 TCP Listen 的端口保持一致 srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind((HOST, PORT)) srv.listen(1) conn, addr = srv.accept() header = conn.recv(4) # 先收 4 字节长度头 n = struct.unpack(">i", header)[0] # 大端解析,与 LabVIEW 展平一致 body = b"" while len(body) < n: # 循环收满,防止半包 chunk = conn.recv(n - len(body)) if not chunk: break body += chunk conn.sendall(struct.pack(">i", len(body)) + body) # 原样回写 conn.close() srv.close()LabVIEW 侧对应的接收端是 TCP Read 用 Standard 模式读 4 字节,转成 I32 得到长度,再读对应字节数,最后用 Unflatten From String 还原成波形数组。端口号在监听端和连接端必须一致,本机自测用回环地址,跨机测试要确认防火墙放行。
UDP 的坑在超时和端口绑定。UDP Read 必须设置超时,否则没数据时整个循环卡死;发送端和接收端的端口要错开,一个发一个收,同一个端口既发又收会互相抢。下面这段用来单独验证收发通路:
import socket, time sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) target = ("127.0.0.1", 6150) # 与 UDP Open 的端口一致 for i in range(50): payload = f"{i},{i * 0.5:.3f}\n".encode("ascii") # 序号,幅值 sock.sendto(payload, target) time.sleep(0.1) # 10 Hz,配合 UDP Read 的超时设置DataSocket 用dstp://开头的地址,发布端写、订阅端读,本质是在 TCP 之上包了一层带类型信息的协议。它省事,但别指望跨网段稳定,教学环境里同机或同交换机下用最稳。
5. 避坑与排查:五个现场翻车的现象、成因与解法
5.1 打开与运行阶段的坑
现象:双击 VI 提示版本不兼容,或者前面板能开、框图全是灰的。原因基本是高版本保存的 VI 在低版本环境打开,LabVIEW 不做向下兼容。解决办法是让文件提供方用 Save for Previous Version 另存为低版本,或者本地装对应年份的环境。如果没有源码只有可执行程序,那就装对应版本的 Run-Time Engine,别去折腾开发环境。
现象:While 循环一跑起来风扇狂转,界面点不动。原因是循环体内没有放 Wait (ms) 或等待函数,CPU 被单核吃满。解决是在循环里加 10 到 100 ms 的等待,或者在事件结构里把界面刷新交给超时分支处理。另一种情况是事件结构里做了耗时运算且保持默认的“锁定前面板”,这时候界面假死是设计使然,不是 bug,要把耗时操作挪出去。
现象:温度计指示器数值一直是整数,或者指针顶到满量程不动。前者的原因是数值表示法设成了 I32,除法结果被截断,改成 DBL 即可;后者的原因是温度计的标尺范围还是默认的 0~100 但输入信号超出范围,右键属性改标尺上下限,或者在前面板用标签工具重设范围。
5.2 采集与通信阶段的坑
现象:采集循环跑几分钟后报缓冲区溢出,错误码带 -200279 之类。原因不是卡坏了,而是读得比采得慢,每通道采样数和采样率算出来的循环周期装不下界面绘制加数据处理的时间。解决办法有三条:把每通道采样数调大以减少读取频次、把波形图表的刷新改到单独的循环里、把数据处理挪出采集循环。血泪经验是别一边采集一边在同一个循环里做 FFT,数据量一大必翻车。
现象:TCP 能连上,但收到的数据错位、解析出来的长度大得离谱。原因是分包或者字节序不一致。先确认接收端是不是先读 4 字节长度头再按长度读体,再确认两端的字节序是否统一为大端。有个快速定位的办法:把收到的原始字节用字符串指示器显示出来,看前四个字节是不是自己期望的数值,一眼就能看出是字节序错了还是压根没收到完整包头。
现象:UDP 接收端一直超时,发送端却在正常发。常见原因是端口绑错或者防火墙拦了 UDP。UDP 没有连接概念,发送端不关心有没有人收,所以“发送成功”这个现象毫无参考价值。定位顺序是先在同一个程序里自发自收验证端口,再跨进程验证,最后跨机验证并放行防火墙。还有个小概率原因是发送端和接收端绑了同一个端口,操作系统会有一方绑定失败,但这种失败经常被忽略掉不报错。
5.3 一条排查顺序
遇到“程序不工作”,我一般按这个顺序走:先看错误簇,所有 LabVIEW 函数都有错误输出,把它一路串到最后的 Simple Error Handler,错误信息比猜半天有用;再看数据流,用高亮执行加断点看数据在哪一段变成默认值;最后看资源,设备名、端口号、文件路径这类字符串常量最容易在复制粘贴时改漏。顺序反了会浪费大量时间在猜逻辑上,而实际问题是一根线没连。
6. 用已知信号做体检:帕斯瓦尔校核与一次跑通的顺序
整份实验报告做完,最值得留下的是一个自检习惯:在接入真实信号之前,先用一个参数完全已知的信号把整条链走一遍。我一般用函数波形发生器出 50 Hz、1 V 峰值的正弦,接到模拟输入通道,采集 1000 点,然后做三件事。
| 检查项 | 期望结果 | 不符合时先看哪里 |
|---|---|---|
| 时域幅值 | 峰值约 1 V,无削顶 | 输入量程是否设成 ±5 V 以内 |
| 频谱峰值位置 | 50 Hz 附近单峰 | 采样率与点数算出的频率分辨率 |
| 帕斯瓦尔能量守恒 | 时域与频域平均功率差小于 1e-6 | 单边谱是否乘 2、是否加了窗没修正 |
| 双通道相位 | 两路同源时相位差接近 0 | 通道间是否用了不同的采样时钟 |
| 通信链路 | 本机回环先通,再跨机 | 端口、字节序、分包处理 |
这套体检做完再去接 Demobox 上的电位器、热敏电阻,心里就有底了。真实信号进来之后,读数异常至少能确定问题出在传感器或接线,而不是程序本身。
还有个具体技巧值得单独说:把每个实验的主程序都在框图里保留一个“模拟输入”开关,真信号走采集卡,假信号走随机数或公式波形。这样调试界面和逻辑的时候不用连着硬件,等人不在实验室也能改。这个开关本身就是一个布尔选择加两条支路,成本很低,省下的来回跑腿时间很值。
从那以后我每次拿到一份新的虚拟仪器工程,都会先做三件事:查版本、对通道表、跑一遍已知信号。做完这三步再谈功能,剩下的多半是参数微调的事。希望帮到你。
本文还有配套的精品资源,点击获取