每年到了十一二月到次年春天这段时间,来问我"STM32的智慧农业项目怎么做"的人就明显多起来——大部分是物联网专业的学生,手里攥着一个毕设选题,时间只剩三四个月,心里没底。这个题目的好处在于它把嵌入式开发、传感器采集、物联网联网、云端可视化这几件事全串到了一起,认真做完一套下来,能写进简历的东西远比"基于单片机的简易温度计"要多。麻烦也在这儿:模块一多,任何一个环节出问题都能让整个系统趴窝。这篇我按自己经手和帮人调试过的若干版实际情况,把基于STM32的智慧农业项目从选型、写代码、联网、踩坑排查,一直到学习路线和文档材料结构,一条线讲透,源码工程也会给出可以直接照着搭的骨架。
1. 先想清楚这套系统最终长什么样
1.1 智慧农业项目的最小可用闭环
很多人一上来就打开淘宝搜"智慧农业套件",看到几十个传感器就懵了。先把闭环想清楚,再谈器件。一个能在答辩现场站得住脚的智慧农业系统,本质上是三件事的循环:感知—决策—执行。
感知是采数据:土壤湿度、空气温湿度、光照强度,条件允许再加个二氧化碳浓度或者水位。决策是判断:土壤湿度低于阈值就浇水,温度超过上限就通风或开风扇,光照不足就补光。执行是动作:继电器吸合带动水泵、风扇、补光灯,同时把状态回传给屏幕和云端。
这条链子跑通,就是最小可用闭环。答辩老师最看重的往往不是你堆了多少传感器,而是这个闭环的逻辑是否自洽:阈值怎么定的,采集多久一次,执行之后有没有反馈确认。
提示:先做加法再做减法。一开始功能列全,然后在时间预算里砍到 3 到 4 个采集量 + 2 个执行动作,这个规模刚好能在三周内调通,剩下的时间留给联网和文档。
1.2 别被"智慧"两个字吓到,拆成三层就好理解
把整套系统横向切成三层,后面所有工作都能挂到这棵树上,脑子就不会乱。
| 层级 | 职责 | 典型器件/技术 |
|---|---|---|
| 感知执行层 | 采集物理量、驱动负载 | 土壤湿度、DHT22、BH1750、继电器、水泵 |
| 主控决策层 | 数据滤波、阈值判断、状态机 | STM32F103、裸机或FreeRTOS |
| 联网应用层 | 数据上云、远程控制、可视化 | ESP8266/ESP32、MQTT、物联网云平台 |
分层的好处是排故时能快速定位:数据不对,先怀疑感知层;逻辑不对,看主控;数据上不去,查联网层。我见过太多人把三层搅在一起调试,最后连"到底是传感器坏了还是串口发错了"都分不清。
1.3 功能清单和取舍原则
给一份我在实际项目里常用的目标功能清单,你可以按时间和能力打钩:
- 必做:土壤湿度采集 + 自动/手动浇水、空气温湿度显示、OLED 本地显示、按键设置阈值。
- 加分:光照采集 + 补光、数据上云 + 手机端查看、历史曲线、超限报警。
- 炫技(时间充裕再碰):摄像头、太阳能供电、多节点组网。
取舍原则只有一条:凡是不能在答辩现场稳定演示三遍的功能,一律砍掉。演示翻车一次,前面做得再好也会打折扣。
2. 主控选型的真相:为什么STM32依然是稳妥解
2.1 STM32、ESP32、Arduino 三条路线的实测对比
这几年 ESP32 很火,很多同学纠结要不要直接上 ESP32 省掉联网模块。我的判断是:看你的培养方案和答辩侧重。三条路线的差异我列一下:
| 维度 | STM32F103 | ESP32 | Arduino UNO |
|---|---|---|---|
| 主频 | 72MHz | 240MHz 双核 | 16MHz |
| 联网 | 需外挂模块 | 板载 WiFi/蓝牙 | 需外挂模块 |
| 外设丰富度 | 高(多ADC/定时器/USART) | 高 | 低 |
| 学习曲线 | 较陡 | 中等 | 平缓 |
| 毕设"专业感" | 高 | 中 | 低 |
如果你的专业方向偏嵌入式、课程里讲过 STM32,那就用 STM32。它对 GPIO、ADC、定时器、串口这些底层外设的掌控感是毕设答辩里能讲出深度的地方——比如你能说清 ADC 的采样时间怎么设、DMA 怎么搬运数据、定时器中断多久触发一次。这些细节用 Arduino 很难讲出层次。
ESP32 适合"想省事又要联网"的同学,但很多老师会觉得它偏应用、不够底层。折中方案是 STM32 主控 + ESP8266 联网,既有底层可讲,又满足物联网属性,也是这个选题最主流的组合。
2.2 具体型号怎么选:C8T6 还是 RCT6
型号上别纠结太久,给几条实用建议:
- STM32F103C8T6(蓝板):最便宜、资料最多、够用。64KB Flash、20KB RAM,跑这套系统绰绰有余。缺点是引脚少,IO 紧张时要规划。
- STM32F103RCT6 / ZET6:引脚多、Flash 大,适合要接 LCD 彩屏或多个传感器的情况,正点原子/野火的板子多用这个。
- STM32F407:性能强、有 FPU,但对这个项目属于杀鸡用牛刀,价格和复杂度都上去了,不推荐作为首选。
一个坑:C8T6 的 ADC 通道和某些定时器通道会跟复用引脚冲突,如果你的传感器多,画板或接线前一定先画一张引脚分配表,把每个外设占用的引脚、是否复用冲突列清楚,能省掉大量返工。
2.3 外设资源够不够,动手前先算一笔账
以 C8T6 为例,假设我们要接:土壤湿度(ADC1_CH0)、DHT22(一个普通 GPIO)、BH1750(I2C1)、OLED(I2C1 共用)、ESP8266(USART2)、按键(3 个 GPIO)、继电器(2 个 GPIO)。算下来:ADC 用 1 路,I2C 一组总线挂两个从机没问题,USART 占用一组,GPIO 用了不到 10 个。资源完全够。
真正容易忽略的是定时器:你需要一个定时器做周期采样(比如 1 秒一次),一个定时器给 DHT22 做微秒级延时,一个给串口超时判断用。STM32F103 有 4 个通用定时器加 2 个高级定时器,够分。但如果同时要 PWM 调光、要输入捕获测频率,就得重新规划。动手前把"每个定时器干什么"写下来,比边写边抢要省心得多。
3. 传感器与执行器选型:从土壤湿度到水泵继电器
3.1 土壤湿度传感器,电阻式和电容式到底差在哪
新手最常见的坑就在这。市面上两种:
- 电阻式(两根探针那种,YL-69):便宜,但探针是裸露金属,通电后会电解腐蚀,用几周读数就飘,还容易受土壤盐分影响。作为演示用可以,长期用不推荐。
- 电容式(v1.2 那种,表面有阻焊层):不直接接触土壤电解质,抗腐蚀,读数更稳,价格贵一点点。做毕设我强烈建议用电容式。
接口上有模拟量输出和数字量输出两种。数字量输出(DO)是靠电位器调阈值的比较器,精度差,只适合"湿/干"二值判断。要做连续的湿度百分比,必须用模拟量(AO)接 STM32 的 ADC。这一点很多人踩过:接了 DO 口发现只能出 0 和 1,还以为是程序问题。
标定方法我一般这么做:把探针完全插进干燥的土里记一个 ADC 值记为 dry,插进浇透水的土里记一个 wet,然后线性映射:
// 假设 dry=2800, wet=1200(12位ADC,数值越小越湿,视具体模块而定) uint8_t soil_percent(uint16_t adc) { int32_t p = (int32_t)(2800 - adc) * 100 / (2800 - 1200); if (p < 0) p = 0; if (p > 100) p = 100; return (uint8_t)p; }不同批次的模块这两个端点值差很多,务必自己实测,别抄网上的数。
3.2 温湿度传感器的取舍
DHT11 便宜但精度低(湿度 ±5%、温度 ±2℃),DHT22(AM2302)精度好很多。两者都是单总线时序,DHT22 对时序要求更严格。如果你对精度没底、又怕单总线调不通,可以考虑 SHT30 或 AHT20,走 I2C,协议简单稳定,性价比现在也很高。
DHT 系列最大的问题是时序敏感:读的时候要关中断,否则一次中断就能把时序打乱,返回全 0 或校验错。我一般把读取函数里的中断临时关掉,读完再开,并对失败做重试(连续失败三次再报错,不要一失败就报警,容易误报)。
3.3 光照、CO2、水位这些加分项
- 光照:BH1750 走 I2C,直接出 lux 数值,比光敏电阻省事,精度也够。
- CO2:MH-Z19 走串口,价格偏高,做加分项可以,注意它需要预热。
- 水位:超声波 HC-SR04 测水箱液位,记得加温度补偿,否则夏天冬天差挺多。
这些都不必全上,选一到两个和你的应用场景强相关的即可。比如做温室补光,光照就是必做;做鱼菜共生,水位和溶氧才有意义。功能和场景要能对上话,答辩时才讲得圆。
3.4 执行器:继电器、水泵、补光灯
执行侧核心是继电器。选型看两点:线圈电压(5V 最常见)和触点容量(水泵启动瞬间电流大,选 10A 触点更稳)。驱动上,单片机的 IO 直接驱动继电器线圈是带不动的,必须加驱动。常见方案:
- 三极管(S8050)+ 续流二极管:最省成本。
- 光耦隔离模块:抗干扰好,推荐。
- ULN2003 这类达林顿阵列:一个芯片驱动多路,IO 不够时特别香。
注意:继电器线圈断电瞬间会产生反向电动势,续流二极管不能省,否则这个尖峰可能直接把三极管甚至单片机 IO 打坏。
水泵建议用小功率的直流水泵(5V/12V),别上 220V 交流水泵——实验室环境里安全风险大,答辩老师也不建议。补光用 LED 灯带,配合 MOSFET 做 PWM 调光,比继电器开关更有"智慧农业"的味道。
3.5 供电,最容易被忽视的重灾区
一整套系统同时工作时的电流:STM32 约 50mA,ESP8266 峰值能到 300mA,继电器吸合约 70mA,水泵 12V 那边几百 mA。如果全用 USB 或开发板上的 3.3V 供电,水泵一动电压就塌,单片机复位。
正确做法是分开供电:单片机和控制逻辑一路 3.3V/5V,水泵和继电器驱动一路,两路共地。用一块 12V 电源经 DC-DC 降压给逻辑部分,12V 直接给水泵。这个问题我在第 6 节会展开讲,因为它是"莫名其妙的复位"最常见的元凶。
4. 代码怎么写才不乱:驱动分层与工程结构
4.1 目录结构先定下来
裸机项目最容易写成"一个 main.c 塞八百行"。我建议从第一天就按层分目录:
Project/ ├── Core/ // 启动文件、main.c、中断 ├── Drivers/ │ ├── bsp_gpio.c │ ├── bsp_adc.c │ ├── bsp_usart.c │ ├── bsp_i2c.c │ └── bsp_timer.c ├── Devices/ │ ├── dht22.c │ ├── soil.c │ ├── bh1750.c │ └── oled.c ├── App/ │ ├── app_sensor.c // 采集与滤波 │ ├── app_ctrl.c // 决策与执行 │ └── app_mqtt.c // 联网 └── Middlewares/ └── cJSON/ // 拼 JSON 用这样分的好处:Devices层只关心"怎么从传感器拿数",App层只关心"拿到数做什么"。换传感器只改 Devices,不动逻辑;改策略只动 App,不动底层。
4.2 驱动层用 HAL 还是寄存器
HAL 库开发快,CubeMX 生成一堆初始化代码,适合赶时间的中等项目;寄存器版更能体现功底,但调试慢。我的建议是主控初始化用 CubeMX 生成 HAL,关键外设(比如 ADC 采样、串口中断、DHT 时序)直接操作寄存器或写裸机函数,这样既不慢,又能在答辩里讲出底层细节。
比如读取 DHT22 这种微秒级时序,用 HAL 的HAL_Delay根本不行,得自己写基于DWT或SysTick的微秒延时:
// 基于 DWT 的微秒延时(Cortex-M3/M4 可用) void delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000); while ((DWT->CYCCNT - start) < ticks); }用 DWT 之前别忘了在初始化里使能:先开DEMCR的 TRCENA,再清DWT->CYCCNT,最后置CTRL的 CYCCNTENA。
4.3 定时器与任务调度,别用 delay 卡死
新手最爱在 main 循环里一堆HAL_Delay(1000),结果按键不灵、串口收不全。正确做法是用一个 1ms 的 SysTick 或定时器中断做一个软件时基,主循环里比较时间戳触发任务:
static uint32_t last_sensor = 0; void loop(void) { if (tick_now() - last_sensor >= 1000) { last_sensor = tick_now(); app_sensor_poll(); // 每秒采一次 } app_ctrl_poll(); // 高频轮询,做按键和状态机 app_mqtt_poll(); // 处理串口收发 }这套"时间片轮询"结构简单、够用,还能为后面上 FreeRTOS 打基础。真要上 RTOS,把每个 poll 变成一个任务,配上互斥锁就行。
4.4 ADC 采样一定要做滤波
土壤湿度、光敏这类模拟量,单次采样抖得厉害。我一般采 8 到 16 次,去极值再求平均:
uint16_t adc_filter(uint8_t ch, uint8_t times) { uint32_t sum = 0; uint16_t min = 0xFFFF, max = 0; for (uint8_t i = 0; i < times; i++) { uint16_t v = adc_read(ch); if (v < min) min = v; if (v > max) max = v; sum += v; } sum = sum - min - max; // 去掉一个最大一个最小 return sum / (times - 2); }如果采样量大,用 DMA + 定时器触发自动搬运,CPU 几乎不参与,这个技巧在答辩里很加分。
4.5 用状态机管执行逻辑
浇水这块别写成if (soil < 30) 开泵; if (soil > 60) 关泵;这么写容易出现"来回抖"——湿度在阈值附近反复开关泵。用状态机加滞回就稳了:
| 状态 | 进入条件 | 动作 | 退出条件 |
|---|---|---|---|
| IDLE | 土壤湿度 ≥ 下限 | 关泵 | 湿度 < 下限 |
| WATERING | 湿度 < 下限 | 开泵 | 湿度 > 上限 或 超时 |
| LOCKED | 连续浇水超时 | 关泵报警 | 手动复位 |
上限、下限之间留一段间隔(比如下限 30%、上限 50%),就是滞回,避免频繁启停。再加一个"单次浇水最长 60 秒"的超时保护,防止传感器故障导致泵一直开着。
5. 数据上网:STM32 加 ESP8266 接物联网平台
5.1 为什么用 AT 指令的 ESP8266
STM32 联网主流方案是 ESP8266 模块跑 AT 固件,STM32 通过串口发 AT 指令控制它。虽然要解析字符串有点烦,但逻辑清晰、调试可见(串口助手能直接看指令往来),非常适合毕设。ESP8266 一般默认固件就带 MQTT AT 指令集,不用自己刷。
替代方案是 STM32 自己跑 TCP + 移植 MQTT 协议栈,工作量大得多,不建议在这个时间预算下折腾。
5.2 串口通信最容易出的问题
STM32 和 ESP8266 之间是串口,几个高频坑:
- 波特率不匹配:ESP8266 默认 115200,STM32 也要设 115200,别一个 9600 一个 115200。
- 电平:ESP8266 是 3.3V 逻辑,别接 5V 串口,会烧。
- 收发缓冲:ESP8266 返回的数据是多帧、不定长的,用中断 + 环形缓冲区接收,再在主循环里找关键字符串(比如
+IPD、OK、ERROR),别用阻塞式HAL_UART_Receive等固定长度。 - AT 指令要带回车换行:
AT\r\n,很多人漏了\r\n,模块当没收到。
5.3 MQTT 的接入核心,其实就四步
MQTT 是发布/订阅模型,接入任何物联网云平台的流程都大同小异:
- 配 WiFi:
AT+CWMODE=1设 STA 模式,AT+CWJAP="ssid","pwd"连热点。 - 配 MQTT:设置客户端 ID、用户名、密码(一般平台的三元组),连接服务器。
- 订阅控制主题,收到下发的指令就解析并执行。
- 定时把你的传感器数据发布到上报主题。
云平台一般会给你一个设备三元组(ProductKey、DeviceName、DeviceSecret)和一对主题(上报 topic / 下发 topic)。上报的数据用 JSON 拼,比如:
// 用 cJSON 拼一个上报报文 cJSON *root = cJSON_CreateObject(); cJSON_AddNumberToObject(root, "temp", 25.6); cJSON_AddNumberToObject(root, "humi", 48); cJSON_AddNumberToObject(root, "soil", 37); cJSON_AddNumberToObject(root, "lux", 320); char *payload = cJSON_PrintUnformatted(root); mqtt_publish(TOPIC_REPORT, payload); cJSON_Delete(root); free(payload);云端配好数据解析脚本,就能在手机或网页上看曲线、下发开关指令。
5.4 断网重连必须做,否则演示必挂
实验室的网络说断就断。你的程序必须能检测连接状态并自动重连,思路是:
- 每次上报前判断
mqtt_connected标志。 - 断线时按指数退避重连:1 秒、2 秒、4 秒、8 秒……别死循环刷 AT 指令,那样模块会被指令淹没。
- 重连成功后重新订阅主题——很多人忘了重订阅,结果重连上了收不到控制指令。
本地逻辑(自动浇水、本地显示)要在联网失败时照常工作,做到"联网是增强,不是依赖"。答辩现场哪怕没网,系统也能演,这点极重要。
6. 那些让我熬夜的坑,完整排查链路
6.1 土壤湿度读数乱飘,先别急着换传感器
现象:OLED 上湿度忽高忽低,浇水时更明显。
我的排查顺序是:先看供电,再看采样,最后看传感器本体。实测发现大半是 ADC 参考电压不稳或采样被干扰。具体做法:
- 用万用表量传感器 AO 输出,判断是传感器本身飘还是 ADC 采样问题。如果 AO 稳定而 ADC 读数飘,问题在采样。
- 检查 ADC 采样时间,太长容易被干扰,太短采不准,先设 55.5 个周期试试。
- 加去极值滤波(见 4.4)。
- 最后才换电容式传感器。
这个顺序能避免你一上来就买新传感器结果发现白花钱。
6.2 水泵一开水就复位,元凶是共地供电
现象:手动开泵,单片机立刻重启,屏幕上电复位。试过换电源、加电容都没根治。
根因:泵和单片机共用了同一路 5V,泵启动瞬间电流冲击把电压拉到 4V 以下,触发 BOR(掉电复位)。解决办法就一句话——动力电和控制电分开,两路共地。12V 给泵,经 DC-DC 降到 5V/3.3V 给逻辑,再在逻辑电源入口并一个 100uF 电解加一个 0.1uF 陶瓷电容。改完再没复位过。
6.3 串口打印乱码,往往是时钟没配
Keil 里看到全是乱码,第一个怀疑波特率,但波特率没错。这时候去看系统时钟树:CubeMX 里如果外部晶振设成了 HSE 但板上没焊晶振(或者焊的是 8M 你写成了 12M),主频就是错的,所有基于主频的波特率自然全错。检查SystemCoreClock的实际值是不是你以为的那个数。
6.4 程序跑飞、进 HardFault,抓住这三点
跑飞往往是数组越界、指针空引用、或者栈溢出。
- 首先查数组边界,尤其是接收缓冲区下标。
- 查
printf到串口的重定向,如果没关半主机模式会卡死。 - 用 Keil 的Call Stack + Locals窗口看死在哪,配合 HardFault 处理函数里打印
LR、PC值定位。
一个习惯:把所有可能越界的拷贝换成带长度检查的版本,宁可慢一点也别让程序飞。
7. 从零到毕设:学习路线和时间倒推
7.1 给零基础同学的一条线
如果你之前只学过 C 语言,没碰过单片机,按这个顺序走:
| 阶段 | 内容 | 建议时间 |
|---|---|---|
| 1 | GPIO 点灯、按键、流水灯 | 3~5 天 |
| 2 | 串口收发、printf 重定向 | 3 天 |
| 3 | 定时器中断、PWM | 4 天 |
| 4 | ADC 采集 | 3 天 |
| 5 | I2C 驱动 OLED、读传感器 | 5 天 |
| 6 | 单总线 DHT22 时序 | 4 天 |
| 7 | ESP8266 AT 指令联网 | 5 天 |
| 8 | MQTT 接入云平台 | 5 天 |
| 9 | 整体联调、外壳、文档 | 7 天 |
加起来大约一个半月到两个月。别跳步,前四步是所有后续的基础。
7.2 有基础的同学直接进项目
如果玩过 51 或者 Arduino,可以直接从第 5 阶段切入,前面用两三天复习 HAL 库就够。重点把时间花在联网和整体联调上,这两块最容易卡壳。
7.3 文档材料怎么组织,答辩才有料
很多人代码调通了,文档却写不出来。我一般按这几块组织材料:
- 原理图 + 接线表:每个引脚接什么,一张表说清。
- 系统框图 + 程序流程图:三层结构画出来。
- 通信协议说明:上报 JSON 字段含义、下发指令格式。
- 测试记录:阈值测试、浇水响应时间、联网成功率,用数据说话。
- 遇到的问题与解决:把第 6 节那些坑整理进去,这是最能体现你动手能力的地方。
提示:答辩 PPT 里放一段实物演示视频,比放十张截图都管用。演示前把水泵、传感器都预演三遍。
8. 关于配套源码和代码复用的实在建议
拿到或整理出一份"配套源码"之后,最忌讳的是照抄不理解。我的用法是:先跑通官方例程,再逐个模块替换成自己的实现。比如直接烧一份能联网的例程,确认硬件没问题,然后一个文件一个文件地读,把不明白的地方标出来查手册。
一个工程能不能复用,看三点:驱动层是否和硬件解耦(换引脚只改一个头文件)、协议层是否配置化(三元组、主题名集中放宏定义里)、是否有 README 说明每个文件的职责。满足这三点,这份代码你带到下一个项目里也能接着用。
我个人在带的几届学生里发现,最后答辩拿高分的那几个,都不是功能最多的,而是系统最稳、文档最细、能把自己踩的坑讲成故事的那几个。智慧农业这个题目本身不难,难的是把采集、决策、执行、联网这条链子磨到不掉链子。把第 6 节那些坑一个个填平,你的项目就立住了。