STM32水位检测控制系统:从传感器滤波到泵控闭环实战

STM32水位检测控制系统:从传感器滤波到泵控闭环实战 简介这份文档是一篇面向电子信息、自动化及嵌入式方向毕业设计与课程设计的完整论文围绕基于STM32F103C8T6单片机与阻压式水位检测传感器搭建的水位监测控制报警系统展开。论文系统梳理了硬件选型与接口设计、软件主程序及数据采集、LCD1602显示、按键设置、蜂鸣器报警、继电器水泵控制等模块并给出系统测试与验证过程。系统通过LCD1602和串口上位机显示水位高度MV与报警值ALM按键可修改报警阈值默认低于150cm触发蜂鸣器拨动开关接通后水泵抽水。资源包含1个doc文档约22KB即完整论文正文涵盖绪论、总体设计、硬件设计、软件设计等章节目录结构清晰。已有183人学习下载适合需要参考论文框架、功能定义与模块划分的读者可用于快速理解水位检测控制类题目的实现思路与文档写作要点。1. 水位检测控制系统的难点不在测而在稳水箱、水塔、河道、鱼缸只要涉及液位就绕不开两个动作把水位测出来把水泵或阀门控起来。很多人第一次用 STM32 做水位检测控制系统代码跑通当天就以为完事了结果现场一上电继电器一分钟内咔哒几十次水泵电机发烫屏幕上数字像随机数一样跳。问题不在 MCU 算得慢而在于没处理三件事传感器原始值的抖动、控制阈值附近的临界震荡、执行机构的启动代价。这三点合起来才是这类系统真正要交付的东西——一个能长期无人值守的水位闭环而不是一次漂亮的单点测量。下面的内容按传感器选型、采样与滤波、控制策略、联调排错四段推进代码给的是可以直接抄的骨架参数按实际池体改。2. 水位传感器选型与 STM32 的硬件链路怎么搭选型决定了后面 80% 的软件复杂度。同样是「水位」装在开放河道、密闭水箱、深井里能用的传感器完全不是一类。基于 STM32 的毕业设计里HC-SR04 出现频率最高但如果目标是污水池非接触式方案才是唯一选择。2.1 五类水位传感器的量程、精度与安装边界方案典型量程精度安装方式输出适合场景超声波 HC-SR042cm4m±3mm±1cm顶部非接触回波脉宽清水箱、河道、毕设投入式压力变送器05m±0.5%FS底部投入4-20mA / 0.5-4.5V深井、污水、有泡沫电容式液位01m±1%FS贴壁外置电压/频率小水箱、强腐蚀液体电极探针固定点位开关量侧壁插入高低电平上下限报警点浮球开关固定点位开关量顶部悬挂干接点泵的防干烧保护超声波便宜、无接触、量程够用缺点是怕蒸汽、怕泡沫、怕强风而且有 2cm 左右盲区压力变送器稳定性最好但长期泡在污水里会结垢需要定期提出来清洗。一个工程化的做法是超声波做连续测量浮球开关做冗余的干烧保护两个信号在 STM32 里做逻辑与任一路报异常就停泵。2.2 超声波方案在 STM32 上的接线与电平处理HC-SR04 的工作电压是 5VECHO 引脚输出也是 5V 高电平。STM32 的 GPIO 绝大多数不是 5V 容忍的直连会在几个月内把引脚打坏运气差的当场就锁死。常见做法是 ECHO 串一个 1kΩ 电阻、再对地并 2kΩ把 5V 分压到 3.3V 左右TRIG 由 STM32 直接推 3.3V 也能被模块识别。/* hcsr04.h —— 触发与电平约定 */ #define TRIG_GPIO_Port GPIOA #define TRIG_Pin GPIO_PIN_1 #define ECHO_TIM (htim2) /* PA0 - TIM2_CH1输入捕获 */ #define ECHO_TIM_CH TIM_CHANNEL_1 /* 12us 的触发脉冲用 1MHz 自由计数器做微秒延时 */ static void delay_us(uint32_t us) { uint32_t start __HAL_TIM_GET_COUNTER(htim7); /* TIM7 配置为 1MHz */ while ((__HAL_TIM_GET_COUNTER(htim7) - start) us) { } } void hcsr04_trigger(void) { HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(12); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); }逻辑说明先把 TRIG 拉高保持 12us 以上模块内部自动发 8 个 40kHz 脉冲然后把 ECHO 拉高高电平持续时间与往返距离成正比。参数说明延时函数没有用 HAL_Delay是因为 HAL_Delay 的最小粒度是 1ms而 12us 和几十微秒的回波窗口都要求微秒级TIM7 预分频到 1MHz 后每个计数就是 1us。注意 TIM7 的计数器是 16 位溢出周期 65.5ms远大于单次延时不会误判。2.3 压力变送器接 ADC4-20mA 转电压的采样电阻4-20mA 是工业变送器的标准输出STM32 本身读电流需要一颗采样电阻把电流变电压。取值要同时满足两个约束最大电流对应的电压不超过 ADC 参考电压最小电流对应的电压又不要太小以免浪费分辨率。采样电阻4mA 对应电压20mA 对应电压结论100Ω0.40V2.00V分辨率损失大150Ω0.60V3.00V3.3V 参考下推荐165Ω0.66V3.30V顶到参考电压无余量220Ω0.88V4.40V超出 3.3V损坏 ADC150Ω 是留了余量的稳妥选择。采样电阻要用金属膜、低温漂的普通碳膜电阻随温度变化几个百分点冬天和夏天的水位读数就能差出好几厘米。变送器是两线制时供电和信号共用回路注意电源的电流能力要大于 20mA。2.4 从 ADC 码值到毫米两点标定公式ADC 读回来的是 04095 的裸码值必须做一次线性映射才能变成有物理意义的毫米数。最可靠的方式是两点标定把池子放到已知低位记下码值再加到接近满量程记下码值。/* 两点标定空池码值 820满量程码值 4095对应 2000mm 池深 */ #define CODE_EMPTY 820.0f #define CODE_FULL 4095.0f #define RANGE_MM 2000.0f float adc_code_to_mm(uint16_t code) { float mm (code - CODE_EMPTY) * RANGE_MM / (CODE_FULL - CODE_EMPTY); if (mm 0.0f) mm 0.0f; /* 下限钳位防止负水位 */ if (mm RANGE_MM) mm RANGE_MM; /* 上限钳位 */ return mm; }逻辑说明斜率由量程除以码值跨度得到偏移量由空池码值确定。参数说明CODE_EMPTY 和 CODE_FULL 不能靠数据手册推算必须实测——每个变送器的零点都有偏差导线压降也会让实际码值偏个几十。钳位不能省传感器断线时 ADC 可能读满量程或接近零没有钳位就会把 0 或 4095 当成真实水位传给控制层后患无穷。3. 用 STM32 定时器输入捕获与 ADC 把水位读准硬件链路确定以后采样质量就是系统的天花板。超声波的回波宽度在几十微秒到几十毫秒之间用 GPIO 轮询去量误差能到几百微秒换算成距离就是几厘米用 STM32 的定时器输入捕获误差是一个计数周期。3.1 TIM 输入捕获测量超声波回波宽度思路是让 TIM2_CH1 工作在输入捕获模式先捕获上升沿捕获到以后立刻把极性切成下降沿第二次捕获中断里算出两个计数值之差就是回波时间。/* TIM2: PSC71, ARR0xFFFF, 时钟 72MHz - 计数频率 1MHz, 1 tick 1us */ volatile uint32_t t_rise 0, t_fall 0; volatile uint32_t echo_us 0; volatile uint8_t cap_state 0; /* 0等上升沿 1等下降沿 */ volatile float distance_cm 0.0f; void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Channel ! HAL_TIM_ACTIVE_CHANNEL_1) return; if (htim-Instance ! TIM2) return; if (cap_state 0) { t_rise HAL_TIM_ReadCapturedValue(htim, ECHO_TIM_CH); cap_state 1; __HAL_TIM_SET_CAPTUREPOLARITY(htim, ECHO_TIM_CH, TIM_INPUTCHANNELPOLARITY_FALLING); } else { t_fall HAL_TIM_ReadCapturedValue(htim, ECHO_TIM_CH); /* 16 位计数器若下降沿计数小于上升沿说明期间溢出过一次 */ echo_us (t_fall t_rise) ? (t_fall - t_rise) : (0xFFFFu - t_rise t_fall); distance_cm echo_us / 58.0f; /* 声速近似 340m/s */ cap_state 0; __HAL_TIM_SET_CAPTUREPOLARITY(htim, ECHO_TIM_CH, TIM_INPUTCHANNELPOLARITY_RISING); } }逻辑说明单通道双沿切换的好处是不占用两个捕获通道也避免了两通道各自溢出的对齐问题。参数说明PSC71 是把 72MHz 分频到 1MHz这个值随主频变如果用的是 168MHz 的型号PSC 要设成 167。swap 分支处理的是回波时间超过 65.5ms 的情况正常量程内不会走到但风噪、多次回波反射时会偶发必须兜住否则算出一个天文数字距离。顶部安装时真实水位 池深 - 空高即water_mm tank_h_mm - distance_cm * 10。还有一个容易被忽略的保护如果 ECHO 一直不拉低cap_state 会永久停在 1后续所有测量失效。做法是每次触发前记录时间戳超过 60ms 还没收到下降沿就强制复位极性并丢弃本次数据。3.2 多通道 ADC DMA 采集压力与水温压力变送器、水温传感器这类慢变量用 DMA 后台搬运最省 CPU主循环只管在需要的时候读缓冲区。/* ADC1 规则组扫描 2 个通道CH0 压力、CH1 水温DMA 循环模式 */ uint16_t adc_buf[2]; HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, 2); /* 主循环里直接取用DMA 会周期性刷新 */ float pressure_mm adc_code_to_mm(adc_buf[0]); float temp_c (adc_buf[1] * 3.3f / 4095.0f - 0.5f) * 100.0f; /* LM35 */逻辑说明DMA 设成循环模式后ADC 按扫描序列不停采缓冲区里的值永远是最新一帧不需要在中断里做任何处理。参数说明adc_buf长度必须等于规则组通道数写成(uint32_t *)是 HAL 的函数签名要求缓冲区别忘了加volatile或至少保证不被编译器优化掉。水温数据用来做温度补偿——声速随温度变化约 0.6m/s 每摄氏度温差 20℃ 时超声波测距的误差能到 1% 以上。3.3 中值滤波与一阶低通的组合顺序原始数据先中值去脉冲再低通去高频抖动顺序不能反。反过来做低通会把一个孤立的大跳变「抹」成好几个中间值中值滤波就抓不住它了。#define WIN 9 static uint16_t win[WIN]; static uint8_t widx 0; static float lv_smooth 0.0f; /* 9 点中值滤波窗口长度取奇数 */ uint16_t median9(const uint16_t *src) { uint16_t tmp[WIN]; memcpy(tmp, src, sizeof(tmp)); for (int i 0; i WIN - 1; i) { for (int j 0; j WIN - 1 - i; j) { if (tmp[j] tmp[j 1]) { uint16_t t tmp[j]; tmp[j] tmp[j 1]; tmp[j 1] t; } } } return tmp[WIN / 2]; } float level_filter(float new_mm) { win[widx] (uint16_t)new_mm; widx (widx 1) % WIN; float med median9(win); lv_smooth lv_smooth 0.2f * (med - lv_smooth); /* 一阶低通 a0.2 */ return lv_smooth; }逻辑说明冒泡排序在这里完全够用9 个元素的排序开销比调用一次通用 qsort 还小而且不引入额外依赖。参数说明见下表这几个值是调这套系统时最需要反复动的。参数建议值说明超声波采样周期60100ms间隔太短会收到上一次的残余回波中值窗口 WIN59越大越能压脉冲但引入延迟一阶低通系数 a0.10.3越小越平滑越大越跟得上真实变化回波超时阈值60ms超过即判定为无效帧ADC 采样周期1050ms压力变送器响应本身在百毫秒级温度补偿更新1s水温变化慢无需高频更新滤波强度和实时性是矛盾的。我的经验是先用 a0.2 跑观察串口打出来的曲线如果真实加水过程被压成一条十几秒才爬上去的斜线说明滤波过头如果静止时数字还在个位数跳动说明还不够。窗口和系数都要配合实际池体的截面积调——截面积小的水箱水位变化快滤波要轻。4. 水泵控制策略迟滞开关与增量式 PID 的落地测量做完了控制才是决定设备寿命的部分。水位控制最常见的事故不是测不准而是泵被频繁启停烧掉或者进水阀在目标值附近来回振荡。4.1 单阈值开关为什么会把水泵烧热用「低于 500mm 开泵、高于 500mm 停泵」这种单阈值逻辑水位在 500mm 附近时噪声会把读数推上推下继电器一秒切换好几次。触点拉弧、电机反复承受启动电流几天就坏。正确做法是加迟滞带低于下限才开高于上限才停中间区域保持当前状态。上下限之间的距离叫回差一般取量程的 5%15%。#define LV_START_MM 600.0f /* 低于此值启泵 */ #define LV_STOP_MM 1800.0f /* 高于此值停泵 */ #define PUMP_MIN_MS 30000u /* 泵最短运行时间保护电机 */ typedef enum { PUMP_OFF 0, PUMP_ON 1 } pump_state_t; static pump_state_t st PUMP_OFF; static uint32_t t_switch 0; void pump_control(float lv_mm, uint32_t now_ms) { switch (st) { case PUMP_OFF: if (lv_mm LV_START_MM (now_ms - t_switch) PUMP_MIN_MS) { HAL_GPIO_WritePin(PUMP_GPIO_Port, PUMP_Pin, GPIO_PIN_SET); st PUMP_ON; t_switch now_ms; } break; case PUMP_ON: if (lv_mm LV_STOP_MM) { HAL_GPIO_WritePin(PUMP_GPIO_Port, PUMP_Pin, GPIO_PIN_RESET); st PUMP_OFF; t_switch now_ms; } break; } }逻辑说明状态机保证同一时刻只有一个动作分支被执行回差由 START/STOP 两个常量天然形成最短运行时间再兜一层。参数说明LV_START_MM 通常取目标水位的 60%70%LV_STOP_MM 取上限PUMP_MIN_MS 对潜水泵取 30s 以上对小型隔膜泵 10s 即可。这套代码在 STM32 里由 100ms 的定时器中断或主循环调用now_ms用HAL_GetTick()传入。4.2 增量式 PID 调节进水阀或 PWM 泵如果执行机构不是启停型而是比例阀或者带 PWM 驱动的调速泵就可以上 PID。这里推荐增量式而不是位置式因为增量式输出的是「这次比上次多给多少」天然带积分限幅切换手动自动时也不会有大跳变。/* 增量式 PID输出增量 du累加到 PWM 占空比上 */ static float e1 0.0f, e2 0.0f; float pid_inc(float setpoint, float feedback) { const float Kp 0.80f; const float Ki 0.15f; const float Kd 0.05f; float e0 setpoint - feedback; float du Kp * (e0 - e1) Ki * e0 Kd * (e0 - 2.0f * e1 e2); e2 e1; e1 e0; return du; } /* 调用侧限幅 累加 */ static float duty 0.0f; void valve_update(float target, float actual) { duty pid_inc(target, actual); if (duty 100.0f) duty 100.0f; if (duty 0.0f) duty 0.0f; __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, (uint32_t)(duty * 99.0f)); }逻辑说明增量式公式里比例项只跟误差变化量有关积分项就是当前误差微分项是三点的二阶差分。参数说明Kp 决定响应速度水位系统惯性大Kp 一般不超过 1.5Ki 用来消除稳态误差给太大会在目标值附近来回晃从 0.05 开始往上加比较稳Kd 对噪声极其敏感滤波没做好之前建议先置 0。调参顺序是先把 Ki、Kd 置零加大 Kp 直到出现小幅振荡再回退到振荡的 60%然后加 Ki 消掉静差最后视情况加一点 Kd。串口调试时要同步把目标值、反馈值、输出占空比三个量一起打出来只看其中一个根本判断不出问题出在哪。4.3 继电器、MOSFET 与固态继电器的驱动取舍执行器件驱动电流寿命隔离适用功率注意点机械继电器模块520mA10 万次光耦220V/10A 内触点拉弧加 RC 吸收N 沟道 MOSFET栅极近乎为零无限次无DC 30V/5A 内低边驱动加续流二极管固态继电器 SSR10mA 左右百万次光耦220V/2A 内漏电流大不能当干接点电机驱动模块逻辑侧 mA 级视模块视模块视模块而定注意共地直流小泵优先用 MOSFET 低边驱动栅极串 100Ω 电阻抑制振铃栅源之间并 10kΩ 下拉防止上电误导通泵两端反并一颗肖特基或快恢复二极管吸收关断反峰。交流泵用继电器时必须加 RC 吸收网络或者压敏电阻否则触点寿命会打对折。SSR 的漏电流能到几毫安用来控 LED 指示灯可能关不掉这点在选型时要留意。5. 联调与排错让水位曲线在串口上跑起来代码各自调通不代表系统能跑。水位控制系统真正的验证是让它在实际工况下连续跑几个小时不出异常这需要一套可观测的调试手段。5.1 串口输出水位曲线与关键事件最有效的方式是把时间戳、原始值、滤波值、泵状态四个量按 CSV 打出来用串口助手保存成文件直接丢进表格里画折线。/* 每 200ms 打一行格式ms,raw_us,level_mm,pump */ printf(%lu,%lu,%.1f,%d\r\n, HAL_GetTick(), echo_us, lv_smooth, (int)st);逻辑说明CSV 格式的好处是既能人眼扫也能直接导入分析。参数说明波特率统一用 115200 8N1用 printf 需要重定向fputc到HAL_UART_Transmit或者干脆用 DMA 串口发送避免阻塞主循环。打印周期不要低于采样周期否则串口会成为瓶颈。5.2 常见故障与对应处理现象可能原因处理方式距离恒为 0 或满量程ECHO 未分压、极性切错示波器看 ECHO 波形偶发跳变几米多次回波、风噪加大采样间隔超时丢帧ADC 读数缓慢漂移采样电阻温漂、地噪声加 RC 滤波靠近 MCU 单点接地泵反复启停回差过小或无最短运行限制检查 LV_START/STOP 与时间戳串口乱码波特率或时钟配置不符核对晶振与 PLL 分频长时间运行后死机数组越界、看门狗未开开 IWDG检查 DMA 缓冲长度干烧保护是必须单独做的一路浮球开关接一个带外部中断的 GPIO触发后立刻拉停泵并置故障标志软件层面再配合「泵运行超过 N 分钟水位仍无变化」的逻辑做二次判断。IWDG 独立看门狗按 4s 超时配置主循环里喂狗一旦程序跑飞能自动复位而不是让泵一直转。传感器断线检测也别省连续 10 帧读数超出合理量程就报故障并切到安全状态——宁可停机等人来处理也不要凭一个坏数据做决策。本文还有配套的精品资源点击获取