
简介LabVIEW与STM32协作开发的三通道信号采集与分析系统资源包。上位机基于LabVIEW构建三通道示波器涵盖信号采集、实时波形可视化、多种触发模式并对输入信号执行加窗操作与FFT频谱分析下位机以STM32为主控借助外部中断实现边沿触发配合ADC与DMA完成自动循环采集由独立定时器中断精确控制采样节拍。资源共758个文件压缩包体积5.01MB主要包含h头文件、c源码、cpp源文件、s汇编文件以及LabVIEW工程lvproj、vi、hex固件、uvprojx工程和ioc引脚配置等完整覆盖从固件到上位机的开发链路。代码结构清晰具备较强可读性可直接编译与执行适合嵌入式及仪器仪表方向的学生与开发者学习串口示波器设计、ADCDMA高速采集、边沿触发与FFT频域分析的完整工程实现已有32人学习下载适合作为STM32与LabVIEW联合开发的入门参考。1. 三通道信号采集为什么难在同步而不在采样做过多通道采集的工程师都有同感单通道调通只需要半天三通道同时采且波形还能对齐才是真正花时间的地方。无论是三路振动传感器测设备健康度还是三通道生理电信号做肌电分析采样本身只是 ADC 不断转换真正的问题永远是「三个通道的数据到了上位机之后怎么证明它们是同一时刻发生的」。如果每个通道各用各的定时器触发再靠软件给数据打时间戳误差轻松超过微秒级FFT 出来的相位信息基本不可信。LabVIEW 加 STM32 这套组合恰好是解决这类问题最省力的路线STM32 负责硬实时采样LabVIEW 负责把数据变成能看的波形和能用的分析结果。这篇文章就从底层同步机制讲起一直讲到上位机界面、滤波与频谱分析最后给出验证方法。适合理工科研究生、仪器仪表工程师和刚接触 LabVIEW 做上位机控制界面的开发者。2. STM32 端三通道同步采样用定时器触发而非软件轮询2.1 为什么轮询和中断不适合多通道同步采集很多初学者做 STM32 信号采集时第一反应是在主循环里不断读取 ADC 转换结果或者用 ADC 转换完成中断去搬数据。单通道慢速采集这样写没问题但三通道同步场景下会出现两个致命问题第一每个通道的采样时刻由HAL_ADC_Start的调用顺序决定通道之间存在固定的时间偏移第二主循环里如果有 LCD 刷新、按键扫描之类的任务采样间隔就会抖动时域波形看不出来但频谱上会出现不该有的噪声边带。正确的做法是用定时器触发 ADC 转换。STM32 的 ADC 支持外部触发源把定时器的更新事件配置成 ADC 触发信号定时器每计数溢出一次ADC 就自动启动一次转换。这样采样率完全由定时器的分频系数和重装载值决定与主循环的执行速度无关。三个通道使用同一个定时器触发同一组 ADC转换完成后用 DMA 搬运就能保证三通道的采样时刻严格对齐到同一个触发沿。2.2 用 CubeMX 做最小配置的三通道 ADC 采样常见做法是用 CubeMX 生成初始化代码这样可以避免手写寄存器带来的低级错误。ADC 部分的关键配置如下开启 ADC1 的 IN0、IN1、IN2 三个通道扫描模式设为 Enabled连续转换设为 Disabled因为我们要用定时器触发而非连续转换DMA 选择 Circular 循环模式数据宽度均为 Half Word因为 STM32 的 12 位 ADC 结果存在 16 位寄存器里。定时器选择 TIM2将更新事件配置为 ADC 触发源。初始化完成后主循环里的核心代码只有三行HAL_TIM_Base_Start(htim2); // 启动定时器开始产生触发信号 HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, 3 * SAMPLES_PER_CH); // 启动 ADC 的 DMA 传输一次传输 3 * 采样点数 个数据代码背后的逻辑是HAL_ADC_Start_DMA启动的是 ADC 的 DMA 搬运真正决定采样节奏的是定时器。每次定时器溢出ADC 就会按扫描顺序采集 IN0、IN1、IN2 三个通道DMA 把这 3 个转换结果依次搬运到adc_buf。由于 DMA 是循环模式当缓冲区存满后会自动绕回开头继续写入程序只需要在主循环里查询 DMA 传输计数判断有多少组新数据到达。其中SAMPLES_PER_CH是每个通道的采样点数adc_buf的大小必须声明为3 * SAMPLES_PER_CH。一个典型的配置是采样率 1kHz、每通道 512 点这样 DMA 传输 1536 个数据即为完整的一帧。2.3 采样率计算的三个参数定时器触发 ADC 时采样率的计算公式为采样率 定时器时钟频率 / ((分频系数 1) * (重装载值 1))以 STM32F4 系列为例TIM2 挂载在 APB1 总线上时钟通常为 84MHz。若需要 1kHz 的采样率可以设置预分频器为 83重装载值为 999这样采样率就是 84MHz / (84 * 1000) 1000Hz。也可以通过定时器的内部时钟分频和脉冲宽度调制模式实现类似效果但更新事件触发 ADC 是最直接的配置方式笔者习惯用这个方案因为参数容易理解出错后排查路径清晰。整个链路中还有一个参数常被忽略ADC 的采样周期。在 CubeMX 的 ADC 配置界面里每个通道可以单独设置采样周期单位为 ADC 时钟周期。采样周期设置过短输入阻抗较高的信号源会来不及给采样电容充电导致测量结果偏小。一般信号源输出阻抗在 10kΩ 以下时采样周期设为 15 个时钟周期左右即可如果信号源阻抗较高应增加到 84 或更多。需要注意的是三个通道的采样周期和转换顺序会影响通道间的微小延时。ADC 工作在扫描模式时通道是逐个转换的这意味着 IN1 的转换完成时间会比 IN0 晚一个通道转换时间。对于 1kHz 以内的信号这个微秒级延时可以忽略但如果采集的是几百 kHz 的信号必须意识到三通道并非绝对同时。更严苛的场景应该使用多片 ADC 同步采样或者选用带同步采样保持的多通道 ADC 芯片。3. 定义三通道通信协议从硬件到 LabVIEW 的关键一公里3.1 帧格式设计帧头、长度、数据与校验STM32 采集到的数据要传给 LabVIEW最常见的物理层接口是串口USART使用 USB 转 TTL 模块连接成本低且驱动成熟。但串口是字节流协议如果直接把二进制数据塞进串口LabVIEW 端根本不知道从哪里开始解析。因此必须设计帧格式常见做法是字节偏移内容长度说明0帧头 0xAA 0x552 字节用于同步识别2数据长度1 字节后面有效数据的字节数3通道标识与序号1 字节高 4 位通道号低 4 位帧序号4 - 9三通道数据6 字节每通道 int16低字节在前10校验和1 字节从帧头到数据字节的累加和低 8 位使用小端序低字节在前发送因为 STM32 和运行在 x86 平台上的 LabVIEW 默认都是小端不需要额外的字节序转换。帧序号用于检测丢帧LabVIEW 每收到一帧检查序号是否连续若不连续说明传输层发生了丢帧此时频谱分析的结果应该被标记为无效。每帧只携带 6 字节的有效数据加上协议开销共 11 字节。如果采样率为 1kHz串口波特率至少需要11 字节 * 8 位 * 1000 帧/秒 88000 bps因此最低应该选择 115200 波特率留出约 30% 的余量。若采样率提高到 10kHz波特率则至少 880kbps这时建议直接换用 USB 虚拟串口或者改用 TCP 通信传统 USB 转 TTL 芯片在高波特率下容易因线材质量产生误码。3.2 STM32 端通过串口 DMA 发送帧在 STM32 端推荐将组帧和发送放入定时器中断或者 DMA 传输完成中断中执行。为了不阻塞采样发送使用串口 DMA 模式先构造好一帧数据放入发送缓冲区然后调用HAL_UART_Transmit_DMA函数立即返回实际发送由 DMA 控制器完成。uint8_t tx_buf[11]; uint8_t frame_seq 0; void build_and_send_frame(uint16_t ch0, uint16_t ch1, uint16_t ch2) { tx_buf[0] 0xAA; tx_buf[1] 0x55; tx_buf[2] 6; // 数据长度 tx_buf[3] (0 4) | (frame_seq 0x0F); // 通道号 0帧序号 tx_buf[4] ch0 0xFF; tx_buf[5] (ch0 8) 0xFF; // 低字节在前 tx_buf[6] ch1 0xFF; tx_buf[7] (ch1 8) 0xFF; tx_buf[8] ch2 0xFF; tx_buf[9] (ch2 8) 0xFF; tx_buf[10] 0; for (int i 0; i 10; i) { tx_buf[10] tx_buf[i]; // 累加和校验 } frame_seq; HAL_UART_Transmit_DMA(huart1, tx_buf, 11); }这里有一个细节frame_seq只取低 4 位也就是说帧序号每 16 帧循环一次LabVIEW 端只需要判断序号是否与上一次连续即可不需要处理回绕问题。校验和选最简单的累加和而不是 CRC是因为在这个波特率下误码率很低累加和已经能拦截绝大多数单字节错误而且 LabVIEW 端计算同样的累加和开销极小。如果传输环境存在强电磁干扰再升级为 CRC16配合重传机制使用。3.3 LabVIEW 串口读取的 VISA 配置LabVIEW 端读取串口数据核心是 VISA 函数。在程序框图里放置 VISA 配置串口节点需要确认以下参数串口资源名对应设备管理器中识别到的 COM 口波特率、数据位、校验位、停止位必须与 STM32 一致典型值是 115200、8、None、1终止符设置为 0因为我们的帧协议自带长度字段不使用串口终止符。读取时不能一次性把缓冲区读完然后硬解析因为串口数据是流式的一帧可能被拆成两段到达。常见做法是先用 VISA 读取属性节点获取当前缓冲区字节数再一次性读取全部可用字节追加到前置缓存中然后从缓存中查找帧头 0xAA 0x55找到后按长度字段切出一帧剩余部分留给下次处理。VISA 读取超时通常设置为 1000ms。若程序报超时错误不需要立刻怀疑 VISA 配置先检查 STM32 端是否在循环发送。一个很容易踩的坑是 STM32 端用HAL_UART_Transmit阻塞发送在低波特率下这个函数会占用大量 CPU 时间导致 ADC 采样 DMA 缓冲区溢出。改用HAL_UART_Transmit_DMA后问题通常会立刻消失。另一个高频问题是 STM32 的 GPIO 复用配置错误TX/RX 引脚没有正确映射到 USART这类问题用示波器看 TX 引脚是否有波形即可快速确认。4. LabVIEW 上位机三通道界面生产者消费者框架与波形显示4.1 为什么用生产者消费者而不用单循环LabVIEW 做上位机控制界面时最常见的架构选择有两个平铺式顺序结构和生产者消费者。信号采集程序必须选后者原因是串口读取是阻塞操作如果和界面刷新放在同一个循环一旦 VISA 读取在等待数据波形图表就没办法实时刷新界面会不断卡顿。生产者消费者架构包含两个循环生产者循环负责读取串口数据、解析帧、提取三通道数据将处理好的数据通过队列发送给消费者循环消费者循环从队列中取出数据更新波形图表、指示灯和数值显示控件。队列在这里起到解耦和缓冲的作用生产者速度快时数据在队列中排队消费者循环不会丢数据。4.2 三通道界面控件选择与布局前面板设计应围绕「三通道对比」这一核心需求展开控件类型作用波形图Waveform Chart实时滚动显示三通道时域波形采样率显示Numeric Indicator显示当前实际采样率是否与设定一致通道开关3 个 Boolean控制某个通道是否显示丢帧计数器Numeric Indicator显示协议层丢帧总数当前通道幅值3 个 Numeric Indicator显示每个通道的最新测量值波形控件有两个容易混淆的选择Waveform Chart 和 Waveform Graph。Chart 自带历史缓冲区新数据到来时自动左移显示适合实时数据流场景Graph 适合一次性显示完整数组比如读取一个文件后整体绘图。三通道实时采集应该选 Chart并把历史缓冲区长度设置得足够大例如 1000 个点这样波形滚动时能看到约 1 秒的完整历史又不会因为太长导致刷新缓慢。4.3 使用队列传递三通道数据帧生产者循环中的核心代码逻辑如下从缓存中解析出一帧数据后将三个通道的数值组成一个长度为 3 的数组连同帧序号一起打包成簇通过「元素入队列」函数送入队列。消费者循环使用「元素出队列」函数取出簇拆分后分别通过「波形图表」控件的属性节点或直接连线更新显示。// 伪代码对应 LabVIEW 程序框图逻辑 // 生产者循环 // 1. VISA 读取属性节点获取可用字节数 // 2. VISA 读取全部可用字节 // 3. 追加到移位寄存器缓存的字符串尾部 // 4. while 循环查找帧头 0xAA 0x55 // 5. 找到后检查长度字段截取完整一帧 // 6. 解析三通道 int16 数据和帧序号 // 7. 元素入队列 // 消费者循环 // 1. 元素出队列超时 100ms // 2. 拆分三通道数据 // 3. 更新波形图和数值显示这里需要说明的是队列深度的选择。队列默认深度为 100若生产者的速度长期高于消费者队列会持续堆积内存占用不断增长。更稳健的做法是使用有界队列深度设为采样率的两倍当队列满时生产者直接把最旧的数据丢弃。这种策略下界面卡顿时丢掉的只是来不及显示的历史帧实时性反而更好。每个通道的电压换算在显示前完成。STM32 的 ADC 是 12 位分辨率参考电压 3.3V换算公式为电压值 ADC 原始值 * 3.3 / 4096这个换算可以在 STM32 端完成后以浮点数发送也可以发送原始整数在 LabVIEW 端换算。推荐后者因为串口传输浮点数涉及字节序问题而整数传输可靠且省带宽LabVIEW 端一个「数值常量 3.3 / 4096」的乘法和除法即可完成代价微乎其微。5. 信号分析功能滤波、FFT 频谱与数据落盘实现5.1 在 LabVIEW 中给三通道信号加滑动平均滤波原始 ADC 数据往往带有高频噪声直接做频谱分析会看到杂散峰。对慢变信号最常用的处理是滑动平均滤波LabVIEW 中没有直接的滑动平均函数但可以用「移位寄存器 数组」的方式实现。具体做法是在消费者循环的移位寄存器中保存最近 N 个采样点每次新数据到来时删除最旧的一个点追加最新点对数组求平均。// 伪代码 // 移位寄存器保存数组 queue // 新数据 data 到来时: // 数组插入 data 到队尾 // 如果数组长度 N删除索引 0 的元素 // 输出 数组均值窗口 N 的选择需要权衡窗口越大滤波越平滑但滞后越明显。采样率 1kHz 时N 取 10 到 50 较合适对应的滞后时间为 10ms 到 50ms。如果信号本身是正弦波要特别注意滑动平均会在幅值上产生衰减N 越大衰减越大具体衰减量约为sin(pi*f*N/fs) / (pi*f*N/fs)其中 f 为信号频率fs 为采样率。对于更陡峭的滤波需求LabVIEW 提供了高级分析函数库中的 Butterworth 滤波器函数位于「信号处理 → 滤波器」面板。配置时只需要设置滤波器类型低通/高通/带通、阶数和截止频率。二阶低通截止频率设为采样率的十分之一是比较保守的起点若截止频率设得过高滤波效果等同于没有设得过低则会把有效信号一起滤掉。5.2 FFT 频谱分析的参数设置与幅值校正LabVIEW 的「频谱测量」Express VI 可以直接输出功率谱或幅值谱。使用该函数时有两个关键参数容易被忽略窗函数和幅值缩放。默认的矩形窗在采样点数不是信号周期的整数倍时会因频谱泄漏产生大量旁瓣。对于连续采样场景建议选择 Hanning 窗它在频率分辨率和幅值精度之间较为均衡。若需要精确测量幅值则选择 Flat Top 窗它的幅值误差最小但主瓣较宽频率分辨率略差。幅值缩放的设置决定了输出结果的含义。需要区分两种常见设置峰值幅值Peak和均方根幅值RMS。对于标准正弦信号峰值幅值对应正弦波的幅度而 RMS 幅值对应有效值两者相差根号 2 倍。做振动分析时通常关心峰值做噪声测量时通常关心 RMS。在频谱测量 Express VI 的配置界面里可以明确选择但不能两者同时输出需要分别调用两个实例。FFT 点数与频率分辨率的关系是频率分辨率 采样率 / FFT 点数。采样率 1kHzFFT 点数取 1024频率分辨率约为 0.98Hz。如果信号中有两个频率只差 0.5Hz 的分量这个分辨率不足以区分它们需要增大 FFT 点数到 2048 或 4096。需要注意的是FFT 点数增加意味着需要缓存更多采样点实时性会下降在 LabVIEW 中可以使用「缩放 FFT」的连续模式通过重叠处理兼顾实时性。5.3 三通道数据写入 Excel覆盖问题的正解数据落盘是采集系统最容易被忽视的环节。很多工程师用「写入带分隔符电子表格文件」函数保存数据第二次运行时发现文件内容被覆盖。原因很简单该函数的输入端如果接的是文件路径而非文件引用默认以新建模式打开文件同名文件会被直接替换。正确做法有两种。第一种是设置函数输入端的「添加至文件」参数为 True数据会被追加到文件末尾。第二种是使用「打开/创建/替换文件」函数获得文件引用设置访问模式为「读/写」定位到文件末尾后再写入最后关闭引用。对于三通道系统更推荐第二种方式因为它同时支持手动添加文件头比如记录采样率、采集时间、通道名称等元信息。// 伪代码 // 打开文件引用访问模式读写 // 设置文件位置为末尾 // 写入表头: 时间, 通道1, 通道2, 通道3 // 在循环中写入数组: 时间戳, 三通道数据 // 关闭文件引用数据写入的频率需要控制。每帧都写入文件会导致磁盘 IO 频繁消耗 CPU。常见做法是每积累 100 帧批量写入一次或者每秒钟写入一次。LabVIEW 的写电子表格函数输入支持二维数组将 100 帧 × 4 列的数据一次性传入即可执行效率远高于逐行写入。批量写入还带来一个额外好处文件是顺序写的断电时最多丢失最后一批数据不会破坏前面已经写入的部分。6. 验证完整链路的 3 个方法与一段可复用的自检逻辑这一章直接给验证手段用来确认「硬件采样、协议传输、上位机显示与分析」这条链路没有隐形错误同时提供一段可以反复用于系统自检的 LabVIEW 逻辑。6.1 信号发生器自检频率与幅值两把尺子用信号发生器输出一个 100Hz、1V 峰峰值的正弦波同时接入 STM32 的三个通道。如果系统链路没有问题LabVIEW 的 FFT 频谱中最大值应出现在 100Hz 附近幅值应接近 0.5V峰值为 1V 时FFT 峰值对应单边谱的幅度 0.5V。这个测试同时验证了三件事采样率配置是否正确、FFT 幅值缩放是否无误、三个通道是否有接反或短路。下一步把信号频率改为 101Hz重新做一次 FFT观察谱峰是否移动到 101Hz。这一步验证频率分辨率是否符合预期。若谱峰没有落在预期频率优先检查采样率配置而不是怀疑 FFT 函数。6.2 丢帧与恢复测试协议层可靠性的体检在 STM32 端人为制造一次中断在发送帧的代码中插入一个分支当帧序号等于某特定值时跳过发送。LabVIEW 端应该检测到帧序号从 n 直接跳到 n2丢帧计数加 1随后继续正常工作。该测试能确认 LabVIEW 的解析逻辑在丢一帧后不会错位。// 伪代码LabVIEW 中帧序号连续性检查 // 解析出当前帧序号 current_seq // 上一帧序号保存在移位寄存器 last_seq // expected (last_seq 1) 对 16 取模 // 如果 current_seq ! expected: // 丢帧计数加 1 // 记录丢失帧数 (current_seq - last_seq 16) 对 16 取模 // 更新 last_seq current_seq如果 LabVIEW 在遇到断帧后出现大量乱码通常是帧头搜索逻辑写错了在处理缓存时找到帧头后必须同时验证长度字段是否合理例如有效数据长度必须为 6长度异常时不能盲目按该字段切帧应该继续向后搜索下一个帧头。这是串口流式解析中最容易出问题的地方。6.3 进阶用法三通道采集系统与外部程序交互基础功能稳定后可以沿两个方向扩展。第一个方向是让 LabVIEW 把分析结果实时传给其他程序比如将三通道特征值通过 TCP 发送给 Python 脚本做进一步处理。LabVIEW 的 TCP/IP 函数面板提供了 TCP 写入和读取函数初始化时用 TCP 侦听创建连接Python 端用 socket 模块作为客户端连接。需要注意 TCP 一次写入可能只发送部分数据务必调用 TCP 写入函数的返回字节数参数确认全部写入。第二个方向是把采集与分析逻辑编译成独立可执行程序这涉及 LabVIEW 的应用程序生成器。打包时最容易出错的是目标路径中的中文字符导致运行时找不到 DLL建议安装目录统一使用英文路径。如果目标电脑没有安装 LabVIEW 开发环境需要同时打包 LabVIEW Runtime Engine版本必须与开发环境一致运行时引擎版本不一致是程序启动报错的最常见原因。安装路径选择默认值即可不建议为了省空间随意修改因为 LabVIEW 在注册表中的路径信息是写死的移动 Runtime 后程序可能无法找到运行库。本文还有配套的精品资源点击获取