
STM32项目开源水质检测系统代码、原理图、仿真一步到位。前阵子做了个小项目用STM32搭了一套水质检测系统测温度、pH值、TDS外加OLED显示和阈值告警。从画原理图、写驱动到Proteus仿真联调整套资料都整理出来了包括源代码、原理图PDF和仿真工程。今天花点时间把这套东西从头到尾拆开讲一遍。这套项目用的主控是STM32F103C8T6成本低、资料多、外设够用很适合作为入门级的综合实验项目。传感器部分用了三个DS18B20数字温度传感器、pH值模拟量模块、TDS水质检测模块。显示用0.96寸I2C接口OLED告警用有源蜂鸣器加LED数据同时通过串口输出到上位机或虚拟终端。仿真工程在Proteus 8里可以直接跑不需要实物也能完整看到整个系统的工作流程。我把项目分成硬件设计、软件代码、仿真搭建、调试排错四块来讲。不论你是刚学STM32的学生还是想快速做一套环境监测原型机的工程师这份开源资料都可以直接抄作业改一改传感器类型就能用在自己的项目里。1. 整体设计思路与方案选型拆解1.1 为什么选STM32F103C8T6做水质检测主控水质检测这个场景对主控的要求其实不高需要几路ADC采集模拟量、一组I2C或SPI驱动屏幕、一个单总线接口读温度、一对串口做数据输出、若干GPIO控制蜂鸣器和指示灯。这些需求随便一个8位单片机也能凑合跑但实际做下来STM32的优势还是显而易见的。首先是外设资源充足。STM32F103C8T6内置12位ADC通道数量够用而且支持DMA和扫描模式采集多路传感器数据时不必占用CPU。I2C、USART、定时器这些常用外设一应俱全接OLED、接蓝牙、接ESP8266都非常方便后面对项目做功能扩展的空间很大。其次是开发资料极其丰富。标准外设库、HAL库、LL库加上网上数量庞大的例程遇到问题搜一下就有答案。对初学者来说踩坑之后的排查成本低这是非常现实的优势。Keil MDK环境下工程配置也比较顺手下载调试用ST-Link或者串口ISP都能搞定。第三是价格和供货稳定。C8T6这颗芯片的用量极大市面上各种核心板、最小系统板价格很低做原型验证非常划算。即使项目后期要量产换用同系列的国产兼容芯片也很方便不需要改代码逻辑。1.2 系统功能划分与数据流向这套水质检测系统的整体架构可以看作一条单向链路传感器采集物理量主控做数据转换和逻辑判断输出设备呈现结果。传感器端DS18B20负责水温pH模块负责氢离子浓度对应的电压信号TDS模块负责溶解性固体总量对应的电压信号。主控端STM32读取三个传感器的数据通过算法换算成实际物理值同时与预设阈值比较决定是否触发告警。输出端OLED实时刷新显示温度、pH、TDS三项数据串口持续输出结构化的日志信息蜂鸣器和LED在超标时动作。这里有个容易被忽视的设计点传感器数据的采集周期不需要太高频。水质参数变化是缓慢过程1秒刷新一次完全足够。所以我在代码里用了定时器中断做1秒时基在中断回调里置标志位主循环检测到标志位后再去采集和刷新这样可以降低系统功耗也避免显示层频繁刷新造成闪烁。1.3 传感器选型的几个关键点传感器选型是整个项目里最影响实际效果的部分。最初我考虑过用单一的多参数水质探头那种探头精度高、响应快但价格高而且信号调理电路相对复杂不适合开源项目推广。后来选了分立的三个模块成本低、接线简单也更利于大家理解每一种传感器背后的原理。DS18B18B20用单总线协议引脚少只需要一个GPIO口和一个4.7kΩ上拉电阻。测温范围-55℃到125℃12位分辨率下精度0.5℃测水温绰绰有余。pH模块选的是工业级pH电极加信号调理板的组合。电极输出的是毫伏级差分信号调理板将其放大并转换成0到3V的模拟电压输出STM32用ADC读取即可。需要注意的是pH探头需要校准至少要两点校准后面软件部分会详细讲。TDS模块本质上是一个电导率测量电路输出的电压值与水中溶解性固体的浓度相关。探头是两根不锈钢电极通过施加交流激励避免电解极化调理板输出0到2.3V左右的电压对应0到1000ppm。它的输出虽然和pH一样是模拟量但物理机理完全不同不能混用。温度补偿是水质检测里绕不开的话题。pH值和电导率都受温度影响所以我在代码里把DS18B20测到的温度同时用于pH和TDS的换算补偿后面章节会展开讲公式。2. 硬件原理图分析与设计要点2.1 主控最小系统的电路设计主控部分沿用STM32F103C8T6的最小系统电路包括电源、复位、时钟、启动模式选择和去耦电容几部分。原理图里我给了完整标注这里挑几个关键点说一下。电源电路采用USB 5V输入经过AMS1117-3.3稳压得到3.3V系统电压。输入侧加一个100uF电解电容和一个0.1uF陶瓷电容输出侧至少加两个0.1uF去耦电容并在靠近每个电源引脚的位置就近放置。实际画PCB时如果用的是核心板而不是自绘芯片封装这部分可以简化但若是自己画板电源布局的好坏直接影响ADC采样的稳定度。复位电路用10kΩ上拉电阻加0.1uF电容到地NRST引脚接一个按键方便手动复位。时钟部分采用8MHz无源晶振两个22pF负载电容接在晶振两端到地。启动模式BOOT0和BOOT1都通过10kΩ电阻下拉到地保证从Flash启动避免上电后卡进系统存储器。还有一组值得注意的引脚VBAT接3.3V用于给内部RTC和备份寄存器供电VDDA引脚必须单独接3.3V并加滤波电容否则ADC的参考电压不稳采集数值跳得很厉害。这是很多新手画板容易忽略的地方。2.2 pH传感器接口电路设计pH模块输出的模拟信号是电压形式我把它接到了STM32的PA1引脚作为ADC1的通道1输入。硬件上要做的事情并不多因为调理板已经完成了信号放大和阻抗变换。但在原理图设计时有几条经验值得记下来pH模块和主控板必须可靠共地。pH电极的测量本质上是测量电极对之间的电位差调理板的参考地如果不干净测量值会整体漂移。信号线尽量短且在原理图上标注清楚走线要求避免与电源线、继电器驱动线平行走线。如果PCB空间允许在ADC引脚前加一个RC低通滤波器典型值100Ω加0.1uF截止频率大约16kHz能有效抑制高频干扰。pH模块的供电我用5V而不是3.3V。部分pH调理板的运放输出摆幅受供电电压限制5V供电才能保证输出范围覆盖到0到3V左右如果3.3V供电可能导致高pH区域削波失真。pH探头是玻璃电极阻抗很高不能用普通杜邦线延长太多。如果项目里需要做长距离测量信号调理板尽量靠近探头输出端再接长线。2.3 TDS传感器与ADC通道分配TDS模块的输出同样接入了另一路ADC我用的是PA2引脚对应ADC1的通道2。TDS模块的输出电压和TDS浓度近似线性但不同厂家的模块输出范围略有差异所以在原理图里留了一个多圈电位器做增益调整实物调试时先把探头放在已知浓度的标准液中再调节电位器让ADC读数落在合理区间。Proteus仿真里这个电位器同样存在直接用它模拟不同水质下的传感器输出。ADC参考电压这里我特别强调一下STM32F103C8T6的ADC以VDDA作为参考电压VDDA接了3.3V所以12位ADC满量程对应的电压就是3.3V。也就是说ADC读到的数字值除以4096再乘以3.3就得到了实际的电压值。这个换算关系在代码里反复用到。2.4 OLED显示、蜂鸣器与指示灯电路OLED我用的是0.96寸SSD1306驱动芯片的I2C版本SCL接PB6SDA接PB7也就是I2C1的默认引脚。I2C总线上要加4.7kΩ上拉电阻到3.3V这是I2C协议的要求。可能有人会问STM32F103内部带了上拉为什么外部还要加原因是芯片内部上拉阻值比较大对于快速模式400kHz的I2C通信来说上拉不够强会导致信号上升沿变缓在数据多、频率高的时候出现花屏或者卡死。所以外部上拉电阻是标准做法硬件上不要省。蜂鸣器用的是5V有源蜂鸣器通过一个NPN三极管S8050驱动。驱动电路是PA5经过1kΩ电阻连接到三极管基极三极管集电极接蜂鸣器负极蜂鸣器正极接5V发射极接地同时在蜂鸣器两端反向并联一个1N4148二极管做续流保护。PD5输出高电平时三极管导通蜂鸣器发声低电平时关闭。指示灯同理LED正极接3.3V负极经限流电阻接PA6低电平点亮。用三极管驱动而不是直接用GPIO推蜂鸣器是因为有源蜂鸣器的工作电流可能需要20到30mA虽然STM32的GPIO理论上能输出这么多但长期使用会对芯片造成负担一不小心还可能烧IO口。加上三极管后GPIO只提供毫安级的基极电流可靠性高得多。2.5 通信接口预留与电平转换为了方便调试和后续扩展我在原理图上预留了USART1接口PA9为TXPA10为RX通过一个4针排针引出。调试时接USB转TTL模块就能在电脑上看到串口日志。如果是实物场景想加无线传输这个串口可以直接接ESP8266或HC-05蓝牙模块无需改板。要注意的是STM32的USART电平是3.3V TTL而部分USB转TTL模块或者蓝牙模块是5V供电但逻辑电平兼容3.3V的连接前务必确认电平兼容性避免反向灌电流损坏芯片。如果确实需要5V逻辑转换可以用最简单的分压电阻或者专用电平转换芯片。3. 软件代码实现与核心逻辑解析3.1 工程结构与外设初始化框架软件部分我用的是标准外设库开发环境Keil MDK5。工程文件组织刻意做了模块化划分这样阅读代码和后期移植都方便。目录结构大致如下Project/ ├── User/ │ ├── main.c # 主循环、系统时钟、任务调度 │ ├── stm32f10x_it.c # 中断服务函数 ├── BSP/ │ ├── bsp_adc.c # ADC多通道采集与滤波 │ ├── bsp_ds18b20.c # DS18B20单总线驱动 │ ├── bsp_oled.c # OLED驱动与界面绘制 │ ├── bsp_uart.c # 串口初始化、printf重定向 │ └── bsp_alarm.c # 告警逻辑封装 └── Core/ └── ... # 标准外设库文件系统时钟配置在SystemInit函数中完成默认使用外部8MHz晶振经PLL倍频到72MHz。使用标准外设库时如果晶振频率不是8MHz记得修改system_stm32f10x.c中的PLL参数否则外设时序全部错乱。主循环采用非阻塞架构用SysTick产生1ms时基核心代码中设置一个1000ms的软件定时标志检测到标志后执行一次传感器采集和OLED刷新int main(void) { SystemInit(); delay_init(); USART1_Init(115200); OLED_Init(); ADC1_Init(); DS18B20_Init(); printf(Water Quality Monitor Start...\r\n); while (1) { if (task_1s_flag) { task_1s_flag 0; water_quality_monitor_task(); } } }这个方式的好处是主循环不会被采集流程阻塞后续如果要加按键扫描、蓝牙通信或者其他任务只需要再增加不同周期的任务标志即可架构扩展性很好。3.2 ADC多通道采集与滤波实现ADC部分用ADC1的通道1和通道2分别采集pH模块和TDS模块的电压。配置为扫描模式每次转换完成后通过DMA搬运结果这样CPU几乎不需要等待时间。初始化部分重点有几个坑我在代码里加了注释void ADC1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); GPIO_InitStructure.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); ADC_DeInit(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; ADC_Init(ADC1, ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_55Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }ADC校准那几行尤其重要。STM32F1系列的ADC上电后需要进行一次校准否则转换结果会有几十码的偏移导致最终电压计算误差偏大。仿真过程中如果发现数据明显偏小或偏大先检查校准流程有没有执行。滤波方面我采用了中值平均滤波也就是连续采集N次去掉最大值和最小值剩下的取平均。这个算法简单但对水质传感器这类随机噪声比较明显的信号效果很好比单纯的平均滤波更能抵抗偶尔出现的尖峰干扰。uint16_t ADC_GetFilteredValue(ADC_TypeDef* ADCx, uint8_t channel) { uint16_t values[11]; uint32_t sum 0; uint16_t min_val, max_val; int i, j; for (i 0; i 11; i) { values[i] ADC_ReadChannel(ADCx, channel); delay_ms(2); } min_val values[0]; max_val values[0]; for (i 0; i 11; i) { if (values[i] min_val) min_val values[i]; if (values[i] max_val) max_val values[i]; } for (i 0; i 11; i) { if (values[i] ! min_val values[i] ! max_val) sum values[i]; } return (uint16_t)(sum / 9); }采样间隔这里我取了2ms每次刷新界面时11次采样加上延时总共约20ms对1秒刷新周期来说完全可接受。3.3 pH与TDS数据换算的核心逻辑ADC读到的是数字量必须经过换算才能变成有物理意义的pH值和TDS浓度。这一节是整个软件部分的精髓。先说TDS的换算。TDS模块的输出电压与浓度近似线性但不同厂家的模块差异比较大。我用的模块说明书里给出的典型电压对应关系是0V对应0ppm2.3V对应1000ppm。所以最简单的线性公式是float voltage (float)adc_value / 4096.0f * 3.3f; float tds_value voltage / 2.3f * 1000.0f;但实际使用中这个公式在低浓度段误差较大因为电导率与浓度在小范围并不完全线性。更准一点的做法是查表加线性插值我在工程里提供了一张温漂修正系数表因为TDS探头的电导率受温度影响明显每升高1℃电导率大约升高2%。所以最终代码里做了温度补偿float tds_value_compensated tds_value * (1.0f 0.02f * (temperature - 25.0f));也就是说25℃时补偿系数为1温度越高相同电压对应的TDS值越大这是符合物理规律的。再来看pH的换算。pH模块输出的电压值与pH值呈反比关系pH越高输出电压越低。市售模块的典型参数是pH 7.0时输出约2.5VpH 4.0时输出约3.0VpH 10.0时输出约2.0V。也就是说pH每变化1.0电压变化约0.18V但不同批次电极的斜率会有差异因此必须做两点标定。标定思路在代码里体现为两个校准参数pH7缓冲液标定点voltage_pH7和pH4缓冲液标定斜率slope。实测时用户在pH 7.0标准液中记录当前电压在pH 4.0标准液中再记录一次电压斜率计算如下float slope (voltage_pH7 - voltage_pH4) / (7.0f - 4.0f); float ph_value 7.0f (voltage_pH7 - current_voltage) / slope;这个公式的逻辑是当前电压与pH7标定点的差值除以每单位pH对应的电压变化量就得到了pH偏移量。斜率是负数所以需要仔细理清楚符号。代码里我用float类型的标定参数存放在Flash中方便后续通过串口指令更新而不用重新编译固件。3.4 DS18B20温度读取与CRC校验DS18B20用的是单总线协议时序要求比较严格。代码里用GPIO模拟时序核心函数包括复位、写字节、读字节三个步骤。温度转换过程要特别注意启动一次温度转换后DS18B20需要最长750ms才能完成转换如果立即去读暂存器读回来的可能是上一次的旧数据。所以我在代码里每次发起转换后延时800ms再读取数据和初始化下一次转换。虽然这会增加主循环任务的时间但对1秒刷新周期来说没有影响。读取到的原始数据是16位有符号数低4位为小数部分高5位为符号位。转换公式float temperature (float)(raw_temp 4) (float)(raw_temp 0x0F) * 0.0625f;代码里我额外加了CRC校验虽然DS18B20的CRC算法比较简单但可以通过查表方式实现。如果CRC校验失败这帧数据直接丢弃不更新温度值。原因是温度数据要参与pH和TDS的温度补偿一旦读到错误数据会导致补偿出错宁可让数据保持上一次的值也不能用坏数据去污染补偿计算。3.5 OLED界面布局与告警逻辑OLED驱动部分用的是SSD1306初始化寄存器序列不多基本是列地址范围、起始行列、对比度、电荷泵开关等十来条命令。0.96寸128x64分辨率我用6x8点阵字体分三行显示每行显示一个参数第一行显示TEMPERATURE: 25.3℃第二行显示PH VALUE: 6.89第三行显示TDS VALUE: 255ppm字体取模工具生成数组放到独立的font.h文件里换字体只需要替换数组即可。刷新逻辑采用全刷策略因为数据变化频率不高每秒钟刷一次不会有闪烁问题。如果以后要提高刷新率可以改成局部刷新只更新变化的字符区域。告警判断独立封装在bsp_alarm.c中每个参数有两个阈值都在程序开头以宏定义给出了默认值。例如pH低于6.5或高于8.5视为异常TDS高于500ppm视为异常。温度只作为监测数据不触发告警因为不同场景下的水温正常范围差异太大硬性判断意义不大。告警动作是蜂鸣器响1秒停4秒循环LED同步闪烁。为了让告警不阻塞主流程我用定时器中断来翻转IO而不是在主循环里延时。蜂鸣器控制用PD5指示灯用PA6代码中通过一个全局告警标志位来统一管理这个设计比较像实际产品里的状态机逻辑。3.6 串口输出与printf重定向串口初始化的波特率设为1152008位数据1位停止位无校验。为了方便调试我在工程里做了printf重定向核心代码是重写fputc函数int fputc(int ch, FILE* f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }实际使用中上面的代码只要在Keil的Options里勾选Use MicroLIB就可以用printf直接输出到串口。如果不勾选MicroLIB需要处理半主机模式的问题会报错或卡死在启动代码里。这是新手最容易踩的坑。串口输出格式做成了类似日志的结构包含时间戳、参数名、单位、告警状态方便上位机解析printf([%lu] TEMP%.1f, PH%.2f, TDS%.1f, ALARM%d\r\n, get_tick_ms(), temperature, ph_value, tds_value, alarm_flag);4. 仿真工程搭建与Proteus联调实操4.1 仿真所需的软件环境仿真部分的软件准备如下Keil MDK5用于编写和编译STM32工程生成hex文件。版本不需要太高5.20以上即可。Proteus 8 Professional建议使用8.6以上版本。低版本对STM32F103C8T6模型的支持不完整可能无法仿真ADC外设。如果使用Store的proteus版本部分库元件路径和原理图绘制方式略有差别但核心联调步骤一致。软件安装过程就不多说了记得先装Keil再装Proteus或者装完Keil后给Proteus添加ARM编译器路径。Proteus本身不自带ARM GCC编译器需要手动指定Keil的安装路径才能协同仿真。这一步没配置好后面编译产生的hex文件就无法在Proteus里加载。4.2 Proteus原理图绘制与关键元件选择在Proteus中新建工程后从元件库中提取以下元件STM32F103C8T6在Pick Devices里搜STM32F103C8注意Proteus有些版本需要勾选全部库才能搜到。DS18B20直接搜DS18B20模型是现成的。电位器POT-HG用于模拟pH和TDS传感器输出的模拟电压。pH传感器电压范围2.0V到3.0VTDS传感器电压范围0到2.3V两个电位器分别接到ADC引脚通过调节电位器旋钮改变输入电压就能模拟不同水质情况。虚拟终端VIRTUAL TERMINAL用于显示串口输出。电阻、电容、按键等按原理图放置。OLED在Proteus里的处理方式比较特殊。Proteus 8自带的I2C调试器没有图形界面想看到OLED显示内容网上有人做SSD1306的Proteus模型插件但并不是官方标准库的一部分对于开源项目来说可以退而用之虚拟方式验证。我的做法是在Proteus仿真里省去OLED改用虚拟终端输出串口数据判断系统运行状态而OLED显示效果以实物验证为准。这一点要特别说明仿真中看不到OLED不算问题是Proteus模型库的局限不代表代码有bug。4.3 完整联调流程具体联调按下面的步骤走在Keil中打开工程点击编译按钮确保编译通过且没有警告。编译输出目录下会生成hex文件比如Output/WaterQuality/WaterQuality.hex。在Proteus中双击STM32F103C8T6芯片在Program File里浏览选中刚才生成的hex文件点击OK。修改芯片的时钟频率为72MHz和代码里SystemInit配置保持一致。这一步很关键如果频率不匹配DS18B20的单总线时序会错乱温度读出来永远是0。点击运行按钮打开虚拟终端。调节pH对应的电位器观察虚拟终端中PH值的数字变化。调节TDS对应的电位器观察TDS值的变化。将TDS电位器电压调高到超过阈值观察蜂鸣器逻辑引脚的电平变化。Proteus里可以直接测引脚颜色红色是高电平蓝色是低电平。仿真过程中最有价值的地方在于你可以任意改变传感器输入信号而不必担心损坏传感器或等待水质变化。调试算法和逻辑的效率远高于实物实验。我实测过整个仿真工程从点击运行到看到串口数据输出耗时不超过2秒响应速度很快。4.4 仿真与实物之间的差异Proteus仿真无法完全复现实物的所有特征这个必须有心理预期。第一是ADC噪声。实物中ADC采样值天然带有噪声需要滤波算法才能稳定而仿真里电位器输出的电压很干净滤波效果看起来不明显。这不代表滤波代码没用拿到实物上就会看到区别。第二是传感器模块的模型精度。Proteus中的DS18B20模型虽然能正确响应单总线时序但温度转换速度比实物快很多实物的750ms延时在仿真里被压缩得很短。所以代码里的延时参数在仿真环境验证没问题后到实物上还要再测试调整。第三是I2C时序问题。Proteus对I2C的模拟比较严格如果代码里I2C频率设置过高仿真里可能通信失败。我用的SSD1306驱动代码跑400kHz在仿真里就遇到过通信超时的问题后来在模块头文件里把I2C时钟调整到100kHz才通过。所以结论是仿真适合验证逻辑正确性实物测试才是检验系统稳定性的标准。不建议只做仿真不做实物两者结合才是这套开源项目最合适的使用方式。5. 常见问题与排查技巧实录5.1 ADC采集数值跳变严重【现象】串口输出数据波动很大同一个传感器静态情况下采样值上下跳了几十个码。【排查思路】先检查电源是否稳定。ADC参考电压是VDDA如果VDDA上纹波大采集值一定跳。用示波器看3.3V供电电压是否稳定不稳定则检查电容布局。再检查模拟输入引脚是否悬空。没有接传感器时引脚处于高阻状态采集值随机跳动属于正常现象接上传感器后如果还跳才需要继续排查。滤波算法有没有正确生效。只做单次采样不经过滤波处理数据一定跳。确认调用的是滤波函数而不是直接读ADC寄存器。最后检查信号线上有没有干扰源。蜂鸣器、继电器这类感性负载动作时会产生反向电动势如果续流二极管没接可能会影响ADC读数。【解决方法】在信号源和ADC引脚之间加RC低通滤波软件上加大采样次数配合中值平均滤波基本能把波动控制在合理范围。5.2 DS18B20读不到温度或读到固定值【现象】温度始终显示0.0或者85.0。【排查思路】这两个现象的含义完全不同。0.0说明单总线通信失败代码没有从DS18B20读到有效数据。先查上拉电阻有没有接DS18B20的数据线必须接4.7kΩ上拉到3.3V如果漏接时序完全无法工作。再查GPIO配置数据线引脚必须配置为开漏输出模式我用的是PB1初始化时配置为GPIO_Mode_Out_OD。85.0是DS18B20上电后的默认值。出现这个值说明总线通信正常但温度转换尚未完成或转换命令未正确执行。检查代码里是否在发起转换后等待足够时间如果等待时间太短读到的就是暂存器中上电默认值85。【解决方法】确保单总线时序符合数据手册要求转换等待时间设置在750ms以上。如果用的是Proteus仿真把芯片频率改为72MHz避免时序因为频率不匹配而出错。5.3 OLED不亮或者花屏【现象】OLED上电后无显示或者有显示但内容错乱。【排查思路】I2C地址是否正确。SSD1306的I2C地址通常是0x787位地址0x3C左移一位但部分模块是0x7A。检查代码里的设备地址和实际模块是否一致。上拉电阻是否接上。I2C总线必须有上拉没有上拉时通信不稳定表现就是OLED随机亮灭或者花屏。初始化时序是否正确。SSD1306上电后需要等待一段时间才能接受初始化命令如果上电后立即初始化可能失败。在OLED_Init函数开头加至少100ms延时。界面刷新方式是否正确。如果刷屏时没有先清屏就直接写数据字符位置会累积看起来就是花屏。【解决方法】Proteus仿真下如果花屏调整I2C时钟到100kHz。实物中如果花屏先用逻辑分析仪抓I2C波形确认设备地址和应答位正确。5.4 仿真中调节电位器数值不变化【现象】Proteus仿真中运行正常但调节pH或TDS电位器虚拟终端输出不变。【排查思路】电位器的输出引脚是否确实接到了对应的ADC引脚。Proteus里连线容易连错检查接线是否有交点。电位器的滑动端通常标注W或OUT正确的接法是滑动端接ADC引脚两端分别接电源和地。电位器步进值设置是否合理。Proteus电位器默认步进值是1%即每调节一次变化总阻值的1%导致输出电压二次变化很小。在电位器属性里把Step值改大比如改成15这样效果才明显。滤波算法是否把变化量平滑掉了。中值平均滤波的响应速度相对较慢调节电位器后需要等待几个刷新周期才能看到输出变化不要把调节后立即无反应误判为故障。【解决方法】改大电位器步进值耐心等待1到2秒刷新周期如果还不行就检查接线和ADC通道映射关系。5.5 代码编译通过但Proteus运行无响应【现象】Keil编译成功生成hex文件加载到Proteus芯片后点击运行芯片引脚没有电平变化虚拟终端也没有输出。【排查思路】检查hex文件加载是否正确。Proteus里芯片型号是否选错如果选了F103C6flash容量只有32KB而编译出来的代码可能超过这个限制导致程序没完整加载。检查芯片是否进入复位死循环。BOOT0和BOOT1引脚在Proteus里默认状态如果不对程序可能跑不起来。将两个引脚接地或者用引脚工具强制拉低。检查时钟配置。如果SystemInit里的PLL配置和Proteus模型支持不一致程序会跑飞。Proteus对时钟的模拟比较严格配置错误直接卡死。【解决方法】按顺序检查芯片型号、BOOT引脚电平、时钟频率配置。还可以在工程main函数开头加一个GPIO翻转测试如果引脚能翻转说明程序在跑再逐功能排查。常见问题典型现象排查优先级ADC数值跳变静态采样值波动大先查电源滤波再查滤波算法DS18B20读数为0单总线通信失败先查上拉电阻再查GPIO模式DS18B20读数为85转换未完成就读取查转换等待时间OLED花屏显示内容错乱先查I2C地址和上拉再查初始化延时仿真调节无效电位器调节数值不变先查接线再查电位器步进值仿真无响应芯片无任何输出先查型号再查BOOT和时钟6. 开源资料清单与后续扩展方向这套项目开源的内容包含以下文件完整源码工程Keil MDK5可直接打开编译文件模块划分清晰注释完整。原理图PDF和源工程文件用立创EDA绘制可直接导出用于PCB设计。Proteus仿真工程导入即可运行无需额外配置。使用说明文档包含硬件接线表、引脚定义、串口通信协议和参数校准步骤。资料获取方式在项目发布页有详细说明代码使用MIT协议个人学习和商用都可以直接使用标注出处即可。从扩展性的角度这套系统可以往几个方向继续迭代第一把串口输出改成MQTT上报配一块ESP8266模块就能接入物联网平台实现远程水质监控。ESP8266用AT指令即可代码层面只需要在串口中断里处理收到的返回信息复杂度不高。第二增加USB或SD卡存储功能实现历史数据的本地记录。STM32F103自带USB外设SD卡用SPI接口驱动对现有工程改动也不大。第三把传感器类型替换成其他模拟量传感器比如光照强度、土壤湿度、空气质量等。因为这些传感器的输出大多也是电压信号复用现在的ADC采集、滤波、阈值告警链路只需要改换算公式即可。我在实际做这个项目时最大的体会是水质检测系统的难点其实不在STM32本身而在传感器的标定和数据的可靠性。同样的硬件平台算法和标定做得好不好直接决定了系统能不能用。因此代码里的滤波、温度补偿、两点标定这些细节绝对不是可有可无的装饰而是系统能否稳定工作的关键。这套开源项目把这部分逻辑全部公开出来正是希望大家不只是复制代码而是能理解每一步换算背后的原理这样移到自己的项目里时才能真正做到举一反三。