STM32水质检测系统开发:从ADC采集到滤波标定的完整实战

发布时间:2026/8/31 3:19:39
STM32水质检测系统开发:从ADC采集到滤波标定的完整实战 简介本资源是一套基于STM32F103系列单片机开发的C语言水质监测系统完整工程面向嵌入式初学者与课程设计、毕业设计及物联网实践项目开发者解决水体PH值、TDS总溶解固体及温度三项核心参数的实时采集、本地显示与远程上传需求。压缩包含280个文件总计11.12MB其中C源文件46个与头文件72个构成主控逻辑Keil工程配置文件uvprojx、uvoptx、编译中间文件o、d、crf及可执行镜像axf、hex齐全配套PDF说明文档与调试配置dbgconf便于快速部署。已有2038人学习下载提供从传感器驱动ADC、I2C、数据滤波处理、LCD本地显示到OneNet平台HTTP/MQTT协议对接的全链路实现代码结构清晰、模块划分合理适合作为嵌入式C语言综合实训范例深入理解硬件驱动、通信协议与低功耗传感应用。 做水质检测系统这个项目算是我在嵌入式开发这条路上真正脱了一层皮又长了一层肉的经历。当时手里只有一块STM32F103C8T6核心板、几个传感器模块和一个模糊的需求硬是靠着查手册、看源码、反复试错把一套能实时显示、能标定、能报警的水质检测系统给磕了出来。今天不聊虚的直接把整个项目的设计思路、硬件选型、C语言代码架构、标定过程和踩坑记录全部摊开来讲给准备做单片机项目或者正在纠结怎么把传感器信号变成稳定数据的你一个完整参考。先说结论这套基于STM32的水质检测系统核心是三条模拟量采集链路加一条数字温度链路通过ADC多通道扫描加DMA搬运配合滑动均值滤波和温度补偿算法最终在OLED上实时刷新TDS溶解性总固体、pH值和温度三个指标同时支持按键设置阈值超限就触发蜂鸣器报警。整个工程用标准库开发代码分层清晰移植性很强后续想加蓝牙、Wi-Fi模块往云端传数据也就是多写几个外设驱动的事。1. 项目背景与整体设计思路1.1 为什么选STM32做水质检测水质检测听起来像是一门化学课或者环境工程课的课题但落到电子设计上本质就是一个多通道数据采集系统。你需要同时读取好几个传感器的电压信号做信号调理然后通过算法把这些电压换算成人能看懂的水质参数。这种活儿单片机天然比PC更适合因为传感器接口、ADC采样、实时控制、低功耗这些需求单片机的片内外设刚好全都能覆盖。在单片机选型上我最后敲定STM32F103C8T6理由很实在第一性能足够。72MHz主频12位ADC三个ADC外设可以同时采集多路信号处理几个传感器的数据绰绰有余。第二生态成熟。标准外设库的资料满天飞寄存器手册、参考代码、论坛讨论遇到问题基本都能搜到解决方案对新手极其友好。第三成本低。核心板几十块钱坏了不心疼适合反复折腾。相比用51单片机STM32的优势在于ADC精度和采样效率。51单片机虽然也能做AD转换但通常是8位精度而且需要外扩ADC芯片电路复杂度和代码复杂度都会上升。STM32内置的12位ADC虽然不能和专用ADC芯片比但对于TDS、pH这类输出缓慢变化的直流信号来说精度完全够用。1.2 系统架构和硬件组成整套系统的信号链路是这样的各类水质传感器将水质参数转换为电信号经过信号调理电路处理后送入STM32内置ADC转换主控MCU对多通道数据进行采集、滤波、补偿和换算最终将结果显示在OLED屏幕上并根据设置的阈值控制报警模块。硬件清单如下主控STM32F103C8T6核心板板载8MHz晶振通过PLL倍频到72MHzTDS传感器探针式TDS模块输出模拟电压带温度补偿输入接口pH传感器pH电极探头配信号变送板输出模拟电压温度传感器DS18B20单总线数字传感器用于温度补偿显示0.96寸I2C接口OLED显示屏SSD1306驱动交互三个独立按键菜单切换、数值加减、确认报警有源蜂鸣器电源5V USB供电板载AMS1117稳压到3.3V这套系统中TDS传感器和pH传感器输出的都是模拟电压信号所以必须占用ADC通道。温度用DS18B20不走ADC是用GPIO模拟单总线时序读取数字值。在实际接线中我特别注重TDS传感器和pH传感器的信号线走线尽量避免布在电机、继电器等大功率器件附近因为这类器件启停瞬间会产生很强的电磁干扰模拟信号线如果离得太近采集到的电压信号会夹杂大量毛刺后期软件滤波难度倍增。1.3 开发环境搭建开发环境用Keil MDK5搭配ST-Link V2下载器。工程基于STM32标准外设库V3.5代码分层为驱动层和应用层。我建议新人在建工程时不要把文件堆在一起而是按功能模块划分文件夹比如BSP目录存放LCD、DS18B20、ADC等板级外设驱动APP目录存放主流程逻辑、菜单逻辑、数据处理逻辑Core目录存放启动文件、系统时钟配置等共享一份工程模板的价值在于你做不同项目时可以直接复用大部分驱动代码只需要改应用层逻辑就行。这个工程我后来整整用了快两年每次做新东西都从这套骨架开始改省了大量时间。2. 硬件选型与电路设计要点2.1 TDS传感器电导率测量原理与选型TDSTotal Dissolved Solids溶解性总固体传感器测量的是水中溶解性固体的总含量单位是ppmmg/L。它的工作原理并不复杂水中的离子浓度越高导电性越强通过测量水的电导率再乘以一个系数就能换算出TDS值。市面上常见的TDS模块有两种输出方式一种是直接输出模拟电压模块内部已经做了信号调理另一种是厂商自带算法数字接口输出。我选的是模拟电压输出的模块因为它给开发者最大的算法定制空间你可以自己写滤波、自己写温度补偿而不是被动接受模块内置的处理逻辑。模块输出范围大致是0到2.3V对应0到1000ppm负载电阻和探头常数都已经在模块上校准过直接接ADC就行。这里要特别说一个很多人忽略的坑TDS电极极化。测量电导率时如果电极两端一直加直流电压溶液中的离子会在电极表面富集形成极化层导致测量值随时间漂移。解决办法是让测量工作在交流激励下进行。大部分成品TDS模块内部已经加了交流激励源所以不用太担心。但如果你自己用裸电极搭电路一定要设计一个方波发生器去驱动探头不能直接加直流电平。2.2 pH传感器高阻抗信号处理pH传感器和TDS传感器完全是两种风格。pH电极是一个高阻抗器件内阻可达几十兆欧甚至上百兆欧所以它的信号调理电路必须要有高输入阻抗的运算放大器做缓冲否则信号会被ADC的采样阻抗直接吃掉测出来的电压完全不准。我用的pH模块已经在板子上集成了一级高阻抗缓冲放大输出阻抗很低可以直接接ADC。但pH模块的精度受温度影响很大因为能斯特方程里的斜率项和温度有关。所以我要同时采集DS18B20的温度数据然后在软件里做温度补偿。选型方面普通实验室用的pH电极分玻璃电极和复合电极。玻璃电极稳定性高但响应慢且不能用在含氟化物的水样里。复合电极把测量电极和参比电极做在一起使用方便适合自动化设备。我这个项目用的是通用复合电极成本低能满足教学演示和普通水质评估的需求。2.3 电源与信号调理电路整机用5V供电STM32核心板板载3.3V稳压给MCU和传感器模块用。这里有个非常重要的细节TDS模块和pH模块的参考地必须和MCU共地否则信号电平没法统一。有些模块有独立的电源引脚如果供电电压不一致还要考虑电平匹配问题。我测试时发现如果TDS模块用5V供电而MCU的ADC参考电压是3.3V模块输出0到2.3V虽然不会超过3.3V但如果模块工作在5V下输出满量程可能接近4V此时直接接3.3V的ADC引脚就有烧毁风险必须用电阻分压或运放电平转换。保险起见我给所有传感器模块统一用3.3V供电虽然部分模块手册标注5V供电但实际上3.3V也能正常工作只是满量程输出略有降低通过软件标定就能消除。ADC的参考电压AVDD我认为也是值得重视的环节。STM32F103的ADC参考电压接VREF引脚核心板上一般已经接到3.3V。如果追求更高精度可以给VREF单独接一路基准电压源比如REF3030精度能稳定到0.2%以内。不过对于TDS这种本身就有10%误差的传感器外部参考源带来的提升有限我没上直接用板载3.3V。3. 软件架构与C语言实现核心3.1 代码分层模板工程搭建思维这个项目的代码文件划分核心的思想就是让每一层只干一件事绝不跨界。硬件相关的寄存器操作全部封装在BSP层数据滤波和参数换算是应用层主循环只调度逻辑任务。工程结构 - Core/ - main.c 主函数任务调度 - stm32f10x_it.c 中断服务函数 - BSP/ - bsp_adc.c ADC初始化、DMA采集 - bsp_ds18b20.c DS18B20驱动 - bsp_oled.c OLED驱动 - bsp_key.c 按键驱动 - bsp_beep.c 蜂鸣器驱动 - APP/ - app_task.c 应用逻辑主循环 - app_filter.c 滤波算法滑动均值 - app_calc.c 物理量换算TDS/pH补偿计算 - app_menu.c 菜单界面逻辑这样的结构后期想换个显示屏、换个传感器只需要修改对应的BSP文件应用层代码完全不用动。我在做一个项目的时候可以从之前调好的工程里直接拷贝BSP层代码整体迁移成本极低。3.2 ADC多通道采样与DMA搬运ADC部分是本项目的核心之一。TDS和pH两个通道要同时采集我用ADC1的通道0和通道1配置为扫描模式加多通道采样采样结果通过DMA自动搬运到内存数组里。DMA的意义在于ADC转换完成后数据不需要CPU干预就能存到指定的变量里CPU可以腾出手去刷新OLED、处理按键逻辑。// ADC初始化配置 void BSP_ADC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // PA0(TDS) PA1(pH) 配置为模拟输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); // ADC1 配置 ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 扫描模式 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 连续转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 2; // 2个通道 ADC_Init(ADC1, ADC_InitStructure); // 通道转换顺序和采样时间 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); // DMA1 通道1 配置 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 2; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, DMA_InitStructure); ADC_DMACmd(ADC1, ENABLE); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }采样时间选55.5个周期是折算之后单个通道大约20kHz的有效采样率对水质传感器这种低频信号来说绰绰有余但又能有效抑制高频干扰。采集到的数据放在全局数组adc_buf里主循环直接读这个数组就行了。3.3 滑动均值滤波拒绝毛刺的实用方案实际测量中ADC采到的原始值跳动非常大TDS输出的模拟电压在探头附近有气泡或者水流动不稳定的情况下跳变幅度能到几十毫伏换算成TDS就是几十甚至上百ppm的波动。面对这种问题我选用的方案是滑动均值滤波。我在三个硬性条件之间做了折中一是要能有效平滑短期波动二是要保留一定的响应速度三是代码要简单不能占用太多内存和CPU。滑动均值滤波的窗口取20次采样值每来一个新数据就丢弃最老的一个数据然后计算平均值实质是一个低通滤波器。#define FILTER_N 20 static uint16_t filter_buf[FILTER_N]; static uint8_t filter_index 0; static uint8_t filter_count 0; uint32_t SlidingMeanFilter(uint16_t new_value) { uint32_t sum 0; uint8_t i 0; filter_buf[filter_index] new_value; filter_index (filter_index 1) % FILTER_N; if (filter_count FILTER_N) filter_count; for (i 0; i filter_count; i) sum filter_buf[i]; return sum / filter_count; }这段代码有好几个细节值得琢磨。filter_index用模运算做环形缓冲不用移动数据时间复杂度固定是O(N)主循环里调用它不会产生不确定的延迟。filter_count的存在是为了解决开机初期的“空窗期”问题如果只积累了5个数据就直接除以5而不是除以20否则平均值会被前几个零拉低必须等20个数据填满才进入稳定状态。工程里我也遇到过一个比较棘手的情况偶然性的尖峰脉冲干扰比如电机启动瞬间的浪涌耦合到传感器线路上滑动均值虽然能减轻影响但一个足够大的尖峰依然能在窗口内残留一段时间。为了处理这类情况我在滑动均值之前加了一道中值滤波每次连续采样5次取中间值作为一次有效输入。两个滤波算法串联之后曲线平滑度非常理想实测TDS读数稳定后波动量在±5ppm以内。3.4 物理量换算与温度补偿标定计算ADC采集出来的是数字量要把它变成真实的物理量必须走完从数字到电压、从电压到电导率、从电导率到TDS这样一条换算链。这一部分是项目的灵魂也是很多人容易出错的地方。ADC数字量转电压的公式非常简单float voltage (float)adc_value * 3.3f / 4096.0f;12位ADC的分辨率是4096级参考电压3.3V所以每个LSB对应的电压约为0.806mV。TDS传感器模块的输出电压和电导率存在线性关系但比例系数受探头常数和模块增益的影响。我不能直接拿厂商给的默认系数就用因为不同的水样、不同的探头安装位置实际测量值都会偏离。正确做法是配置标准溶液进行单点标定。标定过程是这样的准备一杯TDS已知的标准液比如500ppm把探头放进去稳定2分钟读取模块输出电压然后反推系数// 电压 - TDS (ppm) tds_value voltage * tds_coefficient;其中tds_coefficient初始给一个经验值1500然后通过标准液校准调整。更准确的做法是两点标定用0ppm纯水和500ppm标准液分别测输出电压线性拟合出斜率和截距但实际工程中由于pH传感器和TDS传感器的电压都在零点附近截距项影响不大单点标定足够用。温度补偿是另一个重头戏。溶液的导电率和温度强相关温度升高1℃电导率大约增加2%左右。如果不在软件里做补偿夏天和冬天测同一杯水TDS读数能差出10%以上。标准做法是把测量值折算到25℃基准下的等效值// 温度补偿到25℃ float temp_factor 1.0f 0.02f * (current_temp - 25.0f); tds_compensated tds_raw / temp_factor;pH传感器的温度补偿逻辑稍有不同。pH电极的输出电压随温度变化满足能斯特方程每pH单位在25℃下对应约59.16mV但实际斜率会随温度漂移所以要对斜率项做修正// pH 7 (7.0 - voltage_mid) / (slope * temp_correction) float slope_25c 59.16f; // mV/pH float temp_correction (273.15f current_temp) / (273.15f 25.0f); float slope slope_25c * temp_correction; ph_value 7.0f (voltage_mid - voltage) / slope;这里voltage_mid是pH值为7.0时电极在中性缓冲液中的输出电压一般在标定时测量得到。pH电极的输出电压和pH成线性反比关系pH越高输出电压越低。3.5 按键菜单与显示逻辑人机交互方面我用OLED显示主界面和菜单界面三个按键分别承担“菜单/确认”“加”“减”的功能。菜单逻辑的本质是一个状态机状态切换的驱动源是按键事件。typedef enum { DISPLAY_MAIN, // 主界面 MENU_TDS_THRESHOLD, // TDS阈值设置 MENU_PH_THRESHOLD // pH阈值设置 } MENU_STATE; MENU_STATE current_menu_state DISPLAY_MAIN;主循环里轮询按键一旦检测到短按KEY1就切换状态。在阈值设置状态下KEY2和KEY3调整阈值调整结果实时写回全局变量并且存入内部Flash掉电不丢失。用户不想每次开机重新设置阈值。OLED用的是SSD1306驱动芯片I2C接口。驱动函数的实现并不复杂核心是把显存数组通过I2C写入SSD1306的GRAM。注意SSD1306内部有一块128x64的显存每个像素点对应一个bit点亮就写1。我用的OLED一屏能显示4行16列标准ASCII字符所以在主界面同时显示TDS、pH、温度三个参数绰绰有余每行一个参数用sprintf格式化后再显示。3.6 蜂鸣器报警逻辑报警逻辑很简单当TDS值超过阈值或者pH值超出设定范围时GPIO输出高电平驱动有源蜂鸣器发声。但真正让我反复调整的是报警的“迟滞”问题。如果阈值设为500ppm实测值恰好是499ppm那么蜂鸣器会处于一种临界状态数据轻微跳动就会导致报警状态反复翻转不仅烦人还容易损坏蜂鸣器。解决方法是加入迟滞比较实测值要超过设定的上限加上迟滞带才触发报警低于上限减去迟滞带才解除报警。迟滞带取阈值的5%既能抑制抖动又不至于让报警变得迟钝。这是工业控制里比较常用的一种思路。4. 实操过程与核心环节实现4.1 从零开始搭建工程的过程记录我这里记录一下从拿到核心板到跑通显示大约需要经过哪些步骤。新手可能觉得这部分太基础但它决定了后续所有功能调试的底子所以我建议按顺序完整操作一遍。第一步安装Keil MDK5并且安装好STM32F1系列的器件支持包。第二步新建工程选择芯片型号STM32F103C8T6。第三步把标准外设库的源码文件复制到工程目录下并且把全局宏STM32F10X_MD、USE_STDPERIPH_DRIVER加上。第四步配置时钟。我这里直接用库函数SystemInit它在启动文件里会被自动调用把时钟从8MHz外部晶振倍频到72MHz。第五步写一个简单的GPIO翻转程序下载到板子里用LED验证最小系统正常。这五步走完才算具备了点灯之外所有功能开发的基础。我不建议直接跳到最后一步写完整程序因为如果程序跑不起来你很难判断是硬件问题、驱动问题还是逻辑问题。从最小的功能模块一个一个迭代每个模块都是可测的问题定位速度会快很多。4.2 传感器标定的完整步骤传感器标定是这个项目里最耗时也最关键的一环我在这上面踩过的坑比写代码踩过的还多。以TDS传感器为例整个标定过程是这样的准备标准液。这一步很多人图省事直接拿自来水标但自来水的TDS值本身就不稳定标出来的系数没有参考价值。我买了TDS标准缓冲液粉剂按说明配成500ppm的溶液。配制时要注意粉剂要用天平称量准确水尽量用去离子水或蒸馏水不能用自来水不然配出来的浓度根本不对。将探头放入标准液中深度至少要没过探头的两个电极等待2分钟让读数稳定。这段时间如果发现读数有缓慢漂移多半是电极表面有油污或气泡。处理方法是先用去离子水冲洗探头再轻轻甩干不可用纸巾擦因为有可能会损伤电极表面镀层。记录模块输出电压同时用DS18B20测标准液温度。根据温度补偿公式计算系数写入程序中的tds_coefficient变量。这样完成之后再测量另一杯未知水样和商用TDS笔做对比误差在5%以内就说明标定成功。如果偏差很大优先检查标准液温度补偿是否做对以及探头是否完全浸没。pH传感器的标定比TDS复杂常规做法是两点标定。先测pH6.86缓冲液记录电压再测pH4.01缓冲液记录电压。两个点的电压差除以pH差得到实际斜率。如果实际斜率明显偏离理论值59.16mV/pH说明电极老化或缓冲液失效。标定结束后把斜率和零点电压保存在程序里这就是整个项目里最重要的两个校正参数。4.3 实测数据记录与误差分析在25℃室温环境下我用这套系统对几组水样做了实测误差基本在预期范围内。这里放其中一组数据水样商用TDS笔读数(ppm)本系统读数(ppm)误差纯净水451自来水2052112.9%标准液500498-0.4%盐水963940-2.4%纯净水的绝对误差虽然只有1ppm但相对误差高达25%这是因为在极低浓度下探头本身的残余电导率变成了主要影响因素。实际使用中如果测的是纯净水的TDS读数为个位数到十几ppm这个精度其实是够用的但在报告里应该注明低浓度段的相对误差会明显增大。pH实测方面我用标准缓冲液6.86和9.18分别验证误差都在0.1pH以内。pH传感器的漂移问题在开机前15分钟特别明显电极电位的建立过程比较漫长。所以我的使用习惯是系统上电后先等5到10分钟等传感器读数稳定了再记录对于需要精确测量的场景这个预热流程必须遵守。4.4 常见问题与排查技巧实录ADC采样值一直满量程或一直为零。先量传感器模块输出电压用手摸探头看电压有没有变化。如果模块输出正常问题在STM32的ADC配置上重点检查ADC通道号是否对应、DMA搬运地址是否正确。如果模块输出异常优先检查供电和共地。TDS读数漂移严重一直在几百ppm范围内大幅跳动。可能原因有三个探头没完全浸入液面、探头顶部接线处进水短路、水样里有气泡。先用肉眼观察排除可见问题再用排除法把探头从液体中取出测空气值如果空气值稳定说明探头本身没问题问题在水样或者屏蔽干扰。OLED显示乱码或白屏。先检查接线顺序很多OLED模块的I2C引脚标的是SCL、SDA但有的板子会加电平转换电路导致引脚定义和丝印不一致。其次检查I2C地址SSD1306有0x3C和0x3D两种地址取决于模块的地址选择电阻。最后检查时序把I2C速率从400K降到100K试试有些质量一般的杜邦线在400K下波形畸变严重。DS18B20读不到温度返回85或者-55度。85度是DS18B20上电复位时的寄存器默认值说明MCU和DS18B20之间通信没成功。优先检查上拉电阻DS18B20的数据线必须有4.7K欧上拉到VCC很多新手直接拉一根杜邦线接MCU没有上拉电阻通信时好时坏。再检查时序单总线的时序非常苛刻拉低时间、释放时间差几十微秒就会失败最好用逻辑分析仪对齐时序波形。4.5 关键代码逻辑解析主函数的整体调度逻辑是每100ms执行一次数据采集和界面刷新每500ms执行一次按键扫描和报警判断。这个时间分配是经过测试的OLED刷新太快会闪烁太慢又显得卡顿。数据采集放最前面因为滤波算法需要样本连续中间被其他任务打断也没关系只要每个周期都有新数据进来就行。int main(void) { SystemInit(); BSP_ADC_Init(); BSP_DS18B20_Init(); BSP_OLED_Init(); BSP_Key_Init(); BSP_Beep_Init(); while (1) { // 1. 采集原始值 adc_tds_raw adc_buf[0]; adc_ph_raw adc_buf[1]; temperature DS18B20_GetTemp(); // 2. 滤波 tds_filtered SlidingMeanFilter(adc_tds_raw); // 3. 物理量换算 tds_value TDS_Calc(tds_filtered, temperature); ph_value PH_Calc(adc_ph_raw, temperature); // 4. 界面刷新 APP_Display(tds_value, ph_value, temperature); // 5. 按键与报警 APP_KeyScan(); APP_AlarmCheck(tds_value, ph_value); // 调度延时 delay_ms(100); } }这段代码的时间分配策略是固定节拍扫一遍所有任务这是一种最原始的操作系统节拍思想。如果你的项目要扩展更多功能比如传感器的数量、通信协议栈、复杂按键逻辑这种裸机轮询方式会越来越吃力到时候就需要考虑用定时器做任务调度或者直接上RTOS。5. 实际使用场景与扩展方向5.1 系统适合的应用场景这套水质检测系统做成之后我主要用在三个场景。第一家用饮水安全评估定期测一下自来水、净水器出水、桶装水的TDS和pH值形成一个简单的趋势记录。第二课堂教学演示把单片机项目和数据采集结合起来学生能看到传感器信号从模拟到数字到用户界面的完整链路比只讲书本理论直观得多。第三室内鱼缸水质监控鱼缸里水的pH值非常关键但变化是缓慢的人不可能24小时盯着有了自动报警功能水质的突然恶化就能及早发现。如果需要做成一个产品级的设备还应该考虑防水外壳、电极的定期维护提醒、数据存储能力。这个系统目前只能算是一个功能原型从原型到产品之间还隔着可靠性验证、长时间稳定性测试、外观散热设计这些坎。但作为毕业设计、电子设计竞赛或者DIY项目它已经完全够用了。5.2 后续功能扩展我要说这个项目做完之后其实只发挥了STM32的很小一部分能力。后续扩展可以往三个方向走一是加数据通信模块。比如把ESP8266接在STM32的串口2上编一个简单协议把采集到的水质数据上传到云平台手机上随时查看。实现难度不高但价值感提升非常明显真正实现了远程监控。二是加存储功能。在TDS和pH传感器上各接一个模拟开关外加一个24C02的I2C EEPROM就可以定时把数据存下来。或者更高端一点的方案用SPI接口的SD卡模块把带时间戳的历史数据存成CSV文件导出来用Excel分析趋势效果非常好。三是加自动给水控制。比如鱼缸场景pH值低于阈值就控制水泵加入缓冲液TDS过高就自动换水。这时候需要增加一个继电器模块通过GPIO控制外部设备同时要对加液泵的启停做防抖处理避免频繁启停造成设备损坏。5.3 我对这套系统的几点体会写了这么多最后分享一点个人的实际体会。在开发过程中最容易出问题的部分不是算法也不是代码语法而是传感器输出的噪声和波动。软件滤波看似简单但窗口长度的选择直接影响系统的响应速度和稳定性我实际调的时候每次改动都要在真实水样场景下跑够半小时才能确认效果这是代码质量之外最难量化的一步。还有一个让我印象深刻的问题是硬件连接松动导致ADC读数间歇性跳变。一开始我以为是算法问题花了很多时间反复调滤波参数最后发现是杜邦线接触不良。做这种模拟信号采集项目建议所有传感器线都用焊接方式固定杜邦线只适合做原型验证不适合长时间运行。如果实在要用杜邦线也要用质量好的并且在测试前把所有接口插紧一遍。项目里最值得回味的不是最后测出的漂亮数据而是中间不断犯错、定位、修正的过程。这种从混乱中逐渐建立起确定性的体验恐怕才是做嵌入式项目最大的收获。如果你正在做类似的水质检测系统或者准备用STM32做传感器采集项目上面这些经验应该能帮你少走几周弯路。本文还有配套的精品资源点击获取