尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于STM32的设备状态监测系统:从原理到实战

基于STM32的设备状态监测系统:从原理到实战 我们做设备维护的最怕什么不是设备坏了而是设备坏了你不知道等到生产线停了才反应过来那时候损失已经不是换一个轴承能补回来的了。我这两年一直在做基于 STM32 的 MCU 状态监测项目就是为了解决这个问题——让设备在出故障之前就“开口说话”。这篇文章就把我整套的方案思路、硬件选型、软件架构和踩过的坑从原理到代码一次说清楚希望对正在做类似项目的朋友有实际帮助。先说清楚这篇文章适合谁看。如果你手里正好有 STM32 的开发板或者已经在做自己的产品原型想给它加上振动采集、温度监测、异常报警这类功能那这篇文章基本是按图索骥的。如果你只是在选型阶段想了解工业状态监测设备到底是怎么做出来的、里面软件和算法是怎么跑的也能从里面获得一个完整的全貌。1. 整体方案设计先画清楚骨架再动手写代码1.1 状态监测系统到底在监测什么工业设备的状态监测核心目标是通过传感器采集设备运行时的物理量分析出设备当前的健康状态。最常见的物理量有三个振动、温度、电流。振动反映机械磨损、轴承故障、转子不平衡温度反映润滑不良、过载、散热失效电流反映电机负载异常、堵转、缺相。我选 STM32 作为主控原因很简单生态成熟、资料多、价格可控F103 系列批量价几块钱、性能足够跑轻量级信号处理。在这个项目里STM32 的角色是边缘节点——它负责采集传感器数据、做简单的特征提取和判断然后把结果通过通信接口上报给上位机或云平台。这种边缘计算的架构有个明显的好处即使网络断了本地异常检测依然在工作不会因为断网而失去监控能力。1.2 软件架构选择裸机还是 RTOS这是整个项目里第一个要决策的问题。状态监测软件涉及的任务不少传感器数据采集、信号处理算法、通信协议解析、状态判断与报警、日志存储。如果全塞在一个 while(1) 里代码会越来越难维护时序也会互相干扰。我建议直接上 RTOS实时操作系统比如 FreeRTOS 或 RT-Thread。可能有人觉得状态监测这种活用裸机也不难确实简单 Demo 裸机能搞定但如果你想把它做成一个可靠的、能持续运行几个月不重启的设备用 RTOS 会让任务边界清晰得多。我在这个项目里用的是 FreeRTOS任务划分如下sensor_task优先级最高负责读取 ADC 或 SPI 传感器的数据周期 1mssignal_process_task负责对原始数据做 FFT、计算 RMS、峰值等特征周期 10mscomm_task负责 Modbus/串口/以太网数据交互周期 100ms 左右monitor_task负责状态判断和报警输出周期 50ms任务之间用消息队列传递数据传感器任务只负责“采”信号处理任务只负责“算”各管一段逻辑非常清晰。1.3 为什么选用“边缘计算 云端汇总”的混合模式我见过不少方案是把所有数据一口气全部传到云端在云端做报警判断。这种方案在实验室做 Demo 没问题到了工业现场就暴露问题了现场网络不稳定、带宽有限、数据量大振动信号动辄每秒几万采样点一旦网络卡顿云端收到的数据全是滞后的报警自然也是滞后的。所以我在设计上做了折衷STM32 本地完成 80% 的实时判断逻辑过阈值、越限、趋势突变只把特征值和报警事件上报原始波形数据按需上传比如设备异常时缓存一段原始数据供人工分析。这样既保证了实时性又保留了数据回溯能力。2. 硬件选型与传感器接入信号链路决定了数据质量2.1 主控芯片选型不止看主频还要看外设资源状态监测项目里主控选型不能只看主频关键要看ADC 采样能力、DMA 通道、定时器触发能力、通信外设数量。我实测下来STM32F407 是性价比很高的选择168MHz 主频、3 个 12 位 ADC、2 个 DMA 控制器还可以通过定时器触发 ADC 实现精确等间隔采样。如果你做更轻量的产品F103 也能跑但注意它的 ADC 是 12 位、最大采样率 1Msps如果只测振动1kHz 以内的信号基本够用要测更宽频带的话建议上 F4 系列。另外有电机控制需求的可以考虑 STM32G4 系列内置 FPU 和更高性能的 ADC 甚至 PGA。选型时还有一个容易忽略的点芯片的引脚封装尽量选 LQFP 的方便手工焊接和调试QFN 封装做样机时很容易虚焊。2.2 振动传感器选型与信号调理振动传感器常用的有两种压电式加速度计和 MEMS 加速度计。压电式精度高、频响宽但需要电荷放大器和 IEPE 恒流源供电电路复杂一些MEMS 加速度计比如 ADXL345、MPU6050便宜、直接输出数字量或模拟量适合做低成本的设备状态监测。我这次用的是 ADXL345——I2C/SPI 接口、±16g 量程、13 位分辨率对大多数工业设备振动监测也够用了。硬件连接很简单ADXL345 的 SDO 接 GNDI2C 地址 0x53、SDA 接 PB9、SCL 接 PB8。需要留意的是ADXL345 的输出是数字量内部有滤波器直接通过 SPI 读取测量结果即可不需要自己搭放大电路。如果你用模拟输出的 MEMS 传感器如 ADXL335那信号调理就是一个必须处理的问题MEMS 输出阻抗较高、信号幅度较小一般只有几百 mV需要在进入 STM32 ADC 前加一级运放偏置电路把信号拉到 ADC 输入范围中间。这里有个非常容易踩的坑不经过运放直接把传感器输出接 MCU 的 ADC 引脚读出来的数据要么溢出、要么灵敏度极低因为 ADC 参考电压和传感器输出范围不匹配。2.3 温度、电流等其他传感器接入温度我用的是 PT100 铂电阻 MAX31865 转换芯片SPI 接口直接读阻值到温度的换算不需要自己算寄存器直读即可。这里提一下三线制和四线制的区别三线制可以补偿导线电阻四线制精度最高。因为状态监测本身就是长期稳定运行的需求建议用四线制 PT100多两根线但省心很多。电流采样用霍尔电流传感器ACS712 或开环霍尔输出是模拟电压直接进 STM32 ADC。注意量程选择让正常工作电流对应到 ADC 量程的 30%~70% 之间这样既能保留过载检测的空间又有足够的低电流分辨率。2.4 通信接口预留有线为主、无线为辅工业现场最可靠的通信方式是 RS485 总线我预留了 RS485SP3485 芯片接口Modbus RTU 协议上报数据。同时留了一个 ESP8266/ESP32 的 UART 接口用于 Wi-Fi 无线上报方便在实验室环境调试上位机。以太网接口W5500也可以加但工业现场很多设备柜里不方便拉网线RS485 是基本功。3. 软件核心逻辑实现从寄存器到算法逐步拆解3.1 ADC 多通道扫描循环采样 DMA振动、温度、电流都要采集我用了 ADC1 的 3 个通道IN0、IN1、IN2配置为扫描模式 循环采样 DMA 搬运。这样 CPU 不用干预数据搬运采集完直接到内存数组里效率很高。配置时几个关键点// 开启 ADC 时钟和 GPIO 时钟 __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // 配置 GPIO 为模拟输入 GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2; GPIO_InitStruct.Mode GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // DMA 配置内存地址自增外设地址固定 hdma_adc.Instance DMA2_Stream0; hdma_adc.Init.Channel DMA_CHANNEL_0; hdma_adc.Init.Direction DMA_PERIPH_TO_MEMORY; hdma_adc.Init.PeriphInc DMA_PINC_DISABLE; hdma_adc.Init.MemInc DMA_MINC_ENABLE; hdma_adc.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; hdma_adc.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; hdma_adc.Init.Mode DMA_CIRCULAR; // 循环模式 HAL_DMA_Init(hdma_adc);DMA 用循环模式ADC 转换完成一次 3 通道后数据自动搬到内存数组里。这样 ADC 就一直满速跑处理器只需要定期去内存里取数据。精度优化STM32 ADC 的绝对精度有限可以参考校准值做软件校准——用高精度万用表实测输入电压和 ADC 读到的值对比做线性修正。我在项目里做了这个校准效果明显误差从 ±2% 缩小到了 ±0.2% 以内。3.2 振动信号的特征提取时域 频域状态监测的核心不是采集数据而是从数据里提取出能表征设备健康状态的特征。我在软件里计算了以下几组特征时域特征RMS 值、峰值、峰峰值、标准差。RMS 反映总振动能量峰值反映瞬时冲击。频域特征通过 FFT 将时域信号变换到频域观察特定频段的能量分布。设备出现故障时某些频段的能量会明显升高。我在工程里移植了 STM32 官方 DSP 库的 FFT 函数。计算 1024 点 FFT 在 STM32F407 上耗时约 0.5msCMSIS-DSP 优化后完全可以满足实时性要求。// 使用 CMSIS-DSP 库做 FFT #include arm_math.h #define FFT_SIZE 1024 static float32_t input[FFT_SIZE * 2]; static float32_t output[FFT_SIZE]; static arm_cfft_instance_f32 fft_instance; // 初始化 arm_cfft_init_f32(fft_instance, FFT_SIZE); // 填充输入数据实部为采样值虚部为0 for (int i 0; i FFT_SIZE; i) { input[2 * i] adc_buffer[i]; input[2 * i 1] 0; } // 执行 FFT arm_cfft_f32(fft_instance, input, 0, 1); // 计算幅值 arm_cmplx_mag_f32(input, output, FFT_SIZE);这里的关键点FFT 之前最好对数据做加窗处理汉宁窗否则频谱泄漏会让结果失真。CMSIS-DSP 库有arm_win_f32接口直接用很方便。3.3 异常判断算法阈值 趋势 逻辑光有特征还不够怎么判断“正常”还是“异常”我的策略是三层判断第一层是绝对阈值判断某一特征超过预设的上限直接报警。比如振动 RMS 超过 4.5mm/s说明设备已经处于危险状态。第二层是趋势判断在绝对阈值没到之前如果特征值在持续上升比如连续 10 分钟 RMS 增加速率超过设定值就要提前预警。这比单纯等阈值触发要早得多真正体现了“状态监测”的价值。第三层是逻辑判断结合多参数做综合判断。比如振动偏高但同时温度正常可能是传感器松了而不是设备故障振动偏高且温度也偏高大概率是轴承润滑不良。这三层判断组合起来误报率比单阈值方式低很多。我实际跑下来误报率从早期单阈值方案的每周 3~4 次降到了一个月不到 1 次。3.4 数据存储SD 卡还是 Flash工业应用里日志存储必不可少。我的做法是Flash 存储配置和事件日志SD 卡存储原始波形数据。STM32 片内 Flash 用来存报警记录、设备配置掉电不丢失SD 卡用来存“异常触发前后各 5120 个采样点”的原始数据这些数据后期可以用 Python 离线分析复现故障场景。SD 卡读写用 FatFs 文件系统SPI 模式足够。注意 SPI 模式下 SD 卡初始化要执行专用的初始化序列CMD0、CMD8、CMD55、ACMD41这部分网上代码很多我直接用 CubeMX 生成的 FatFs 库就搞定了没踩太大的坑。3.5 通信协议Modbus 上报设备要对上位机说话不能自己“自嗨”。我选了 Modbus RTU 作为上报协议原因很简单工业界对 Modbus 的接受度太高了几乎所有 PLC 和组态软件都支持它。硬件用 RS485 半双工9600bps 波特率也可以调到 115200但工业环境 9600 更抗干扰。在 STM32 上实现 Modbus 从站我直接参考了开源的 FreeModbus 库裁剪后移植。地址分配很简单保持寄存器 0x0000设备状态字0正常1预警2报警保持寄存器 0x0001振动 RMS 值放大 100 倍传输保持寄存器 0x0002温度值放大 10 倍传输保持寄存器 0x0003电流值放大 100 倍传输用 CubMX 生成的 UART 中断接收 定时器超时判断一帧结束实现起来不算难。有一点要注意Modbus RTU 帧间隔要求是 3.5 个字符时间9600 波特率下约 4ms定时器超时设为 5ms 比较稳妥。4. 系统联调与问题排查实战中遇到的坑4.1 串口调试常见的“假死”问题调试过程中最让我头疼的一个问题是程序运行一段时间后串口就“假死”了不再响应任何指令。排查了很久原因是 HAL 库串口接收中断处理中我用了阻塞方式处理 Modbus 数据帧在中断里做了耗时的 CRC 校验和数据处理导致其他中断不能及时响应。解决方法是中断服务函数里只做“把字节放入环形缓冲区”这一个动作具体的帧解析、CRC 校验放到主循环或独立任务里去处理。这也是我在很多 STM32 项目里反复强调的——中断服务函数要尽量短把立即要做的做完就退出不要在里面做业务逻辑。4.2 JTAG 引脚冲突这个坑很经典但确实很容易踩我一开始用 PA13、PA14、PA15 做普通 GPIO 来控制 LED结果程序一直跑不起来后来才发现这三个引脚默认是 SWD 调试引脚。如果你要用到这些引脚必须在程序启动时调用__HAL_AFIO_REMAP_SWJ_NOJTAG();或者直接在 CubeMX 的 SYS 配置里 Debug 选择“Serial Wire”会自动禁用 JTAG 只保留 SWD这样 PB3、PB4、PA15 就能当普通 IO 用了。调试引脚冲突是新手最容易困惑的问题排查思路是先看 Pinout 配置里有没有功能冲突再用 ST-Link Utility 把芯片整片擦除验证硬件是否完好。4.3 定时器触发的采样周期不准确做振动分析必须保证等间隔采样否则 FFT 结果会出现严重的频谱混叠。我之前用 HAL_Delay 来做采样定时结果在有大循环处理时采样周期抖动很大FFT 出来的频谱都是毛刺。解决方法是改用定时器中断或 TimerDMA 的触发方式让定时器自动触发 ADC 转换DMA 自动搬运整个采样过程不经过 CPU。这样采样间隔由硬件保证抖动在纳秒级完全满足信号分析需求。如果你非要用软件定时器做采样务必计算清楚中断服务函数的执行时间确保它不是影响采样时刻抖动的主要因素。实测下来中断服务函数超过 10us 就会对 1kHz 以上的振动信号产生明显影响。4.4 常见问题速查表现象可能原因排查/解决方式ADC 读到的数据全是 0GPIO 未配置为模拟输入检查 CubeMX Pinout 的 GPIO Mode 是否为 AnalogADC 数据跳变剧烈参考电压不稳定/采样保持时间不足用 10uF0.1uF 电容对 Vref 滤波增加采样周期DMA 只搬了一次数据就不动了DMA 模式未配置为循环模式确认 Mode 设为 DMA_CIRCULARFFT 结果异常采样率不恒定/未加窗改用定时器硬件触发采样增加汉宁窗串口一接收数据就死机中断里做了耗时操作/缓冲区溢出中断只放数据进环形缓冲解析放到主循环Modbus 上位机读不到数据RS485 方向控制时序不对发送前拉高 DE发送完成后延时再拉低注意延时至少 1 个字符时间程序上电跑一会儿就复位看门狗超时/电源跌落检查 IWDG 喂狗周期是否够电源端加大电容4.5 看门狗的正确用法工业设备必须加看门狗但看门狗用得不对反而会造成“假稳定”。我见过不少程序为了“防止死机”把喂狗放在主循环里结果任何任务阻塞时主循环还在跑看门狗根本起不到监控作用。正确的做法是在最高优先级的任务比如传感器采样任务里喂狗确保关键任务在正常调度才能说明系统还活着。另外喂狗周期建议设置为设备正常任务循环周期的 3~5 倍太短容易误复位太长则失去了保护意义。注意一点IWDG独立看门狗一旦开启就无法软件关闭只能通过复位来重新配置。调试阶段建议先禁用看门狗等程序稳定了再打开否则会频繁看门狗复位干扰调试。5. 把 Demo 做成品工程化要点总结5.1 上电初始化流程做一个“好脾气”的设备设备一上电不要立刻开始采集和上报。我的初始化顺序是时钟和 GPIO 初始化串口/DMA/ADC/传感器初始化读取 Flash 中的配置参数设备地址、报警阈值初始化通信RS485、Wi-Fi执行传感器自检读到的值是否在合理范围内数据采集任务和通信任务启动这个顺序里有几个细节值得注意配置参数必须在传感器采集开始之前读取。我遇到过一个问题设备上电后立刻用默认阈值判断但此时传感器还没稳定采集到的异常数据直接触发了误报。后来加了“传感器稳定延时 500ms 上电前 10 秒不报警”的逻辑这个问题就消失了。5.2 定时器与数据缓存波形回放才是排查故障的利器设备真正出现故障时光看报警信息是不够的还要有原始数据可以分析。我在软件里实现了一个环形缓冲区一直缓存最近 5 秒的振动原始数据一旦触发报警立刻把这 5 秒数据和报警后 5 秒数据打包存储到 SD 卡。这样事后用 Python 做频谱分析能非常清楚地看到故障特征频率。// 环形缓冲简化示例 #define BUFFER_SIZE 5120 static float32_t ring_buffer[BUFFER_SIZE]; static uint32_t write_index 0; static uint32_t read_index 0; void buffer_write(float32_t sample) { ring_buffer[write_index] sample; write_index (write_index 1) % BUFFER_SIZE; // 如果写指针追上读指针丢弃最旧数据 if (write_index read_index) { read_index (read_index 1) % BUFFER_SIZE; } }这个设计在设备稳定运行时几乎不占资源一旦异常触发立刻就能拿到“犯罪现场”的完整数据。5.3 系统资源占用估算我们大致估算一下资源占用STM32F407 有 192KB SRAM我的程序里 FFT 缓冲区用了 8KB1024 点 float32 复数环形缓冲区 20KB任务栈总共分配了 8KB各驱动缓冲区 5KB整体占用不到 50KB留了充足的余量。Flash 方面编译出来代码大小约 80KB在 512KB Flash 里绰绰有余。资源维度占用情况剩余空间Flash代码常量约 80KB430KBSRAM全局堆栈约 50KB140KBCPU 负载1kHz 采样10ms 处理周期约 12%充足这么看即便后续加上更复杂的算法比如小波分析、包络分析资源也完全够用。性能瓶颈不在 MCU而在传感器的频响范围和采样精度所以项目初期的硬件选型才是决定天花板的要素。5.4 从 STM32 项目到产品的“最后一公里”Demo 跑通和产品稳定运行之间差距往往比想象中大得多。我梳理了几个在产品化阶段必须处理的问题电源设计工业现场电源波动大必须加防反接、浪涌抑制和共模电感。状态监测设备一般功耗不高用隔离 DC-DC 模块是不错的选择。电磁兼容设备本身是监测振动的最容易受到变频器等大功率设备的干扰。PCB 布局时模拟地和数字地要单点连接传感器线材用屏蔽线并单端接地。固件升级产品交付后一定会有功能迭代除非你打算把设备拆回来升级否则最好把 Bootloader 加上支持通过通信接口进行固件更新。STM32 的 System Bootloader 支持 UART 下载也可以自己写一套基于 Modbus 的升级协议。远程运维如果设备分散部署在不同地方每台都派人到现场升级和调参会累死运维团队。我在项目里加了远程参数配置功能报警阈值、采样率都支持通过通信接口在线修改这样即使设备装在现场也能远程调整策略。6. 上位机与可视化让监测数据“看得见”6.1 上位机选型从串口助手到可视化界面调试阶段用串口助手就够了能看到原始数据即可。但给客户展示、或者长期观察趋势的时候还是需要一个可视化界面。我第一版用 Python PyQt5 写了简单的上位机读取串口数据后实时绘制曲线。PyQt5 图表库 pyqtgraph 性能很好绘制 100Hz 更新率的曲线毫无压力。如果你不想写界面直接接 Node-RED 或者 Grafana 也行——用 MQTT 协议上报到本机 BrokerGrafana 里配置好数据源几分钟就能出一个漂亮的仪表盘。6.2 云平台对接MQTT 上报数据设备端用的是 ESP8266 模块通过 UART 对接 STM32ESP8266 上跑 AT 固件。STM32 通过 AT 指令连接 Wi-Fi 和 MQTT Broker我用的是本地部署的 EMQX以 JSON 格式上报数据{ device_id: stm32_monitor_01, timestamp: 1703123456, rms: 2.35, temperature: 45.6, current: 3.21, status: normal }JSON 解析在 STM32 上有 cJSON 库可以用裁剪后占用资源很少。注意字符串处理时一定要防止缓冲区溢出网络数据包不可信长度校验必须做。6.3 报警通知短信还是 App 推送报警通知是状态监测系统的“最后一环”。工业场景里很多设备间没人值守如果报警没人看到再及时也没用。我这边是通过云平台的规则引擎把报警事件转成 Webhook 推送到企业微信/钉钉群。这样做的好处是实施成本极低不需要开发 App维护人员只要加群就能收到报警推送。需要提醒的是报警通知一定要做去重和延迟确认。设备振动一旦超出阈值传感器每 50ms 就可能产生一个报警事件如果每个都推送群消息就爆炸了。我的做法是同一报警源在 10 分钟内只推送一条直到状态恢复正常再重新允许推送。7. 阶段性成果与指标用数据说话项目完成第一版联调后我在实验室环境跑了两周的稳定性测试同时在电机试验台上做了故障注入实验通过人为改变轴承预紧力模拟磨损振动 RMS 测量误差≤±3%对比高精度商用振动计温度测量误差≤±0.5℃4 线制 PT100 MAX31865异常检出率注入故障 20 次检出 20 次漏报 0 次误报率两周内 0 次系统连续无故障运行超过 14 天有外部复位及看门狗策略生效这些结果算是在不用高端 DSP 的前提下达到了不错的平衡。当然如果要覆盖更宽频段、更高精度的信号分析STM32H7 甚至外接 FPGA 会是下一步的方向但成本和技术复杂度都会明显提升。我自己做完这个项目最大的感受是状态监测系统的核心不是“监测”本身而是“判断”和“提前量”。同样的传感器、同样的 MCU算法和软件架构的好坏直接决定了这套设备是“会叫的看门狗”还是“只会录数据的数据记录仪”。希望这篇文章能帮你少走一些弯路。
返回列表