简介:本资源是一套面向单片机初学者与毕业设计学生的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.c的GetSoilHumidity()函数中,不依赖浮点运算(避免 KEIL 浮点库开销),而是预置 5 点校准表:
| ADC Raw Value | 实测湿度(%) | 备注(对应土壤状态) |
|---|---|---|
| 3850 | 0 | 烘干土(电阻最大) |
| 3200 | 20 | 干燥 |
| 2500 | 50 | 中等 |
| 1800 | 80 | 潮湿 |
| 1200 | 100 | 饱和(电阻最小) |
换算逻辑用整型运算实现:
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_adc和delta_hum用uint32_t避免 16 位溢出:当adc_val=3200时,ratio = (3200-2500)*30 = 21000,若用uint16_t会截断为21000 % 65536 = 21000(看似正确),但若delta_hum=80则700*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 Errors且No Warnings。常见警告如Duplicate Pin Names(重复引脚名)或Floating Net(悬空网络)必须修复。特别注意GND和3.3V网络:
- 在原理图中,所有 GND 符号必须使用同一
Net Label(如GND),不能混用VSS、Ground等别名; 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 后,焊接前必须完成三步:
- 万用表通断测试:红表笔接
3.3V网络任意点,黑表笔依次触碰 STM32 的 VDD、VDDA、传感器 VCC、LED 阳极,应全部导通(<1Ω); - 短路排查:红黑表笔分别接
3.3V和GND,电阻值应 >10kΩ(排除电源短路); - 晶振起振验证:上电后,用示波器探头(×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,非CB或ZE); - 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; - Settings→
Flash 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 但无法连接 MCU | st-info --probe(需安装 stlink-utils) | 按住开发板NRST按键,点击 KEILLoad,待提示“Connecting…”时松开按键(强制进入 SWD 模式) |
| 连接成功但擦除 Flash 失败 | st-flash erase | 在 KEILDebug页取消勾选Reset and Run,先执行Erase再Load;或检查Flash Download中算法是否匹配 C8T6 |
4.5.1 验证烧录成功的黄金三步
- 串口监听:用 XCOM 或 Tera Term 连接
USART1(PA9/PA10),波特率 115200,上电后应收到Soil Humidity: XX%循环输出; - LED 状态:干燥土壤下红灯灭、绿灯亮;湿度 >85% 时红灯亮、蜂鸣器响;
- 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) {;},所有业务逻辑由中断驱动——这才是裸机开发的正确范式。”
本文还有配套的精品资源,点击获取