1. 项目概述:软硬件联动的液位控制系统
这个项目本质上是一个工业级液位控制系统的精简版实现,核心在于打通STM32单片机与LabVIEW上位机的双向通信链路。我在去年为某净水设备厂商做技术咨询时,就遇到过几乎相同的需求场景——只不过他们的版本需要支持Modbus RTU协议和4-20mA信号传输。这次分享的方案虽然简化了工业协议栈,但完整保留了闭环控制的核心逻辑。
硬件架构上,系统采用模块化设计:
- 感知层:模拟量输出的液位传感器(0-5V对应0-100cm量程)
- 控制层:STM32F103C8T6最小系统板(72MHz主频的Cortex-M3内核)
- 执行层:两组10A容量的磁保持继电器(省电且状态可保持)
- 人机界面:0.96寸SSD1306 OLED屏(128x64分辨率)
- 通信接口:CH340G USB转TTL模块(兼容3.3V电平)
软件部分则形成双机协作:
- 下位机:基于Keil MDK开发的固件(标准库与HAL库双版本)
- 上位机:LabVIEW 2018/2023双版本工程文件
关键设计原则:所有执行器必须带硬件互锁,这是我在工业现场用炸三个继电器换来的教训。图中可见PCB上专门预留了续流二极管的位置。
2. 硬件设计关键细节
2.1 传感器信号调理电路
市售液位传感器输出普遍存在两个问题:输出阻抗高(约10kΩ)和带有工频干扰。我的解决方案是设计两级运放电路:
- 第一级采用TI的INA128仪表放大器,将传感器输出的差分信号转换为单端信号
- 第二级用OP07搭建二阶低通滤波器(截止频率15Hz)
c复制// ADC采样配置要点
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
实测该方案在50Hz工频环境下,可将测量波动控制在±0.5%FS以内。注意ADC参考电压必须接低噪声LDO(如AMS1117-3.3),普通开关电源会导致末位数据跳变。
2.2 继电器驱动安全设计
双继电器控制水泵正反转时,必须确保:
- 物理互锁:两组触点常闭端交叉连接
- 软件互锁:在GPIO操作函数中加入状态检查
c复制void Relay_Control(PumpAction action) {
static uint8_t last_state = 0xFF;
// 状态未变化时直接返回
if(last_state == action) return;
// 先断开所有继电器
GPIO_SetBits(IN_PORT, IN_PIN);
GPIO_SetBits(OUT_PORT, OUT_PIN);
delay_ms(20); // 确保机械触点完全分离
// 根据指令吸合对应继电器
if(action == PUMP_IN) {
GPIO_ResetBits(IN_PORT, IN_PIN);
} else if(action == PUMP_OUT) {
GPIO_ResetBits(OUT_PORT, OUT_PIN);
}
last_state = action;
}
3. 下位机软件架构
3.1 多任务调度设计
虽然使用裸机开发,但仍需实现伪多任务机制。我的方案是将功能模块划分为:
- 高优先级任务:通信中断服务(USART1_IRQHandler)
- 中优先级任务:控制算法(每100ms执行)
- 低优先级任务:界面刷新(每300ms执行)
c复制void SysTick_Handler(void) {
static uint32_t tick = 0;
tick++;
if(tick % 10 == 0) { // 100ms
Control_Task();
}
if(tick % 30 == 0) { // 300ms
Display_Task();
}
}
3.2 自定义通信协议
为简化开发,没有采用Modbus而是自定义了轻量协议:
| 字节位置 | 内容 | 说明 |
|---|---|---|
| 0 | 0xAA | 帧头 |
| 1 | 指令类型 | 0x01:设置 0x02:查询 |
| 2-3 | 数据载荷 | 大端格式 |
| 4 | 0x55 | 帧尾 |
协议特点:
- 固定5字节长度
- 带简单校验和(字节1+字节2+字节3)
- 超时重传机制(500ms无应答重发)
4. LabVIEW上位机开发技巧
4.1 串口通信优化
新手常犯的错误是直接使用"VISA读取"节点,这会导致数据包截断。正确做法是:
- 配置串口参数时,必须设置终止符为0x55(与协议帧尾一致)
- 使用"字节数组至字符串转换"时选择"二进制"模式
- 添加超时处理分支,防止界面卡死

4.2 波形显示性能优化
当采集周期小于200ms时,直接使用Waveform Chart会导致界面卡顿。我的解决方案是:
- 启用图表缓冲(Buffer Size设为1000点)
- 使用"创建波形"函数替代直接赋值
- 在While循环中添加20ms等待
5. 系统调试经验
5.1 电磁兼容问题处理
在现场测试时遇到继电器动作导致ADC采样异常的情况,通过以下措施解决:
- 在继电器线圈两端并联1N4007二极管
- ADC输入线改用双绞线
- 软件上增加中值滤波算法
5.2 水位控制算法优化
最初使用简单的开关控制,导致水泵频繁启停。改进方案:
- 加入死区控制(±3cm)
- 采用PWM调速控制(需换用支持PWM的水泵)
- 增加滞后补偿算法
c复制// 改进后的控制逻辑
void Control_Algorithm(void) {
static uint8_t last_action = STOP;
float error = current_level - target_level;
if(fabs(error) > 3.0) { // 死区控制
if(error > 0 && last_action != PUMP_OUT) {
Relay_Control(PUMP_OUT);
last_action = PUMP_OUT;
}
else if(error < 0 && last_action != PUMP_IN) {
Relay_Control(PUMP_IN);
last_action = PUMP_IN;
}
} else {
if(last_action != STOP) {
Relay_Control(STOP);
last_action = STOP;
}
}
}
6. 工程文件管理建议
-
代码版本控制:
- STM32工程使用Git管理,忽略Output和Listing文件夹
- LabVIEW项目启用"分离编译"模式
-
文档规范:
- 硬件BOM表注明器件采购链接
- 软件API文档使用Doxygen生成
-
测试记录:
- 保留串口通信原始日志
- 截图记录不同水位下的控制效果
这个项目最让我惊喜的是OLED的功耗表现——整套系统在待机状态下,整机电流仅8.7mA(5V供电),这意味着用2000mAh的充电宝可以连续工作超过9天。后来在实际部署时,客户要求增加4G远程监控功能,我又在现有架构上叠加了ESP-01S模块,通过AT指令与STM32交互,不过那就是另一个故事了
