资讯动态

LabWindows/CVI入门:用C语言快速开发上位机测控界面

发布时间:2026/10/24 6:16:55 来源:尧图企业网站定制
简介LabWindows/CVI 基础教程面向熟悉 C 语言且了解 Windows 操作系统的测控软件开发者旨在帮助初学者快速掌握 LabWindows/CVI 的编程思想与开发步骤。教程以实例驱动从一个生成正弦波的入门程序入手带领读者依次完成创建用户界面、添加 Acquire / Clear / Quit 三个命令按钮与图形显示控件设置 Panel 标题与自动居中属性编写按钮回调函数并实现绘图、清除、退出等逻辑完整覆盖事件驱动编程、控件交互和调试流程。资源为 1 个 PDF 文件包体约 1.08MB内容紧凑便于按章学习目前已有 978 人学习下载适合作为 CVI 上位机开发的起步读物。后续章节还涉及数据处理、实时系统接口与硬件通信等主题能够在入门基础上进一步拓展至实际测控项目。1. 先别被名字吓到LabWindows/CVI 到底是做什么的很多刚开始接触测控领域的朋友一听到 LabWindows/CVI 这个名字就有点发怵——又长又绕还带一串缩写总觉得自己还差得远。其实它没有想象中那么高冷。你可以把它理解成一套「给 C 语言加了测控工具箱」的开发环境写逻辑用的还是你熟悉的 C 语言它只是把画界面、画波形、读数据这些琐碎又专业的事提前做成了现成的功能模块。用它写出来的程序通常被称为上位机——也就是电脑屏幕上那个用来控制仪器、显示数据、做过程监控的窗口。如果你学过 C 语言又恰好想往检测系统、数据采集、自动测量这些方向发展从它入门是一条比较顺的路。接下来我会用第一个小例子带你走一遍完整流程跑完你就知道它一点都不玄。2. 先把环境和思路摸熟三个文件、事件驱动和几个窗口开始动手之前建议先花二十分钟把 CVI 的「家底」认识清楚。磨刀不误砍柴工尤其是它和你之前写过的纯 C 程序思路不太一样如果不明白这一点后面每一步都会觉得别扭。2.1 认识三兄弟.uir、.c、.h 各管什么不管是入门还是以后做实际项目你打交道最多的就是三种文件。先是 .uir 文件全称是 User Interface Resource中文叫用户界面文件。你可以把它理解成一张「装修效果图」面板上放几个按钮、几块显示区域、表格长什么样、窗口标题写什么全都在这个文件里用鼠标拖出来。它存的是界面的布局和属性里面不包含任何业务逻辑。换句话说界面长什么样完全由 .uir 决定。然后是 .c 文件这就是普通的 C 语言源码文件。所有响应按钮、处理数据、画波形、算结果的代码都写在这里。界面归界面逻辑归逻辑两者分开存放这是 CVI 项目的一个基本组织方式。最后是 .h 头文件。它一般不用你手写是 CVI 根据界面文件自动生成的。每次你保存 .uir 文件CVI 会把面板和每个控件的「编号」整理成常量写进这个头文件里。比如你给 Acquire 按钮的 Constant Name 填了 ACQUIRE那么在代码里Acquire 按钮就通过 PANEL_ACQUIRE 这个常量被程序识别。这样做的道理和门牌号类似你不需要记住按钮在内存里的确切位置只需要拿着这个名字就能找到它。初学者最容易困惑的是三者的关系。用一个生活例子.uir 是火锅店的装修布局决定哪些位置放桌子和调料台.c 是后厨负责真正出菜.h 是菜单把桌号和菜品对应起来。程序运行时main 函数先用 LoadPanel 把 .uir 装修好的面板装进内存然后按钮被按下时.c 里的回调函数执行期间通过 .h 里的常量找到对应控件去操作。我第一次学的时候一直没搞清楚 .h 是哪来的结果自己手写了一个头文件里面的常量和界面根本对不上号编译一直报错。后来才明白只要把界面改好并保存头文件会跟着自动更新完全不用碰。这个认知让我少走了很多弯路。2.2 事件驱动按钮按下去之后程序在忙什么这是 LabWindows/CVI 和传统 C 程序相比最大的差异点也是新手最先要扭转的思路。以前你写 C 程序通常是顺序执行main 里从上到下跑遇到函数就调用跑完就结束。而 CVI 程序是事件驱动的它运行起来之后不是一口气把代码跑完而是进入一个「待命状态」等用户操作。用户的每一次点击、键盘输入、定时器触发都会产生一个「事件」程序把它派发给对应的回调函数去处理。处理完之后又回到待命状态继续等待下一次操作。对应到我们的第一个例子程序启动后停在界面上你按下 Acquire 按钮这个「按下」动作就是一次事件CVI 发现这个事件应该交给 AcquireCallback 函数去处理于是进入那个函数执行生成正弦波、画曲线的代码执行完再回到待命状态。整个过程可以看成「等待—响应—再等待」的循环。回调函数里那个 switch (event) 就是在判断「这次来的是什么事件」。对按钮来说最终要处理的通常是 EVENT_COMMIT意思是「用户完成了一次有效操作」。实际上按钮还有按下、弹起等更细的事件但对大多数入门程序来说只在 EVENT_COMMIT 里写处理代码就够了其他事件暂时用不到。理解了这一点再去看自动生成的代码就不会发怵。main 函数里那句 RunUserInterface() 不是普通函数调用它表示把控制权交给界面系统让程序进入前面说的那个待命循环。后面我们添加的代码基本都是往各个回调函数里填内容而不是在 main 里堆逻辑。这也是为什么教程反复强调「回调函数」——它是事件驱动程序的灵魂。2.3 每个新手都要逛熟的三个窗口安装并启动 CVI 之后你会看到几个经常切换的窗口先跟它们混个脸熟操作时就不会找不到北。第一个是项目窗Project Window。它管理整个工程包含哪些文件.uir、.c、.h 都列在这里。新建的文件记得保存到这个工程里运行时才能顺利加载资源。如果你发现某个文件没出现在项目窗里编译时大概率会报错找不到文件。第二个是用户界面编辑窗。每当你打开一个 .uir 文件就会弹出这个可视化编辑器。面板、按钮、Graph 都直接在这里拖放和改属性。第一部分的全部操作基本都在这里完成后面学新控件比如 Timer、Table、Chart也是从这个窗口添加。第三个是源代码窗。Generate All Code 生成的 .c 文件就在这里打开。注意平时自己手写代码是直接在 .c 文件里写而用「函数面板」往代码里插入库函数时光标位置决定了代码插到哪里。这个细节很多人翻过车后面我会专门讲。把这几个窗口的角色记住接下来的操作就有地图了在用户界面编辑窗画界面在源代码窗写回调在项目窗管理文件。版本较新的 CVI 2019 以及常见的发行版界面排布与这套逻辑基本一致所以早年教程上的操作步骤放到现在依然能跟着做不用太担心「版本太老没法跟上」。3. 亲手做第一个界面三个按钮加一块波形区域这一章我们开始动手。目标很明确做出一个面板上面有三个按钮和一个显示波形的 Graph。做完这一步你就掌握了 CVI 界面设计的基本功——之后做任何上位机界面流程都是同一套。3.1 新建一个用户界面文件打开 CVI 之后第一步不是急着写代码而是从菜单选择 FileNewUser Interface(*.uir)。这个操作会创建一个新的界面文件并自动弹出空白的 Untitled Panel 窗口。这个空白面板就是你程序运行时的窗口它的灰色区域就是摆放控件的容器。创建时要注意保存位置。因为后面运行程序要按文件名加载资源建议在硬盘上单独建一个文件夹存放工程例如建立一个名为模拟项目X 的目录再把 .uir 文件命名为 FirstSample.uir。位置越规整后面找起来越省心。很多新手随手把文件丢在桌面等到编译时报错找不到文件翻遍整个电脑才明白是路径乱了。我个人的习惯是一打开工程就先保存一次文件哪怕界面还没画好。养成这个习惯以后至少不太会因为「没保存就生成代码」这种小问题导致编译不过。这个小习惯看起来不起眼实际用起来能省不少事。3.2 把控件拖上去三个按钮和一块 Graph现在给空白面板添加控件。在面板灰色区域点鼠标右键会弹出一个控件菜单。选择 Command ButtonSquare Command Button就会在面板上创建一个方形按钮。按同样操作创建三个按钮再选择 GraphGraph 创建一个用于显示图形的控件。四个控件创建好之后用鼠标直接拖动它们的位置调整到你觉得顺眼的样子。界面布局没有严格标准入门阶段怎么顺手怎么来关键是能看清哪个按钮干什么用。通常的做法是把三个按钮放在左侧或下方把 Graph 放在中间偏右的大块区域这样运行时波形有足够的空间显示。这里有一个小提示创建多个同类型控件时CVI 默认会给它们起名为 Untitled Control、Untitled Control_2 之类的名字Constant Name 也是默认值。先不用管它下一步统一改名就行。注意控件创建后如果不小心拖到了面板外面运行时会显示不出来。只要在编辑窗口里把它拖回灰色区域即可不影响工程文件。3.3 修改属性从 Untitled 变成有名字的控件双击面板空白处会弹出 Edit Panel 窗口。把 Panel Title 从 Untitled Panel 改成 Demo Panel这是程序运行时窗口顶部显示的名字。再勾选 Auto-Center Vertically(when loaded) 和 Auto-Center Horizontally(when loaded)这样程序运行时窗口会自动居中视觉效果更友好。完成后点 OK。接着双击 Graph 控件在 Edit Graph 窗口里把 Label 改为 ShowWave。Label 是显示在控件旁边的文字标签用来告诉使用者这块区域是干嘛的。这里只改显示名Graph 的坐标轴范围后面遇到波形显示问题时再调。然后逐个双击三个按钮编辑属性。这里要重点说三个字段它们是整个 CVI 回调机制的核心。第一个是 Constant Name它是这个控件在代码中的唯一标识相当于门牌号。第二个是 Callback Function它填写的是当用户点击这个按钮时要调用的函数名。第三个是 Label它决定按钮表面显示的文字。按照下表配置三个按钮按钮Constant NameCallback FunctionLabel第一个按钮ACQUIREAcquireCallbackAcquire第二个按钮CLEARClearCallbackClear第三个按钮QUITQuitCallbackQuit配置完成后点击 OK然后保存文件。此时界面部分已经完成后面所有活动都转移到代码上来。注意一点属性面板里的 Label 如果写成 Acquire 这样的形式程序运行时按 Alt A 也能触发这个按钮这是 CVI 自带的热键功能不需要额外写代码。4. 让按钮真正干活生成正弦波并跑通第一个程序界面画好了但它还不会动。这一章我们把它变成能响应的程序。好消息是CVI 会自动生成大部分基础代码我们只需要往两个回调函数里填关键逻辑就行。4.1 让 CVI 自动生成代码骨架确认当前激活的是用户界面编辑窗然后选择菜单 CodeGenerateAll Code…。这时会弹出一个对话框问你想生成哪些代码。在 Select QuituserInterface Callback 里勾选 QuitCallback意思是把退出程序这个动作绑定到这个函数上。对话框里还有几个选项Generate WinMain instead of Main 勾选后入口函数会变成 WinMain桌面程序一般保持默认的 Main 即可Select panels to load and display at startup 是选择哪个面板在启动时显示只有一个面板时默认就行不用额外操作。点 OK 后源代码窗打开里面已经生成好了一个基本可运行的程序。自动生成的代码大致如下#include cvirte.h #include userint.h #include FirstSample.h static int panelHandle; int main (int argc, char *argv[]) { if (InitCVIRTE (0, argv, 0) 0) return -1; /* out of memory */ if ((panelHandle LoadPanel (0, FirstSample.uir, PANEL)) 0) return -1; DisplayPanel (panelHandle); RunUserInterface (); DiscardPanel (panelHandle); return 0; } int CVICALLBACK AcquireCallback (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: break; } return 0; } /* ClearCallback 与 QuitCallback 结构类似 */这段代码里有几个函数值得看懂。InitCVIRTE 负责初始化 CVI 运行时环境返回 0 表示初始化失败程序直接退出。LoadPanel 从磁盘加载 FirstSample.uir 界面文件返回面板句柄小于 0 表示加载失败。DisplayPanel 把面板显示出来RunUserInterface 进入前面说的事件待命循环。QuitCallback 里那句 QuitUserInterface(0) 则是退出这个循环的钥匙。自动生成的 AcquireCallback 和 ClearCallback 目前只有空壳真正的功能代码需要我们自己添加。4.2 给 Acquire 按钮填上「生成正弦波」的代码先在源文件顶部定义一个全局数组用来存放生成的正弦波数据。CVI 里添加库函数有一个很有特色的方式——函数面板。把鼠标光标放到 AcquireCallback 函数中 case EVENT_COMMIT: 下面的空白行然后选择菜单 LibraryAdvanced Analysis…在分类里找到 Signal GenerationSine Pattern弹出函数面板。在函数面板中填写参数Output Array 选择刚才定义的数组 WaveNumber of Samples 填 100Amplitude 填 1.0Frequency 填 10Phase 填 0。面板上有清晰的参数提示照着填即可。填好后点击插入按钮代码就会出现在你光标所在的位置。插入后AcquireCallback 里会多出一行调用。接下来还需要让 Graph 把数组画出来继续用函数面板从 LibraryUser InterfaceControlsGraph 中选择 PlotY 函数按提示把数据画到 ShowWave 控件上。两个函数都插好之后代码看起来是这样double Wave[100]; int CVICALLBACK AcquireCallback (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: /* 生成 100 个点的正弦波数据 */ SinePattern (100, Wave, 1.0, 10.0, 0.0); /* 把正弦波画到 ShowWave 控件上 */ PlotY (panelHandle, PANEL_SHOWWAVE, Wave, 100, VAL_DOUBLE, VAL_LINE, VAL_NONE, VAL_SOLID, 1.0, VAL_RED); break; } return 0; }解释一下参数SinePattern 的第一个参数是样本数 100第三个参数 1.0 是幅值波峰高度第四个 10.0 是频率最后一个 0.0 是初始相位。PlotY 里的 PANEL_SHOWWAVE 对应你给 Graph 起的 Constant NameWave 是数据数组100 是点数后面的 VAL_DOUBLE 表示数据源是 double 类型VAL_LINE 表示用折线方式绘制VAL_RED 是线条颜色。这些枚举值不用背函数面板里都能选。4.3 给 Clear 按钮填代码并运行Clear 按钮的功能是清除波形同样把光标放在 ClearCallback 的 EVENT_COMMIT 分支里从函数面板选择 User InterfaceControlsGraphDeleteGraphPlot参数选择 PANEL_SHOWWAVE、-1表示删除所有曲线、VAL_IMMEDIATE_DRAW立即重绘。插入后代码大致是int CVICALLBACK ClearCallback (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: DeleteGraphPlot (panelHandle, PANEL_SHOWWAVE, -1, VAL_IMMEDIATE_DRAW); break; } return 0; }到这里这个程序已经完整了。编译运行点击工具栏的运行按钮窗口弹出并居中显示。点 AcquireGraph 里出现一条正弦曲线点 Clear曲线被清除点 Quit程序退出。我第一次跑通这个例子时很有成就感——第一次体会到「界面加逻辑」的完整流程发现自己其实已经掌握了 CVI 最核心的工作方式。往后不管做什么项目本质都是在重复这一步画界面、绑回调、插代码。5. 新手最常见的五个坑一次排查一个别慌任何工具第一次上手都会踩坑这里把新手最容易遇到的情况集中列出来按现象、原因、解决三步写遇到问题可以直接对号入座。5.1 编译报错找不到头文件或控件常量现象编译时报错说打不开 FirstSample.h或者提示 ACQUIRE、PANEL_SHOWWAVE 这类标识符未定义。原因常见于两种情况。一是生成代码之前.uir 文件没有保存或者没保存到工程所在目录导致 CVI 无法同步生成头文件二是在改完控件属性后没有重新生成代码代码里的常量名和界面里的对不上。解决先保存 .uir 文件再重新执行 CodeGenerateAll Code…并在项目窗确认 FirstSample.h 已经包含在工程里。如果还不行把项目窗里的 .h 文件删掉后重新生成一次。5.2 按钮点了没反应回调函数没绑定上现象程序能启动界面也正常但点击 Acquire 按钮没有任何反应也不报错。原因大多数时候是控件的 Callback Function 没填或者填的函数名和实际函数名不一致。属性面板里 Constant Name 与 Callback Function 是两回事前者是控件标识后者才是事件发生时调用的函数漏填或拼错都会导致事件派发不到。解决双击按钮打开属性编辑窗核对 Constant Name 和 Callback Function 是否与代码里的函数名完全一致。改完后保存 .uir重新生成代码再编译。5.3 波形显示得很奇怪Y 轴范围和数据类型的锅现象画出来的曲线只显示一部分或者整条线拧成一团看不出正弦形状。原因最常见的是 Graph 的 Y 轴范围没设置好。正弦波理论幅值在 -1 到 1 之间如果 Y 轴范围设成了 0 到 100波形自然被压扁成一条直线。另一个常见原因是把数组类型传错了比如用 float 数组却告诉 PlotY 是 VAL_DOUBLE。解决双击 Graph 打开属性窗把 Y 轴范围先设为 -1.5 到 1.5或者直接选择自动刻度。传参时留意 PlotY 里数据类型枚举让它和数组声明保持一致。5.4 函数面板插代码插错位置库函数跑到函数外现象用函数面板插入 SinePattern 后代码没有出现在想要的位置甚至跑到函数外面导致编译报语法错误。原因函数面板是按光标位置插入代码的。如果光标不在函数体内插进去的代码就会落在错误位置。解决插入前先单击函数体内的空行再打开函数面板。插入后立刻看一眼代码位置不对就撤销重来。这条也是我当初翻车最多的地方。5.5 程序一闪而过界面都没看到就结束了现象点击运行后窗口闪现一下立即关闭根本来不及操作。原因通常是 LoadPanel 的文件名写错或路径不对导致面板没加载成功程序提前 return 退出。也有可能是 RunUserInterface 之前代码里出现了异常返回。解决检查 LoadPanel(FirstSample.uir, PANEL) 里的文件名是否与你保存的文件一致文件是否和工程文件在同一个目录下。如果仍然一闪而过优先处理编译信息里提示的那一行代码。6. 跑通第一个程序之后下一步学什么第一个正弦波程序的意义不只是「我做出一个会动的界面」而是让你把 CVI 的完整工作流程走通了一遍设计界面、绑定回调、用函数面板插代码、编译运行。这套流程是后续所有功能的骨架往后的学习都可以沿着它展开。接下来的学习建议按顺序推进。先花时间把常用控件认熟尤其是 Timer 控件它会按固定间隔触发事件是做定时采集、循环刷新数据的基础还有 Table 控件用于在界面上显示批量数据。第二个方向是数据采集如果你手头有采集卡或仪器学 DAQ 库和 VISA 串口通信这是 CVI 在测控领域的主场也是「上位机开发」这个词真正的用武之地。第三个方向是高级分析比如滤波、FFT 变换用函数面板就能完成写起来比纯 C 方便很多。第四个方向是多线程和实时性把耗时的采集放到线程里界面才不会卡。如果一时不知道先从哪个下手建议沿着这条线走控件 → 定时器 → 数据采集 → 分析处理。每一步都像这次的第一个程序一样用一个最小例子跑通再逐步加复杂度。这个思路对绝大多数上位机开发入门都适用不会走偏。坦白讲我最初学 CVI 时也踩过不少坑尤其是回调函数绑定那次折腾了很久才发现是属性面板里填错了一个名字。从那以后我每次新建工程都强制自己走一遍固定流程先建目录存文件再画界面、检查属性、保存、生成代码、最后才写逻辑。流程固定下来之后出错率低了很多排错也快了很多。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑