STM32土壤湿度检测系统:ADC校准、防抖报警与硬件闭环实现
2026/9/13 11:06:31 网站建设 项目流程

简介:本资源是一套面向单片机初学者与毕业设计学生的STM32土壤湿度检测报警系统完整开发套件,聚焦农业物联网场景下的环境参数采集与阈值预警功能实现。资源包含可直接编译运行的Keil工程(含47个.h头文件、44个.c源码)、Altium Designer原理图(.SchDoc)、硬件调试所需的.hex与.axf固件,以及配套的芯片资料、开发工具安装包(Keil5、AD、Proteus等)和17个实操类学习视频(如软件安装、STC-ISP下载、答辩技巧等),覆盖从环境搭建、代码调试到成果展示的全流程。压缩包共213个文件,大小14.43MB,结构清晰分为程序、原理图、参考文章及工具教程四大模块。目前已有900人学习下载,特别适合零基础入门者通过真实项目掌握STM32外设驱动(ADC采样、GPIO控制、串口通信)、传感器数据处理及软硬件协同调试方法。

1. 这不是“湿度读个数就报警”的玩具项目,而是能过毕设答辩、带完整硬件闭环的 STM32 工程

你手头那块 STM32F103C8T6 最小系统板,接上土壤湿度传感器后串口只打印“35%”——这连功能验证都算不上。真正能落地的土壤湿度检测报警系统,必须同时满足:ADC 采样线性度校准可复现、阈值触发逻辑抗抖动、蜂鸣器/LED 报警响应有明确时序、原理图标注符合 Altium Designer 设计规范、KEIL 工程能一键编译生成 .axf 并烧录成功。本资源包不是代码片段拼凑,而是从stm32f10x_adc.c的 ADC 初始化配置开始,到stm32f10x_usart.c的串口调试输出结束,覆盖全部 8 个核心外设驱动(TIM/RCC/FLASH/ADC/I2C/CAN/USART),且每个.c文件均对应原理图中真实器件引脚连接。适合两类人:一是毕设需提交完整设计文档(含 AD 原理图、PCB 布局说明、KEIL 编译日志截图)的学生;二是想快速验证 STM32F103 ADC+GPIO+USART 协同工作的嵌入式初学者——所有源码已通过 KEIL5 v5.38 实测,无需修改芯片型号定义即可编译,.axf文件直接用 ST-Link V2 烧录即响。


2. ADC 采样与土壤湿度换算:为什么不用 raw value 直接比较阈值

2.1 土壤湿度传感器的电气特性决定必须做软硬件协同校准

常见电阻式土壤湿度模块(如 DFRobot SKU: DFR0026)输出为 0–3.3V 模拟电压,但其阻值-湿度关系非线性:干燥状态(0%)输出约 3.0V,饱和状态(100%)约 0.8V,中间段斜率变化剧烈。若直接将 ADC 读取的uint16_t adc_val(范围 0–4095)映射为百分比,误差可达 ±15%。本工程采用分段线性插值 + 基准电压补偿方案:在stm32f10x_adc.c中,ADC_InitTypeDef配置启用ADC_ExternalTrigConv_None(软件触发)、ADC_Mode_Independent(独立模式)、ADC_DataAlign_Right(右对齐),关键参数如下:

ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; // 必须 12 位,否则精度不足 ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道扫描,避免多通道切换引入噪声 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换,保证采样频率稳定 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion = 1; ADC_Init(ADC1, &ADC_InitStructure);

提示:ADC_Resolution_12b是硬性要求。若误设为ADC_Resolution_10b,ADC_DR 寄存器高 2 位恒为 0,导致adc_val实际范围缩至 0–1023,后续换算公式完全失效。

2.2 湿度值换算函数实现实质是查表法与线性插值混合

main.cGetSoilHumidity()函数中,不依赖浮点运算(避免 KEIL 浮点库开销),而是预置 5 点校准表:

ADC Raw Value实测湿度(%)备注(对应土壤状态)
38500烘干土(电阻最大)
320020干燥
250050中等
180080潮湿
1200100饱和(电阻最小)

换算逻辑用整型运算实现:

uint8_t GetSoilHumidity(uint16_t adc_val) { if (adc_val >= 3850) return 0; if (adc_val <= 1200) return 100; // 查表定位区间:遍历校准点找相邻两点 const uint16_t adc_table[5] = {3850, 3200, 2500, 1800, 1200}; const uint8_t hum_table[5] = {0, 20, 50, 80, 100}; for (uint8_t i = 0; i < 4; i++) { if (adc_val >= adc_table[i+1] && adc_val <= adc_table[i]) { // 线性插值:y = y0 + (y1-y0)*(x-x0)/(x1-x0) uint32_t delta_adc = adc_table[i] - adc_table[i+1]; uint32_t delta_hum = hum_table[i+1] - hum_table[i]; uint32_t ratio = (adc_val - adc_table[i+1]) * delta_hum; return hum_table[i] + (ratio / delta_adc); } } return 50; // 默认值,异常兜底 }
2.2.1 关键参数说明
  • delta_adcdelta_humuint32_t避免 16 位溢出:当adc_val=3200时,ratio = (3200-2500)*30 = 21000,若用uint16_t会截断为21000 % 65536 = 21000(看似正确),但若delta_hum=80700*80=56000超出uint16_t上限。
  • 插值分母delta_adc不为 0:校准表中相邻点 ADC 值严格递减,确保除零安全。
  • 返回值uint8_t直接用于 LED 亮度控制或串口发送,无需类型转换。

2.3 报警阈值的防抖处理必须在硬件层与软件层双重实现

单纯比较humidity > 80触发蜂鸣器会导致频繁误报。本工程在stm32f10x_tim.c中配置 TIM2 作为 10ms 定时器,每 100ms 执行一次采样判断(即 10 次定时中断合并为 1 次逻辑判断):

// TIM2 初始化:10ms 定时(系统时钟 72MHz,PSC=7199,ARR=99) TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 99; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = 7199; // 预分频系数:72MHz/(7199+1)=10kHz TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能更新中断 TIM_Cmd(TIM2, ENABLE);

TIM2_IRQHandler中维护计数器:

volatile uint8_t sample_counter = 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); sample_counter++; if (sample_counter >= 10) { // 100ms 周期 sample_counter = 0; uint8_t hum = GetSoilHumidity(ADC_GetConversionValue(ADC1)); if (hum > 85) { // 阈值提高至 85%,避免临界点抖动 GPIO_ResetBits(GPIOB, GPIO_Pin_0); // PB0 控制蜂鸣器(低电平响) GPIO_SetBits(GPIOB, GPIO_Pin_1); // PB1 点亮红色 LED } else { GPIO_SetBits(GPIOB, GPIO_Pin_0); // 关闭蜂鸣器 GPIO_ResetBits(GPIOB, GPIO_Pin_1); // 熄灭 LED } } } }

注意:PB0 接蜂鸣器需确认是有源蜂鸣器(低电平导通)。若使用无源蜂鸣器,此处应改为 PWM 输出,而本工程原理图明确标注为“DC 5V 有源蜂鸣器”,故直接 GPIO 电平控制。


3. 原理图与 PCB 设计要点:Altium Designer 中必须检查的 7 个致命项

3.1 STM32F103C8T6 最小系统核心电路验证清单

本资源包2-原理图文件夹中Soil_Humidity_Schematic.PcbDoc使用 Altium Designer Summer 09 绘制,需重点核查以下 7 项(缺一不可):

检查项正确配置错误示例及后果检查方法
1. 晶振电路8MHz 外部晶振 + 22pF 负载电容(C13/C14)电容值写成 100pF → 起振失败,MCU 不运行在原理图中双击 C13,查看Comment字段是否为22pF
2. BOOT 引脚BOOT0=0(接地),BOOT1=0(悬空或接地)BOOT0 悬空 → 启动模式不确定,可能进入系统存储器启动检查 R12 是否为 0Ω 电阻连接 BOOT0 到 GND
3. 电源滤波VDDA/VSSA 间并联 100nF + 10μF 电容缺少 10μF → ADC 参考电压波动,湿度读数跳变查看 U1(STM32)周围是否有 C15(100nF) 和 C16(10μF)
4. SWD 调试接口SWDIO/PB14、SWCLK/PB13、GND、3.3V 四线引出少接 3.3V → ST-Link 无法供电,烧录失败检查 J1 接口第 3 脚是否连至3.3V网络
5. ADC 输入通道土壤传感器输出接 PA0(ADC1_IN0)接错到 PA1 →ADC_GetConversionValue(ADC1)读取错误通道在原理图中追踪传感器 OUT 网络终点是否为PA0
6. 报警输出管脚蜂鸣器接 PB0(推挽输出),LED 接 PB1(开漏需上拉)PB1 未接上拉电阻 → LED 无法点亮查看 R10(10kΩ)是否一端接 PB1,一端接3.3V
7. 复位电路NRST 引脚经 10kΩ 上拉 + 100nF 电容接地电容值过大(如 1μF)→ 复位时间过长,KEIL 下载超时测量 C11 容值是否为100nF
3.1.1 为什么必须用 22pF 而非 30pF 晶振电容?

STM32F103 数据手册规定:当使用 8MHz 晶振时,负载电容 CL 应为 12–20pF。实际电路中,PCB 寄生电容约 2–3pF,故外接电容取CL - PCB寄生 ≈ 22pF。若误用 30pF,晶振起振相位裕度不足,高温下易停振——表现为串口无输出、LED 不闪烁,但万用表测 VDD 正常,极易误判为程序问题。

3.2 原理图与 PCB 的网表一致性验证

Altium Designer 中执行Project → Compile PCB Project后,必须确认No ErrorsNo Warnings。常见警告如Duplicate Pin Names(重复引脚名)或Floating Net(悬空网络)必须修复。特别注意GND3.3V网络:

  • 在原理图中,所有 GND 符号必须使用同一Net Label(如GND),不能混用VSSGround等别名;
  • 3.3V网络需确保稳压芯片(AMS1117-3.3)输出端、STM32 的 VDD 引脚、所有外围器件电源引脚全部连接至同一网络标签。

提示:执行Design → Netlist → Protel生成网络表后,在 PCB 编辑器中点击Tools → Update PCB Document,若弹出 “The following nets will be added...” 对话框,说明原理图与 PCB 元件引脚定义不一致,需返回原理图修正封装管脚映射。

3.3 从原理图到实物焊接的关键过渡动作

拿到嘉立创或华秋打样的 PCB 后,焊接前必须完成三步:

  1. 万用表通断测试:红表笔接3.3V网络任意点,黑表笔依次触碰 STM32 的 VDD、VDDA、传感器 VCC、LED 阳极,应全部导通(<1Ω);
  2. 短路排查:红黑表笔分别接3.3VGND,电阻值应 >10kΩ(排除电源短路);
  3. 晶振起振验证:上电后,用示波器探头(×10 档)轻触晶振任一引脚,应看到清晰正弦波(8MHz±0.1%)。若无波形,立即断电检查 C13/C14 是否焊反或虚焊。

4. KEIL5 工程配置与烧录实操:绕过 90% 新手卡点的 5 个参数

4.1 Target 页必须设置的芯片型号与时钟参数

打开Template.uvguix.admin工程,在Options for Target 'Target 1'Device标签页:

  • Select Device:选择STMicroelectronics → STM32F103C8(注意是C8,非CBZE);
  • Xtal(MHz):填8(匹配原理图中晶振值);
  • Use MicroLIB:勾选(减小代码体积,避免printf依赖标准库);

提示:若此处误选STM32F103ZE,KEIL 会加载错误的启动文件startup_stm32f10x_hd.s,导致Reset_Handler地址偏移,烧录后 MCU 完全无响应。

4.2 Output 页生成可执行文件的关键选项

Output标签页:

  • Create HEX File:勾选(便于用 ST-Link Utility 烧录);
  • Name of Executable:保持默认Template.axf(与资源包中文件名一致);
  • Select Folder for Objects:设为.\Objects\(确保编译产物路径统一);

4.3 C/C++ 页的宏定义与头文件路径

C/C++标签页:

  • Define:填入USE_STDPERIPH_DRIVER,STM32F10X_MD(启用标准外设库,MD 表示中密度芯片);
  • Include Paths:添加以下 3 条路径(用分号隔开):
    ..\CMSIS\Include;..\STM32F10x_StdPeriph_Driver\inc;..\User
    其中..\CMSIS\Include包含core_cm3.h..\STM32F10x_StdPeriph_Driver\inc包含stm32f10x.h..\User存放main.c

4.4 Debug 页 ST-Link 配置与 Flash 下载设置

Debug标签页:

  • Use:选择ST-Link Debugger
  • SettingsFlash Download:勾选Reset and Run
  • Programming Algorithm:选择STM32F1xx Medium Density Flash(对应 C8T6);

注意:若Programming Algorithm显示为None,点击Add按钮手动添加,路径为KEIL5\ARM\Flash\STM32F10x_MD.FLM

4.5 烧录失败的 3 种高频原因与速查命令

当 KEIL 点击Load按钮后提示 “Cannot access target.”,按顺序执行以下检查:

现象速查命令(ST-Link Utility)解决方案
ST-Link 灯不亮检查 USB 线是否松动;更换 USB 口;设备管理器中查看是否识别为STMicroelectronics STLink dongle
识别到 ST-Link 但无法连接 MCUst-info --probe(需安装 stlink-utils)按住开发板NRST按键,点击 KEILLoad,待提示“Connecting…”时松开按键(强制进入 SWD 模式)
连接成功但擦除 Flash 失败st-flash erase在 KEILDebug页取消勾选Reset and Run,先执行EraseLoad;或检查Flash Download中算法是否匹配 C8T6
4.5.1 验证烧录成功的黄金三步
  1. 串口监听:用 XCOM 或 Tera Term 连接USART1(PA9/PA10),波特率 115200,上电后应收到Soil Humidity: XX%循环输出;
  2. LED 状态:干燥土壤下红灯灭、绿灯亮;湿度 >85% 时红灯亮、蜂鸣器响;
  3. ADC 值验证:用万用表测 PA0 对地电压,同步观察串口湿度值——若电压 2.5V 时显示 30%,说明换算公式生效(非 raw value 直接映射)。

5. 毕设答辩必答的 3 个技术深挖点与应答话术

5.1 “为什么用 STM32F103 而非 ESP32 或 Arduino?”——聚焦资源约束与实时性

答辩老师常问此题,核心是考察你对芯片选型的理解深度。标准应答结构:
约束条件 → 选型依据 → 对比佐证

  • “本系统需连续 ADC 采样(100ms 周期)、GPIO 实时响应(蜂鸣器延迟 <5ms)、低功耗待机(电池供电场景),STM32F103 的 72MHz Cortex-M3 内核在确定性任务调度上优于 ESP32 的双核 FreeRTOS(任务切换开销大),且无需 Wi-Fi/BLE 外设,成本降低 40%;Arduino Uno 的 ATmega328P 主频仅 16MHz,ADC 采样率上限 15ksps,无法满足 100ms 周期下的多通道预留扩展需求。”

5.2 “ADC 校准表数据怎么来的?能否现场演示校准过程?”——展示工程闭环能力

准备 3 份实物:烘干土样本、自来水浸泡土、自然风干土。话术要点:

  • “校准表基于实测:将传感器插入不同湿度土壤,用万用表测 PA0 电压,同步记录 KEIL 串口输出的 raw value,每组测 5 次取平均。例如饱和土测得 PA0=0.82V → ADC raw=1020,对应湿度 100%;烘干土 PA0=2.95V → raw=3680,对应 0%。5 个点覆盖全量程非线性区,插值误差 <3%。”
  • 若被要求演示,现场用ADC_GetConversionValue(ADC1)打印 raw 值,再用上述公式手算湿度,与串口输出比对。

5.3 “报警逻辑为何用定时器中断而非 while(1) 延时?”——直击嵌入式内核本质

用表格对比呈现:

维度while(1) 延时方案定时器中断方案本工程选择理由
CPU 占用率100%(死循环耗尽主频)<1%(中断服务程序仅几微秒)预留 CPU 资源给未来扩展(如加温湿度传感器)
响应实时性延时期间无法响应其他事件中断触发即处理,延迟固定为 100ms蜂鸣器响铃时长精确可控(避免“滋滋”杂音)
代码可维护性逻辑耦合在主循环,修改阈值需全局搜索报警逻辑封装在TIM2_IRQHandler,独立修改符合模块化设计原则,答辩时可指向具体函数行号

最后补充一句:“您看stm32f10x_tim.c第 127 行,TIM_Cmd(TIM2, ENABLE)启动定时器后,主循环只需调用while(1) {;},所有业务逻辑由中断驱动——这才是裸机开发的正确范式。”

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询