简介:一套基于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是64KB,RAM是20KB,装下整个测量程序、温度补偿表和OLED驱动之后还有至少一半余量,不用焦虑存储不够。唯一的建议是不要选那些冷门型号做毕设,比如F030系列虽然便宜,但ADC通道分配和参考电压配置跟F103差异不小,你会在调试上多花不少时间。
2.2 电导池的信号链:从交流激励到ADC能读的直流电压
电导率传感器本身不是直接输出电压的,它就是一个电导池——两根电极插在溶液里,溶液相当于一个电阻,电阻值随离子浓度变化。常见的做法是用交流激励信号加在电极两端,避免直流极化,然后通过运放把流过溶液的电流转换成电压。整流或峰值检波后,得到一个与电导成正比的直流电压,再送进STM32的ADC。
典型的电路结构是:
- 信号源:用STM32的定时器输出一个1kHz~5kHz的方波或正弦波,经过隔直电容后加载到电导池;
- 电流电压转换:电极返回的微弱电流经过运放(常用LM358或OP07)构成的比例放大电路,变成电压信号;
- 整流滤波:有源整流加RC低通滤波,输出平滑的直流电平;
- 分压匹配:这个电平通常在0~3.3V之间,直接进ADC。
这里最容易被忽略的是“交流激励”这四个字。如果直接拿直流电压加在电极上,几分钟后电极表面会发生极化,等效电阻漂移,读数会持续性缓慢变化,怎么调软件都不稳。这个问题后面避坑章节还会细说。
2.3 一张接线表和三个关键参数
整个系统接线并不复杂,但引脚分配要想清楚再焊,尤其是不要跟调试串口打架。我一般用这样的分配方案:
| 模块 | 引脚 | 说明 |
|---|---|---|
| 电导率传感器激励输出 | PA8(TIM1_CH1) | 定时器输出1kHz方波 |
| 调理电路输出(放大后电压) | PA0(ADC1_IN0) | 第一量程通道 |
| 调理电路输出(第二档增益) | PA1(ADC1_IN1) | 第二量程通道,两档用不同放大倍数 |
| NTC温度传感器 | PA2(ADC1_IN2) | 检测溶液温度 |
| OLED I2C | PB8(SCL)/ PB9(SDA) | 显示读数 |
| 按键 | PB0、PB1 | 量程手动切换 / 标定确认 |
| USART1 | PA9(TX)/ PA10(RX) | 调试输出 |
硬件上三个参数最关键:激励频率、放大倍数、采样电阻匹配。激励频率太低会加剧极化,太高则溶液等效电容旁路效应变大,经验值是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.1uF+10uF退耦电容。这两个问题用示波器看波形最容易发现,但很多学生手里没示波器,那就先把采样时间拉满再说。
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的电导率测量仪的方案能帮到你,沿着这条信号链往下做,你会收获一个真正属于自己的测量仪器。
本文还有配套的精品资源,点击获取