1. 项目概述
这个基于STM32的流量监测系统是我最近完成的一个工业级解决方案,主要用于液体流速和流量的实时监测与控制。核心功能包括流速检测、流量统计、阈值报警、水泵控制等,特别适合应用于农业灌溉、工业循环水系统等场景。
系统采用STM32F103C8作为主控芯片,搭配齿轮流量计(仿真阶段使用信号发生器模拟)、LCD1602显示屏、蜂鸣器报警模块和水泵控制电路组成完整闭环系统。在实际测试中,系统能够稳定运行,测量误差控制在±2%以内,完全满足大多数工业场景的需求。
2. 硬件设计详解
2.1 核心硬件选型
主控芯片选择STM32F103C8主要基于以下考虑:
- 72MHz主频足够处理流量计算和控制逻辑
- 内置多个定时器,特别适合脉冲计数应用
- 丰富的外设接口(USART、GPIO等)
- 成本优势明显,市场供应充足
传感器部分采用霍尔效应齿轮流量计,每个齿轮齿通过时会产生一个脉冲信号。实际应用中,我们测试了多种型号,最终选定每转产生8个脉冲的型号,配合特定尺寸的管道,实现每脉冲对应0.02升的流量分辨率。
2.2 电路设计要点
电源部分采用两级稳压设计:
- 第一级:12V转5V(LM7805)
- 第二级:5V转3.3V(AMS1117)
特别在电源输入端加入了TVS二极管和1000μF电解电容,有效抑制电网波动和浪涌。PCB布局时将数字部分和模拟部分分开,中间用0Ω电阻作为单点接地。
重要提示:水泵控制继电器必须单独供电,避免电机干扰影响MCU稳定性。我们在继电器线圈两端并联了1N4007续流二极管,实测可将反电动势降低85%。
3. 软件架构设计
3.1 主程序流程
系统采用前后台架构:
- 主循环处理非实时任务(显示更新、按键扫描等)
- 中断服务程序处理实时性要求高的任务(脉冲计数)
c复制int main(void) {
hardware_init(); // 硬件初始化
timer_init(); // 定时器配置
usart_init(); // 串口初始化
while(1) {
update_display(); // 刷新LCD显示
key_process(); // 按键处理
pump_control(); // 水泵控制
data_report(); // 数据上报
delay_ms(100); // 主循环周期100ms
}
}
3.2 脉冲计数实现
使用TIM2的输入捕获功能实现高精度脉冲计数:
c复制// TIM2初始化配置
void TIM2_Init(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 时基配置
TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 1MHz计数频率
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 输入捕获配置
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
// 启用中断
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
NVIC_EnableIRQ(TIM2_IRQn);
TIM_Cmd(TIM2, ENABLE);
}
4. 关键算法实现
4.1 流量计算模型
流量计算基于以下公式:
瞬时流量(Q) = (ΔP × K) / Δt
其中:
- ΔP:采样周期内的脉冲数变化量
- K:流量计系数(升/脉冲)
- Δt:采样周期(秒)
c复制#define K_FACTOR 0.02f // 每脉冲对应0.02升
float calculate_flow_rate(uint32_t current_pulse, uint32_t last_pulse) {
static uint32_t last_pulse_count = 0;
float flow_rate;
// 防止计数器溢出处理
if(current_pulse < last_pulse_count) {
flow_rate = ((0xFFFFFFFF - last_pulse_count + current_pulse) * K_FACTOR) / SAMPLING_TIME;
} else {
flow_rate = ((current_pulse - last_pulse_count) * K_FACTOR) / SAMPLING_TIME;
}
last_pulse_count = current_pulse;
return flow_rate;
}
4.2 滑动窗口滤波算法
为消除流量突变带来的误报警,采用8点滑动窗口滤波:
c复制#define WINDOW_SIZE 8
typedef struct {
float buffer[WINDOW_SIZE];
uint8_t index;
float sum;
} FilterWindow;
float sliding_window_filter(FilterWindow *window, float new_value) {
// 减去最早的值
window->sum -= window->buffer[window->index];
// 加入新值
window->sum += new_value;
window->buffer[window->index] = new_value;
// 更新索引
window->index = (window->index + 1) % WINDOW_SIZE;
return window->sum / WINDOW_SIZE;
}
5. 人机交互实现
5.1 按键状态机设计
使用有限状态机处理按键逻辑,提高系统响应可靠性:
c复制typedef enum {
KEY_IDLE,
KEY_DEBOUNCE,
KEY_PRESSED,
KEY_LONG_PRESS
} KeyState;
void key_process(void) {
static KeyState state = KEY_IDLE;
static uint32_t press_time = 0;
switch(state) {
case KEY_IDLE:
if(KEY_PRESSED()) {
state = KEY_DEBOUNCE;
press_time = HAL_GetTick();
}
break;
case KEY_DEBOUNCE:
if((HAL_GetTick() - press_time) > 20) { // 20ms消抖
if(KEY_PRESSED()) {
state = KEY_PRESSED;
// 短按处理
}
} else {
state = KEY_IDLE;
}
break;
case KEY_PRESSED:
if(!KEY_PRESSED()) {
state = KEY_IDLE;
} else if((HAL_GetTick() - press_time) > 1000) { // 长按1秒
state = KEY_LONG_PRESS;
// 长按处理
}
break;
case KEY_LONG_PRESS:
if(!KEY_PRESSED()) {
state = KEY_IDLE;
}
break;
}
}
5.2 LCD显示优化
针对工业环境光照条件,采用以下优化措施:
- 使用高对比度LCD模块(负显效果更佳)
- 增加背光自动调节功能
- 关键数据采用闪烁提示
c复制void update_display(void) {
char buffer[17];
// 第一行:瞬时流量
snprintf(buffer, sizeof(buffer), "Flow:%6.2f L/s", current_flow);
LCD_SetCursor(0, 0);
LCD_WriteString(buffer);
// 第二行:累计流量/阈值
snprintf(buffer, sizeof(buffer), "Total:%5.1f/%5.1f", total_volume, threshold);
LCD_SetCursor(0, 1);
LCD_WriteString(buffer);
// 报警状态闪烁提示
if(alarm_status && (HAL_GetTick() % 500 < 250)) {
LCD_SetCursor(15, 1);
LCD_WriteChar('!');
}
}
6. 系统调试与优化
6.1 常见问题排查
在实际调试中遇到的典型问题及解决方案:
-
脉冲计数不准确
- 原因:传感器信号抖动
- 解决:硬件增加RC滤波(10kΩ+0.1μF),软件增加10ms消抖
-
水泵频繁启停
- 原因:阈值附近波动
- 解决:引入滞回比较,设置±5%的滞回区间
-
LCD显示乱码
- 原因:电源干扰
- 解决:增加LCD电源端100nF去耦电容,缩短排线长度
6.2 性能优化技巧
-
中断优化
- 将非关键中断设为低优先级
- 中断服务函数尽量简短
-
内存管理
- 使用静态分配替代动态内存
- 关键变量添加volatile修饰
-
电源管理
- 未使用的外设时钟默认关闭
- 低功耗模式下可关闭LCD背光
7. 扩展功能实现
7.1 无线传输模块
系统预留了USART接口,可方便接入Wi-Fi或蓝牙模块。推荐使用ESP-01S模块,成本低廉且易于集成:
c复制void wifi_send_data(void) {
char json_buffer[64];
snprintf(json_buffer, sizeof(json_buffer),
"{\"flow\":%.2f,\"total\":%.2f,\"thres\":%.2f}",
current_flow, total_volume, threshold);
USART_SendString(USART2, "AT+CIPSEND=0,");
USART_SendString(USART2, itoa(strlen(json_buffer), 10));
USART_SendString(USART2, "\r\n");
delay_ms(100);
USART_SendString(USART2, json_buffer);
}
7.2 数据存储功能
使用SPI接口的Flash芯片(如W25Q16)实现数据存储:
c复制#define FLASH_PAGE_SIZE 256
#define FLASH_BASE_ADDR 0x000000
void save_to_flash(void) {
uint8_t buffer[FLASH_PAGE_SIZE];
// 准备数据
memcpy(buffer, ¤t_flow, sizeof(current_flow));
memcpy(buffer+4, &total_volume, sizeof(total_volume));
// 其他数据...
// 擦除并写入
FLASH_ErasePage(FLASH_BASE_ADDR);
FLASH_WriteBuffer(buffer, FLASH_BASE_ADDR, FLASH_PAGE_SIZE);
}
8. 实际应用建议
-
安装注意事项
- 流量计应安装在管道的直线段,前后保留10倍管径的直管段
- 避免安装在泵出口或阀门附近
- 确保管道充满液体,避免气泡影响
-
校准方法
- 准备标准容器(如10L量筒)
- 记录脉冲数与实际流量对比
- 调整K_FACTOR参数直至误差最小
-
维护周期
- 每3个月检查传感器灵敏度
- 每半年校准一次流量系数
- 定期检查管道密封性
这个项目从原型到稳定运行历时两个月,期间解决了多个工程实际问题。核心经验是:工业级应用必须考虑环境因素(温度、湿度、振动)对系统的影响,良好的硬件设计是基础,可靠的软件算法是关键。系统目前已在三个灌溉项目中实际应用,最长连续运行时间超过180天无故障。