简介一套基于STM32微控制器的电导率测量仪毕业设计与课程作业完整项目面向电子、自动化与嵌入式相关专业学生覆盖从硬件电路到系统软件的开发全流程。项目以STM32为处理核心通过ADC采样与传感器接口实现溶液电导率检测可扩展用于水质分析、环境监测等实务场景。压缩包共101个文件包体约72.97MB文件类型涵盖C源码、H头文件、原理图与PCB设计文件、Keil工程配置、Altium库文件以及FatFS文件系统、LCD液晶驱动、SD卡读写、中文字库等嵌入式常用组件另有演示视频与说明文档辅助理解。目前已有164人学习下载。通过该项目可系统掌握STM32外设驱动编写、ADC数据采集、传感器信号调理、实时操作系统移植等关键技能获得可编译烧录的完整工程模板与硬件参考设计电路对完成毕设课题或课程大作业具有扎实参考价值。1. 从一块板子到一台能出数的仪器基于STM32的电导率测量仪到底在测什么电导率测量在环境监测、水产养殖和水质检测里是刚需而STM32恰恰是这个量级下性价比最合适的主控。所谓“基于STM32的电导率测量仪”本质上不是把传感器接上ADC然后显示一个数字那么简单它是完整的模拟信号链设计传感器激励方式、弱信号调理、模拟量转数字量、量程切换、温度补偿、标定曲线每一环都直接决定最终读数可信不可信。适合接手这个方向的人是有STM32基础、但还不太熟悉模拟前端的学生以及想在课程作业里把电路和嵌入式两段经验一次打通的开发者。这个项目的坑大多不在代码而在你对测量原理的把握程度上。2. 硬件骨架主控选型、传感器与信号链搭建2.1 锁定STM32F103毕设主控为什么选它做电导率测量仪这类带模拟采集的毕设主控选型几乎不用纠结STM32F103C8T6是默认答案。原因有三个方面第一它的ADC是12位、最多支持16个外部通道虽然精度不算惊艳但对电导率这种需要分段量程、不要求超高分辨率的测量场景完全够用第二它有DMA配合定时器触发采样可以做到连续采集不占用CPU这对后面做滑动滤波和显示刷新特别有利第三生态太成熟了——标准库、HAL库、寄存器版例程到处都是遇到问题搜“STM32 ADC切换通道”都有大把现成代码可以对照。如果你手里的是F103ZET6或者F407也完全可以核心逻辑不变只是引脚号要对应调整。C8T6的Flash是64KBRAM是20KB装下整个测量程序、温度补偿表和OLED驱动之后还有至少一半余量不用焦虑存储不够。唯一的建议是不要选那些冷门型号做毕设比如F030系列虽然便宜但ADC通道分配和参考电压配置跟F103差异不小你会在调试上多花不少时间。2.2 电导池的信号链从交流激励到ADC能读的直流电压电导率传感器本身不是直接输出电压的它就是一个电导池——两根电极插在溶液里溶液相当于一个电阻电阻值随离子浓度变化。常见的做法是用交流激励信号加在电极两端避免直流极化然后通过运放把流过溶液的电流转换成电压。整流或峰值检波后得到一个与电导成正比的直流电压再送进STM32的ADC。典型的电路结构是信号源用STM32的定时器输出一个1kHz~5kHz的方波或正弦波经过隔直电容后加载到电导池电流电压转换电极返回的微弱电流经过运放常用LM358或OP07构成的比例放大电路变成电压信号整流滤波有源整流加RC低通滤波输出平滑的直流电平分压匹配这个电平通常在0~3.3V之间直接进ADC。这里最容易被忽略的是“交流激励”这四个字。如果直接拿直流电压加在电极上几分钟后电极表面会发生极化等效电阻漂移读数会持续性缓慢变化怎么调软件都不稳。这个问题后面避坑章节还会细说。2.3 一张接线表和三个关键参数整个系统接线并不复杂但引脚分配要想清楚再焊尤其是不要跟调试串口打架。我一般用这样的分配方案模块引脚说明电导率传感器激励输出PA8TIM1_CH1定时器输出1kHz方波调理电路输出放大后电压PA0ADC1_IN0第一量程通道调理电路输出第二档增益PA1ADC1_IN1第二量程通道两档用不同放大倍数NTC温度传感器PA2ADC1_IN2检测溶液温度OLED I2CPB8SCL/ PB9SDA显示读数按键PB0、PB1量程手动切换 / 标定确认USART1PA9TX/ PA10RX调试输出硬件上三个参数最关键激励频率、放大倍数、采样电阻匹配。激励频率太低会加剧极化太高则溶液等效电容旁路效应变大经验值是1kHz到5kHz之间放大倍数要根据量程定比如测纯水低电导和测盐水高电导相差悬殊单靠一个固定增益是不行的所以才有两路不同放大的通道或者用模拟开关切换反馈电阻。采样电阻的取值则决定了量程上限做毕设的话建议先用一个大范围电位器临时调标定完再固定成贴片电阻。3. 让ADC读出可信数字采集配置、滤波与量程分段3.1 用CubeMX搭采集骨架ADC的DMA与多通道切换STM32CubeMX可以省掉一半时间前提是你知道每个选项在做什么。ADC配置的要点是打开连续转换模式、开启扫描模式、把三个通道两个电导率量程加一个NTC温度都加入序列然后使能DMA循环模式。这样ADC会自己按顺序采集所有通道采完一轮自动从头开始完全不需要CPU介入启动下一次转换。// adc.c 里关键初始化代码 hadc1.Instance ADC1; hadc1.Init.ScanConvMode ENABLE; // 多通道扫描 hadc1.Init.ContinuousConvMode ENABLE; // 连续转换 hadc1.Init.NbrOfConversion 3; // 三个通道 hadc1.Init.DMAContinuousRequests ENABLE; // DMA循环请求 sConfig.Channel ADC_CHANNEL_0; // PA0 第一量程 sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); sConfig.Channel ADC_CHANNEL_1; // PA1 第二量程 HAL_ADC_ConfigChannel(hadc1, sConfig); sConfig.Channel ADC_CHANNEL_2; // PA2 NTC温度 HAL_ADC_ConfigChannel(hadc1, sConfig); HAL_DMA_Start(hdma_adc1, (uint32_t)hadc1.Instance-DR, (uint32_t)adc_buf, 3);这段代码的逻辑是先定义扫描序列里有三个通道然后逐个配置通道对应的端口和采样时间。采样时间那一项默认也行但建议至少选239.5周期因为电导率信号经过整流滤波后虽然平滑了仍然带有少量纹波采样时间足够长能让ADC内部采样电容完全充放电结果更稳。DMA配置好后adc_buf[0]是第一量程、adc_buf[1]是第二量程、adc_buf[2]是NTC温度这个对应关系由扫描顺序决定别搞反了。主循环里只需要读这个数组就行不需要手动启动ADC。3.2 数据不裸奔滑动均值滤波和量程分段直接拿ADC原始值计算电导率读数会在最后一位甚至后两位上乱跳。这不是ADC坏了而是电导池本身存在微小波动加上50Hz工频干扰的残余。解决方案是滑动均值滤波而且窗口长度要有讲究——太短压不住纹波太长会拖慢响应。#define FILTER_LEN 16 static uint16_t filter_buf[FILTER_LEN]; static uint8_t filter_idx 0; static uint32_t filter_sum 0; uint16_t adc_filter(uint16_t new_value, uint16_t *out) { filter_sum - filter_buf[filter_idx]; filter_sum new_value; filter_buf[filter_idx] new_value; filter_idx (filter_idx 1) % FILTER_LEN; *out filter_sum / FILTER_LEN; return *out; }这个函数的逻辑是一个环形队列每次进来一个新值先减去最老的那个值再加上新值然后取平均。16个点对于1kHz的采样率来说就是16毫秒响应速度足够快不会让人觉得读数迟钝。量程分段则是另一个层面的问题。电导率从去离子水的几个uS/cm到海水的几十mS/cm跨度有四个数量级。如果固定用一个增益低端信号太小全是噪底高端直接削顶。所以常见的做法是根据两路不同增益的ADC原始值判断当前落在哪个区间低增益通道饱和了就切高增益通道反之亦然。切换时需要一个滞回区间比如增益A读数是3000、增益B读数是800不要以单一阈值切换否则在交界处会来回跳。3.3 温度补偿用一个NTC把读数折算到25 ℃溶液电导率对温度极其敏感一般来说温度每升高1℃电导率大约增加2%左右不同溶液略有差异。如果温度补偿不做读出来的数值会随环境温度飘标定完第二天就“变准”了。最常用的补偿模型是线性温度系数法float conductivity_compensated raw_conductivity / (1.0f 0.02f * (temperature - 25.0f));这里的0.02f是默认温度系数26℃时候分母是1.02读数被缩回去一点。精确的做法是查阅目标溶液的实际温度系数比如KCl标准溶液取1.91%/℃自来水通常也在2%/℃附近。温度采集部分用一个NTC热敏电阻加分压电阻查表或直接用Steinhart-Hart公式转换。整个电导率测量仪的成败一半在温度补偿——温度测不准补偿就是负优化。NTC的采样值建议接在同一个ADC序列里和电导率通道一起采避免单独采集带来额外时序。4. 代码实现从标准库工程到OLED显示一条龙4.1 工程结构模块拆分与全局数据结构写这个项目最忌讳的就是把所有功能塞进main.c一个文件里几百行代码堆完后面调标定的时候你会疯掉。标准的分法是四个模块adc_task负责采集和滤波compute负责协议计算和补偿display负责OLED和按键debug负责串口输出。全局结构体把中间数据集中管理模块之间通过结构体传递不互相调用内部函数。typedef struct { uint16_t raw_low_gain; uint16_t raw_high_gain; uint16_t raw_temperature; float conductivity; // 当前电导率单位uS/cm float temperature; // 当前温度单位℃ uint8_t range_index; // 当前量程0低增益 1高增益 float k_factor; // 标定系数 } MeterData;这个结构体的好处是每个模块都知道数据在哪里。ADC采集任务只更新raw_字段计算模块读原始值写conductivity显示模块只管读conductivity并刷OLED。以后想加功能比如蓝牙上传或者SD卡存储只需要再多加一个模块读这个结构体。4.2 核心代码采ADC、算电导、切换量程量程判断和电导率计算是整个软件的核心部分。这里的关键是ADC原始值本身不能直接换算成电导率要先经过量程选择然后用标定系数做线性映射。void compute_update(MeterData *meter) { uint16_t active_raw; float mapped; if (meter-raw_low_gain 3500 meter-raw_low_gain 50) { meter-range_index 0; active_raw meter-raw_low_gain; mapped (float)active_raw * 0.10f; // 低增益档AD值转电导率系数 } else { meter-range_index 1; active_raw meter-raw_high_gain; mapped (float)active_raw * 1.50f; // 高增益档系数不同 } meter-conductivity mapped * meter-k_factor; meter-conductivity / (1.0f 0.02f * (meter-temperature - 25.0f)); }这段逻辑清晰但不完美两个档位的映射系数0.10f和1.50f是预先标定出来的典型值真正用的时候需要根据你买的探头电极常数、运放增益重新标定。k_factor是系统标定系数用标准溶液标定时统一修正实际读数与理论值的偏差。量程切换的滞回逻辑在这里也可以加上——当low_gain档读数接近饱和比如大于3900时强制切到high_gain而high_gain档读数小于300时切回low_gain中间留一段区间避免频繁切换。你可以在compute_update里再加两个判据逻辑不难但会让实际体验好很多。4.3 交互层OLED刷新、按键和串口调试输出OLED显示不用太花哨但信息布局要想清楚。第一行显示电导率数值和单位第二行显示温度和量程标志建议加一个“标定中”的状态提示。刷新频率5Hz就够太快OLED内部驱动跟不上反而出现残影。按键交互一般就两个功能短按切换手动量程长按进入标定模式。标定模式里通过串口打印提示把探头放入标准溶液后按确认键程序记录当前ADC值并反推k_factor。这样操作比用按键做复杂菜单简单得多答辩时演示也直观。串口调试输出的格式建议用一行一帧、逗号分隔方便用串口助手或Python脚本画曲线printf(ADC,%d,%d,%d,COND,%.2f,TEMP,%.2f,IDX,%d\r\n, meter-raw_low_gain, meter-raw_high_gain, meter-raw_temperature, meter-conductivity, meter-temperature, meter-range_index);这一行把原始值和计算值都打出来调试时低通滤波、温度补偿有没有生效一眼就能看出来。个人经验是调试电导率这类模拟量项目串口曲线比OLED读数好使十倍因为你能看到趋势而不是瞬间快照。5. 避坑电导率测量仪最容易翻车的5个细节5.1 换挡瞬间读数跳变时序没给足稳定时间现象按键切换量程后读数突然跳到极大值或极小值然后缓慢爬升回到正常。原因量程切换本质上是模拟开关改变放大器的反馈电阻切换瞬间放大器的输出需要重新建立直流工作点这个建立过程要几毫秒甚至几十毫秒。如果程序立刻就读ADC采到的一定是过渡状态。解决切换量程后延时至少50ms再把该通道的前几次采样结果丢弃。我一般在代码里用一个计数器实现切换后settle_cnt 10每次循环递减不为0时该通道数据直接跳过不参与滤波。5.2 探针放进溶液数值乱飘直流极化惹的祸现象读数在某个区间内无规律慢慢漂移把探头拿出来用纯水清洗后再放回溶液初始值每次都不同。原因如果激励信号是直流或方波占空比严重不对称溶液中电极表面会逐渐积聚离子形成极化层等效阻抗不断变化。这是电导率测量的经典物理问题不是电路噪声。解决确认激励信号是真正的交流频率1kHz以上占空比50%。如果用的是PWM输出必须在输出后加隔直电容否则直流分量照样造成极化。另外电极本身要选铂黑或石墨电极纯不锈钢电极的极化效应特别严重。5.3 ADC读数漂移不收敛采样时间和参考电压在捣鬼现象空载时ADC读数稳定在固定值附近但接上传感器后读数慢慢漂最后停在某个偏高的值。原因两个可能一是运放输出阻抗偏高ADC内部采样电容没充满就转换导致采到的值偏低或偏高二是MCU的VREF引脚没有充分退耦运放电流变化拉偏了参考电压。解决ADC采样时间从默认的13.5周期改成239.5周期同时检查硬件上VREF和VDDA引脚是否接了0.1uF10uF退耦电容。这两个问题用示波器看波形最容易发现但很多学生手里没示波器那就先把采样时间拉满再说。5.4 温度补偿结果不对公式里的单位坑你三次现象温度补偿打开后读数反而更不准温度从20℃变到30℃时补偿后的电导率变化更大。原因最典型的错误是NTC转换出来的温度不是摄氏度而是开尔文或者补偿系数搞成了百分比而不是小数。比如1 0.02 * (temp - 25)写成1 2 * (temp - 25)结果在30℃时分母变成11读数直接缩到原来的零点几。解决在串口调试输出里同时印出原始温度和补偿系数肉眼确认温度数值是25附近而非298附近再确认系数0.02是小数形式。建议用标准溶液在不同温度下测量补偿效果误差超过5%就回头查单位。5.5 串口输出乱码或波形卡顿波特率和中断优先级打架现象串口打印数据偶尔出现乱字符或者打印期间OLED刷新明显变慢。原因串口波特率和晶振实际频率不匹配是比较少见的更常见的是HAL库的printf重定向没有正确处理串口发送完成标志导致上一帧还没发完新数据又写进缓冲区。另一个常见原因是SysTick中断优先级和DMA中断优先级配置不当DMA中断被SysTick抢占导致ADC数据更新不均匀。解决使用HAL_UART_Transmit而不是裸printf或者重定向fputc时检查__HAL_UART_GET_FLAG(huart1, UART_FLAG_TC)等待发送完成中断优先级配置上DMA设为低优先级、SysTick设为最低避免采样延迟抖动。6. 把毕设做成能答辩的作品标定、验证与交付技巧电导率测量仪这种项目如果只做到“能显示数字”答辩时很容易被老师一个问题问倒你怎么证明它测的是准的所以你还需要一套完整的标定与验证流程达到“我用标准溶液标过并且有数据记录”的程度。标定的实操方法是买一袋KCl粉配标准溶液常见浓度是84uS/cm、1413uS/cm、12.88mS/cm这三档覆盖纯水、自来水和海水三个典型区间。别用自己配的盐水当标定基准离子纯度不一样误差能到10%。标定过程很简单把探头放进1413uS/cm标准液等读数稳定后记录ADC值算出系数k_factor再把探头放进12.88mS/cm高量程溶液同样操作。两档分别标不要共用一个系数。软件里做成“短按标定键三次依次记录零点、低标、高标”整个过程可以在OLED上显示进度答辩演示时非常直观。验证环节建议做两组数据一组是同一标准溶液反复测10次记录平均值和最大偏差向老师展示重复性另一组是配不同浓度梯度的溶液用你的表测完和理论值画一条线性度曲线截个图放进论文附录。这两张图比任何文字描述都有说服力。往届有学长把项目停在“能点亮OLED”这个阶段答辩时被问温度补偿的原理就哑火了。我的建议是把标定流程、温度补偿公式、量程切逻辑这三件事理解透哪怕代码不是我写的也能讲清楚这才是把课程作业变成毕设作品的关键。这个项目里我自己的习惯是始终留一份串口输出不加关所有数据在调试窗口保持透明。带过几轮课程设计后发现翻车往往不是硬件焊错而是软件把数据“修饰”得比实际更好看。透明输出能让你尽早身份下藏在波形里的异常等积累一两次“原来问题在这”的经验后再做复杂系统就有底气了。希望这套基于STM32的电导率测量仪的方案能帮到你沿着这条信号链往下做你会收获一个真正属于自己的测量仪器。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?