ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

水位传感器原理与STM32实战:从ADC读取到滤波阈值判断

水位传感器原理与STM32实战:从ADC读取到滤波阈值判断 传感器入门最常见的误区就是把“水位传感器”想得太高级觉得它真能像尺子一样告诉你水位现在是多少厘米。实际上——起码是这种十几块钱的模块——它连“称重式”都算不上本质就是一个会随湿度变化阻值的可变电阻。这篇文章我就拿它配合STM32从原理讲到代码再到实测数据和踩坑记录把这颗小模块一次说透。下面所有内容都用我手里这颗最常见的Water Sensor水位传感器板子上带一个LM393比较器和一颗电位器为对象供你直接上手参考。1. 这块传感器到底是怎么工作的核心原理1.1 从名字到原理它不是真在“测水”而是在“测电阻”拆开这模块看核心就俩部分一片裸露的平行走线铜箔和一颗可调电位器。铜箔在水里泡得越深两极之间的等效电阻就越小因为自来水、雨水这类自来水并不是纯水里面溶解了大量离子是能导电的。纯水比如蒸馏水反而几乎不导电所以你把这模块插到纯净水箱里读数变化小得可怜那不是坏了是物理规律。具体电路上这片铜箔和一个固定电阻串联成了一个分压网络。当水漫过铜箔越多铜箔两端的等效电阻就越小分压输出电压也就跟着变。工程师在PCB上把LM393拿来做了一级比较器将当前分压电压与电位器预设的参考电压做比较输出就成了一个干净的数字电平DO而AO则是分压电压的直接引出能反映一个连续变化的值。这里有个很适合新手理解的类比把这模块想象成一个装有湿度感应的“水龙头旋钮”。水位越高相当于把旋钮拧到更低阻值的一档AO输出的电压值就会相应改变。你用任何一路输出都能判断水位状态区别只在精度和阈值控制逻辑上。注意别拿着传感器在开水里烫它板子上的铜箔和电路没有防高温设计长时间浸泡还会加速电极氧化。后面我会专门讲怎么用软件和供电方式延长寿命。1.2 数字量输出DO和模拟量输出AO到底该用哪个新手最容易纠结的点就是AO和DO怎么接。我的建议是先看你的场景是“定性”还是“半定量”。输出信号引脚含义适合场景DO数字输出电平高/低代表“水到/没到”水箱满溢出报警、自动补水阀门开关AO模拟输出电压随水位连续变化粗略水位指示、趋势监测、多点阈值判断DO背后其实是那颗电位器在起决定性作用。你把电位器旋到某个位置就相当于设定了“触发水位”。水漫过这个位置LM393翻转DO输出跳变。这个方式简单但注意它有一个固有毛病传感器表面有水膜、水雾甚至溅到水珠时DO可能反复抖动。AO反而是我更推荐新手去读取的信号。它本身就是一个0VCC之间的模拟电压ST的ADC读一下分分钟就能在串口里看到自己水位对应的电压值。后面我会给出换算公式以及滤波、迟滞的做法这些才是把一块廉价传感器调成可靠检测点的关键。2. 动手之前硬件接线与参数准备2.1 接口定义与接线图文字版直接照着插这模块一般引出4根线名称和功能如下VCC电源正极支持3.3V5VGND电源地AO模拟量输出DO数字量输出STM32这边我用的是STM32F103C8T6最小系统板。ADC读AO引脚建议不要直接接5V供电的AO到片上ADC因为STM32的ADC输入范围是0~3.3V超过3.3V会直接烧采样电路。保险做法是VCC接3.3V让AO最高也只能输出3.3V如果你非要用5V供电那就必须在AO到PA1之间串联两个电阻做二级分压把电压缩到2/3以内。我实测常用的接线方式传感器引脚STM32引脚说明VCC3.3V保持模拟量不超量程GNDGND共地必须接AOPA1ADC1的通道1DOPA0GPIO输入可选读取PA1是ADC1_IN1F103的多个ADC通道是可以自由映射的PA0到PA7基本上都有对应模拟功能。我这里选PA1纯粹是因为它离电源引脚远干扰相对小同时不占用调试口的SWDIO/SWCLK。如果后面要用PWM控制水泵那就把PWM放在PA0这类带TIM2_CH1功能的引脚上别跟ADC抢。2.2 参数准备供电、ADC时钟和采样配置STM32F103的ADC是12位的分辨率4096。ADC挂在APB2总线上但ADC本身的时钟频率限制是不能超过14MHz。CubeMX默认给APB2是72MHz所以ADC分频系数至少取6实际也可以取8这决定了你的采样速度和精度上限。关于采样时间ADC完成一次转换需要经历采样阶段和转换阶段。F103每个通道都有一个可配置的采样时间比如1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5个ADC时钟周期。我的建议是不要选太短的采样时间。水位传感器输出的信号本身是个缓慢变化的直流信号不追求高速所以把采样时间设到239.5周期总转换周期约12MHz下接近20微秒换来的是更稳定的内部采样保持电容充电读数跳变幅度明显小很多。我这边具体参数是外部晶振8MHzPLL倍频到72MHzADC1时钟分频为6得到12MHz采样周期239.5单通道扫描软件触发。这样配置后每组数据刷新间隔约20多微秒完全够用。代码生成阶段注意把连续转换模式打开然后软件循环读取。3. 代码实现从零读取水位数据3.1 工程怎么搭HAL库为主说说标准库的差异现在普通用户用CubeMX HAL库最省心。新建工程选择STM32F103C8Tx配置RCC时把HSE设为Crystal/Ceramic ResonatorSYS里把Debug设为Serial Wire以免把SWD引脚释放掉导致无法二次下载。ADC配置就选择ADC1开启IN1通道采样时间设置为239.5 Cycles转换模式为单次或连续都可以。另外再加一个USART1异步模式115200-8-N-1用于把数据打印到电脑上。生成工程后核心代码就在adc.c和main.c里。如果还是习惯标准库本质区别只有外设初始化写法比如“ADC_SoftwareStartConvCmd(ADC1, ENABLE)”来启动转换然后用“ADC_GetConversionValue(ADC1)”拿结果。核心计算逻辑完全一致我这里就以HAL库为例写全流程。3.2 ADC读取与电压换算核心代码先看最简单的读取函数放在main.c里uint32_t ADC_Read(void) { HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { return HAL_ADC_GetValue(hadc1); } return 0; } float ADC_GetVoltage(void) { uint32_t adc_val ADC_Read(); return (adc_val / 4095.0f) * 3.3f; }这里有几个关键点。第一我开始前调用HAL_ADC_Start读取后如果没有新的启动下一次转换下一次读到的是同一个结果。这里我是循环调用所以没问题。第二分母用的是4095而不是4096因为12位ADC满码值就是4095用4095做除法校准值更贴近实测在低电平时误差能小一点点。第三返回电压时用3.3f而不是3.300f不是因为精度差别而是避免编译器把常量当成double导致ARM在无FPU的M3上跑一段软浮点库函数速度慢很多。如果你有万用表建议实测校准一下电源电压很多ST-Link或者USB转TTL给板子供的电并不是精确的3.3V实测可能3.28或者3.35这时候你只要把3.3f替换成实测值即可精度立竿见影。3.3 让数据开口说话串口输出和PC端观察光有ADC值看单片机内部寄存器没意义得把数据送到电脑上。HAL库下用printf重定向是最快的路子但记得在工程里加上fputc实现#include stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }然后在主循环里打印printf(ADC:%d Voltage:%.3fV\r\n, ADC_Read(), ADC_GetVoltage()); HAL_Delay(200);串口助手随便挑一个波特率设置成115200就能看到数据刷刷往上冒。这里注意连接线别太长杜邦线超过15cm的话在波特率比较高时容易出现乱码或漏字符。我把波特率调到115200用了大约1米的杜邦线也稳定但为了稳妥建议不超过20cm。还有个细节GND一定不能落下STM32和你电脑的USB转串口模块必须共地否则就会看到串口助手偶尔蹦出乱码。3.4 数据处理从原始电压到稳定可靠的阈值判断直接拿ADC值做阈值判断你会发现一个很坑的现象水稍微晃动一下或者传感器边缘有一滴水珠慢慢流下来数值就会忽大忽小DO也会跟着咔哒咔哒闪。这时候要用两板斧滑动平均滤波和迟滞比较。先来滑动平均滤波我用的窗口大小是10次#define FILTER_N 10 float filter_buf[FILTER_N]; uint8_t filter_index 0; float WaterFilter(float new_value) { filter_buf[filter_index] new_value; filter_index (filter_index 1) % FILTER_N; float sum 0; for (int i 0; i FILTER_N; i) { sum filter_buf[i]; } return sum / FILTER_N; }然后阈值判断加上迟滞。迟滞这个概念说白了就是“空调26度开机降到24度再停机”用来避免临界点反复跳变。水位传感器也是一样如果你设定“低于1.2V就报警缺水”水刚过线时候噪声波动会导致报警反复。所以我用两个阈值#define WATER_LOW_V 1.0f // 低于这个值开始缺水报警 #define WATER_HIGH_V 1.4f // 高于这个值才解除缺水报警 uint8_t low_water_flag 0; void WaterJudge(float voltage) { if (voltage WATER_LOW_V) { low_water_flag 1; } else if (voltage WATER_HIGH_V) { low_water_flag 0; } }注意看这个逻辑电压下降穿过1.0V时才置位报警而水位恢复时要升到1.4V才解除报警1.0到1.4之间是一个缓冲区在这个区间内状态保持不变。这样处理之后即使水面波动导致电压在1.0到1.4之间来回跳flag也不会跟着抖。这种方式在工业现场叫“施密特触发”软件上实现几乎零成本强烈建议直接抄。4. 实测记录不同场景下的数据表现4.1 干净自来水下的测量数据先看趋势再谈精度我拿一次性水杯做了个简单测试把传感器竖着固定模拟慢慢注水。每次加水后静置3秒记录AO电压。同样的模块有的批次干燥时输出接近3.3V有的则接近0V原因我前面说了和板子出厂时电位器旋钮位置以及分压结构有关。所以我给出一组实测趋势时你要关注的是变化方向和相对差值而不是绝对数值。我手里这块在干燥空气中AO约2.9V传感器底部刚接触水面立刻跳到2.6V继续加水漫过约1/4高度后稳定在2.2V到1/2高度约2.0V几乎全部淹没时掉到1.5V左右就不再明显下降了。从这个趋势看传感器前半段灵敏度还可以后半段数值会越来越平缓属于典型的“电阻变化率越来越小”现象。这也印证了我前面说的它适合“有没有水、水大概在哪个区域”这类粗判断不适合当精密液位计使用。如果你想做精确到毫米的水位检测请直接考虑超声波传感器或者压力传感器。廉价的方案就是多个传感器在不同高度并联布置每颗只负责“自己这个点有没有淹到”这样反而比盯着模拟量猜高度更可靠。4.2 盐水、油污、气泡、纯净水会给读数带来什么影响这个环节我专门做了交叉测试结果对实际项目很有参考价值。先看盐水。我用的是食盐质量分数约5%的盐水结果传感器接触水面瞬间电压就暴跌了0.7V。这是因为盐水中离子浓度高电极间等效电阻远低于自来水分压输出自然被拉得更低。这说明同一套阈值代码换一种水质后可能误判比如原来低水位判断电压是1.0V换盐水后正常水位都可能低于这个值。解决方案只有一个每一次换水源类型都要重新标定阈值。再看油污。我在水面上倒了一层食用油结果传感器探头表面附着油膜后电压回升了约0.3V而且波动明显变大。其原因是油膜阻隔了电极和水之间的有效接触相当于把电极的一部分绝缘了接触面积变小电阻又变大了。所以这模块不要用在厨房废油液位检测上油污附着是所有接触式电极传感器的死穴。纯净水测试更有意思我拿瓶装纯净水测试发现传感器电压只下降了大概0.1V远低于自来水。这进一步证实了模块工作依赖水的导电性。如果你的应用场景是纯净水、去离子水那这模块基本废了只能靠更灵敏的电导式探针或者电容式方案。另外还有一个很容易忽略的现象气泡。传感器泡在水里一段时间又开始慢慢微气泡附着在铜箔表面我测到电压会缓慢回升0.050.1V。这个漂移不算致命但对想要精确判断临界点的项目来说是隐患。实际使用中尽量把传感器倾斜安装让气泡能自主浮走别平躺在水里等它积气。5. 常见问题排查与避坑清单5.1 典型问题速查表照着逐条查我整理了一张速查表基本覆盖了新手从接线到代码再到实测全部环节会出现的问题症状可能的坑解决办法DO一直输出高电平电位器阈值调太高或太低、传感器未接触水边浸水边调节电位器找到翻转点AO读数始终是满幅值传感器没接触水、电极断路、线路断万用表量AO对GND电压判断是模块还是接线问题AO读数不稳定乱跳电源纹波大、杜邦线过长、水有波动滤波电容100nF并联在VCC和GND之间软件加滑动滤波串口输出乱码或没输出波特率不匹配、没共地、PA9/PA10接反核对设置确认USB转串口模块GND与STM32 GND相连ADC读数为0但电压不为0ADC通道配置错、采样时间太短CubeMX重新确认Channel和引脚映射采样时间改239.5换了一种水后阈值不准电导率变化导致同水位电压偏移重新标定阈值尽量固定水源传感器长时间插入水中后读数漂移电极表面出现氧化层或污垢定期断电取出擦拭或者用软件间歇供电降低极化5.2 容易烧模块的坏习惯我全踩过这模块便宜但也不是随便造。第一个大坑就是带电拔插。很多教程直接让你把杜邦线插在最小系统板上然后传感器这一端经常因为调整位置被反复拔插结果就是在插拔瞬间VCC和GND可能发生短暂短路或者AO线碰到VCC比较器芯片LM393直接报废。正确做法是先把电源断开再插拔传感器端或者直接做成接插件固定好。第二个大坑是长时间通电加速电极腐蚀。这模块的电极就是裸露铜箔直流电压长时间加在上面会产生一个持续的电化学过程水中离子会定向迁移铜面慢慢变黑最终灵敏度下降。解决办法有两个思路一是软件上只做周期性采集比如每1秒采集一次并立即断电代码上用一个MOS管来控制传感器VCC其他时间彻底断电我实测这样寿命可以拉长一个数量级二是每次用完擦拭干净不让它泡在含盐或者偏酸性水里过夜。第三个坑是ADC输入端悬空。如果你只接了AO不接GNDADC引脚等于是浮空输入读出来的值是随机的而且容易把内部采样电容充到奇怪状态。整个模块四根线只要用到AO就必须把GND、VCC连好别省线。第四个坑是供电不够纯净。我从USB口取电时如果同时驱动继电器或者舵机这类大电流负载ADC读数会周期性跳动。处理办法是传感器供电和负载供电分开或者在电源入口并联一颗100uF电解电容再加一颗100nF陶瓷电容。实测这个组合能把电压跌落从0.15V压到0.03V以内读数稳定很多。5.3 量程、防水和保护使用前先想清楚的边界这块传感器的防水能力是存在误区的。PCB板面并没有做全封闭防水处理电极区域泡水没问题但背面电路元件LM393、电位器一旦被水浸没轻则读数异常重则短路烧毁。所以安装时要求传感器倾斜或垂直放置让电路部分始终保持在水面以上电极部分泡在水里。我见过有人整块传感器横躺在水槽底部第二天模块就废了就是这个原因。另一个容易被忽略的是传感器电极之间不能被导电线缆、金属支架这类物体搭桥。如果你把它装在金属水箱里传感器背面的焊点和固定支架一旦碰到水箱壁整个分压电路会被金属壁短路读数直接变成固定值或者跳满幅。正确做法是安装时用尼龙柱或塑料支架固定确保电气上完全隔离。5.4 软件上还能怎么优化让它更好用先说电平触发逻辑。你如果只关心“水箱水满了要停泵”那代码里应该优先判断“从没满到满”的边沿而不是一直检测当前状态。比如只有水位从低变高那一刻触发关泵动作防止水位浮漂抖动导致水泵频繁启停。这个思路代价极低但对设备的保护效果非常大。再说多传感器复用。一块传感器是“有没有水”三块传感器就可以拼出一个“低、中、高”三档逻辑。每颗传感器固定在不同高度读取对应GPIO电平接DO或者ADC电压接AO代码里做一个简单的优先级判断。做自动浇花或者鱼缸自动补水时这种方案远比一颗传感器测连续电压更稳定因为不同高度互不干扰也不用担心模拟量漂移和标定误差。最后提一下我习惯让ST的ADC同时采用两个通道另一个通道接一个独立电位器做“环境校准参考”。因为水温、水质变化会导致基准偏移如果传感器AO和参考通道同时变化程序里就可以算出相对值而不是绝对值阈值漂移能挽回不少。这个属于进阶技巧但对长期运行的项目确实是加分项。根据我个人经验这块Water Sensor水位传感器最大的价值不在于“精准测水位”而在于帮你快速把一个真实世界的水状态变成STM32能理解的电压值从而把整个嵌入式系统跑通。它廉价、易用正因为它不够完美才能逼着你去思考阈值、滤波、迟滞、供电和电极保护这些真正的工程细节。等你把这一整套流程跑顺了再去换更高精度的传感器基本就是换一个数据源的事电气架构和代码逻辑都可以平迁。后面如果想深化可以加上OLED屏幕显示实时水位、配合继电器驱动水泵或者用ESP8266把采集结果上报到家里局域网都是很好的扩展方向。
返回列表