
简介这套资源是基于STM32F103C8T6与ZigBeeCC2530的智能大棚环境监测系统完整源码面向物联网、嵌入式或农业自动化方向的开发者与学生解决大棚内温度、光照、土壤湿度多参数实时采集与无线传输问题。压缩包共37个文件以16个C源文件、13个头文件为主另含Makefile、链接脚本、启动文件及说明文档整体仅79KB目录按主机端与从机端清晰划分便于定位模块与二次开发。系统设计兼顾工程性与教学性主机端完成多传感器数据采集与ZigBee组网发送从机端通过LCD1602实时显示并附带断链检测、蜂鸣器告警源码采用标准库与HAL混合编程包含DMA、中断驱动、环形缓冲区和看门狗机制等典型嵌入式写法对学习STM32外设驱动、通信协议解析及电源设计均有较好参考价值。目前已有36人学习适合需要完整项目源码用于课程设计、毕业设计或从事智能农业开发的技术人员使用。 搞嵌入式这几年陆陆续续做了不少项目但像 STM32 智能大棚监测系统这样既适合练手、又能直接落地到实际场景的还真不多见。前阵子帮朋友弄了一套从传感器选型到上位机看数据整个链路走下来踩了不少坑也沉淀了不少经验。今天就把这套系统的源码拆解和实操过程整理出来给正在做类似项目的同学一个参考。先说清楚这套 STM32 智能大棚监测系统到底能干什么它通过 STM32 单片机采集大棚内的空气温湿度、土壤湿度、光照强度等关键环境参数数据实时显示在本地屏幕上同时通过串口或无线模块上传到上位机一旦某项参数超过预设阈值系统会触发蜂鸣器报警或自动控制排风扇、补光灯、水泵等执行设备。这套系统的价值在于它把数据采集—数据处理—逻辑判断—设备控制整条链路都打通了既不是那种只测个温度就完事的玩具项目也没有复杂到让人无从下手。适合正在学 STM32 的嵌入式爱好者、做毕业设计的大学生以及真正有小型种植棚管理需求的人参考。1. 项目整体设计与方案选型1.1 为什么选择 STM32 做控制核心大棚监测这类项目市面上也有用 Arduino 或者 ESP8266 做的但我个人还是推荐 STM32。原因很直接STM32 的资源丰富程度和实时处理能力能让你在这个项目上舒舒服服地做加法。后面想加个 PID 控温、加个 SD 卡存储历史数据、加个 Wi-Fi 模块连云端STM32 的引脚、定时器、DMA、中断都能扛得住不用换主控重写代码。我这次用的具体型号是 STM32F103C8T6也就是大家常说的蓝丸核心板。这颗芯片 72MHz 主频64KB Flash20KB RAM资源不算夸张但应对大棚监测场景绰绰有余。更重要的是它的资料极其丰富HAL 库和标准库都支持网上随便一搜就是一堆例程哪怕你之前只点过 LED也能快速上手。提示如果你手头是 STM32F407 或者 STM32G431 这类板子这套系统的代码逻辑完全一样只需要根据芯片修改启动文件和时钟配置传感器驱动部分可以直接复用。1.2 系统架构与功能模块划分动手写代码之前我习惯先画一张功能模块图把系统拆清楚。这套大棚监测系统的整体架构可以分成四个层次感知层负责采集环境数据包括 DHT11 空气温湿度传感器、土壤湿度传感器电容式、光敏电阻模块。控制层STM32 主控芯片负责所有数据的汇聚、处理和逻辑判断是整个系统的大脑。执行层包括蜂鸣器报警模块、继电器控制的排风扇/水泵/补光灯。交互层OLED 液晶屏显示实时数据串口/USB-TTL 将数据上报到电脑上位机。这种分层设计的好处是每一层的功能相对独立日后你想替换某个传感器或者增加执行设备只需要改动对应模块的驱动代码不需要把整个工程推倒重来。这也是为什么我一直强调嵌入式项目不要一上来就写 main 函数先把结构理清楚后面能省一半的调试时间。1.3 通信方案与数据链路设计大棚监测系统的数据链路说白了就是采集端到展示端怎么走。最简单的方案是 STM32 通过串口直接连电脑上位机但考虑到实际大棚场景里摄像头和主控之间往往有一段距离我的方案是STM32 把数据通过串口发给一个带 Wi-Fi 透传的模块比如 ESP8266再由 ESP8266 通过局域网把数据推送到上位机软件或云平台。两种通信方案的对比方案优点缺点适用场景有线串口直连稳定、延迟低、实现简单布线麻烦距离受限实验室调试、小型种植箱无线 Wi-Fi 透传部署灵活、可远程监控依赖网络、延迟略高大型大棚、远程管理场景我这套系统里把两种方案都兼容了默认情况下数据走串口输出如果检测到 ESP8266 模块在线则同时走 Wi-Fi 上报。这样在实验室调试时用串口看数据到了现场部署就切到无线灵活性高不少。2. 核心硬件选型与电路设计要点2.1 传感器选型对比与参数解读传感器是整个系统的数据源选型直接决定了监测数据的准确性和系统的稳定性。这部分我踩过不少坑把几个关键传感器的选型经验分享出来。空气温湿度传感器我选了 DHT11价格便宜、库成熟、三根线就能连。它的精度是 ±2℃ 和 ±5%RH量程 0~50℃、20%~90%RH对于大棚环境监测来说完全够用。如果项目预算充足也可以换 DHT22AM2302精度能提升到 ±0.5℃ 和 ±2%RH代价是价格贵了三倍且时序要求更严格。注意DHT11 的采样间隔要求大于 1 秒。我在代码里设置每次读取完成后延时 1.5s 再读下一次这个时间参数必须严格保证否则读回来的数据会一直是上一次的缓存值看起来像卡死了。土壤湿度传感器这里我强烈建议选电容式不要选电阻式叉状探针那种。电容式传感器表面有防腐蚀涂层寿命长得多而电阻式用不了几个月铜箔就会被电解腐蚀读数越来越飘。我用的型号是电容式土壤湿度模块输出模拟电压接 STM32 的 ADC 引脚采样。光照传感器最简单的方案是光敏电阻模块输出数字量DO或模拟量AO成本几块钱能实现天黑自动开补光灯这种逻辑。如果你需要精确的照度值做研究可以换成 BH1750I2C 接口能直接读出 lux 数值代码稍微复杂一点但数据会专业很多。2.2 硬件接口与电气连接这部分我把整套系统的接线表整理出来照着接就行不用再翻数据手册一个个对引脚。模块接口类型STM32 引脚供电电压OLED 显示屏 (I2C)I2CPB8 (SCL), PB9 (SDA)3.3VDHT11 温湿度单总线PB123.3V土壤湿度传感器ADCPA03.3V光敏电阻模块ADCPA13.3V蜂鸣器GPIO 输出PB03.3V继电器 (水泵/风扇)GPIO 输出PB13.3VESP8266 透传串口PA9 (TX), PA10 (RX)3.3V有几个地方需要特别注意供电问题ESP8266 的峰值电流可以达到 300mA如果直接从 STM32 核心板的 3.3V 引脚取电经常会造成电压跌落导致单片机复位。我实际测试中甚至出现了一启动 Wi-Fi 连接、OLED 就黑屏的诡异现象后来排查发现就是供电不足。正确做法是给 ESP8266 单独供电用 AMS1117 稳压模块从 5V 转 3.3V或者用带外部电源供电的 ESP8266 底板。信号电平匹配STM32 的引脚是 3.3V 电平继电器模块如果是 5V 供电控制脚虽然兼容 3.3V 逻辑但最好在控制线上串联一个 1kΩ 电阻防止灌电流过大损坏引脚。ADC 输入范围STM32 的 ADC 输入范围是 0~3.3V而有些土壤湿度模块在供电 5V 时输出也到 5V。所以模块供电建议统一用 3.3V不然直接采样会有烧毁 ADC 引脚的风险。2.3 电源方案与功耗考量大棚里供电条件参差不齐系统电源一定要做好冗余设计。我的方案是外部 12V 电源适配器→LM2596 降压模块→输出 5V→一路给继电器和 DHT11 供电另一路经 AMS1117 降为 3.3V 给 STM32 和传感器、Wi-Fi 模块供电。功耗方面实测下来系统整机电流大概 250mA 左右最大头是 ESP8266 和 OLEDSTM32 本身只有几十毫安。如果后期想增加电池供电做无线采集节点建议把系统切到低功耗模式待机模式 RTC 定时唤醒可以把整机电流压到 10mA 以下但这个优化会带来比较大的软件改动前期先不用考虑把功能跑通再说。3. 软件框架与源码核心逻辑解析3.1 工程结构与代码组织方式拿到源码后我先带大家看懂工程的目录结构。这套代码我习惯用驱动层 应用层的方式组织也是我在企业里做项目的标准做法SmartGreenhouse/ ├── Core/ │ ├── Inc/ # 头文件 │ └── Src/ # main.c、系统时钟配置等 ├── Drivers/ │ ├── bsp_dht11.c # DHT11 单总线驱动 │ ├── bsp_adc.c # ADC 采样驱动 │ ├── bsp_oled.c # OLED 显示驱动 │ └── bsp_uart.c # 串口与 ESP8266 通信驱动 ├── App/ │ ├── app_monitor.c # 主业务逻辑采集、控制、报警 │ └── app_uart_protocol.c # 自定义串口通信协议 └── MDK-ARM/ # Keil 工程文件分层的好处是驱动层的代码基本是写一次到处复用比如 DHT11 的驱动我换了三个项目都在用连改都不用改。应用层则专注业务逻辑把采集到的数据拿去做判断、控制、上报代码看起来清爽排查问题也不会在几百行的驱动堆里找 bug。3.2 传感器采集驱动的关键实现DHT11 的驱动是这套系统里最有技术含量的部分因为它是单总线协议时序要求非常严格。标准的读取时序是这样的主机拉低总线至少 18ms然后释放。DHT11 响应先拉低 80us再拉高 80us。随后每位数据通过高低电平的宽度区分26~28us 的高电平表示070us 左右的高电平表示1。这段代码直接用 GPIO 翻转加延时实现核心部分如下uint8_t DHT11_ReadByte(void) { uint8_t i, data 0; for (i 0; i 8; i) { while (DHT11_IN_Read() 0); // 等待低电平结束 delay_us(40); if (DHT11_IN_Read() 1) // 40us后仍是高电平说明是1 { data | (1 (7 - i)); while (DHT11_IN_Read() 1); // 等待高电平结束 } } return data; }这里有个关键点读取1的时候延时 40us 这个值不能随便改。我最早参考的例程延时用的 30us读取结果经常是一位数据错乱温度直接从 25.5 度跳到了 85 度排查了好久最后用逻辑分析仪抓波形才发现是延时偏短把1误判成了0。实操心得DHT11 这种单总线设备时钟精度要求很高。如果你用内部 RC 振荡器HSI而不是外部晶振HSE延时函数实际延时会比标称值偏差 1%~3%在高低温差大的环境下可能触发时序问题。建议项目一开始就配置好外部 8MHz 晶振并且用定时器做微秒级延时而不是软件空循环——我在工程里就是用 TIM2 做了一个毫秒级和微秒级的延时函数库。3.3 数据处理与联动控制策略原始数据采回来以后不能直接用要做滤波处理。传感器偶发的毛刺信号会让系统误报警这在大棚里是很烦人的事情。我的做法是每次采集连续取 5 次数据去掉最大值和最小值剩下 3 个取平均值这种中值平均滤波逻辑写起来简单效果却非常好。温度数据偶尔跳变的问题基本消失了。float Sensor_Filter(float *buf, uint8_t len) { float sum 0, max buf[0], min buf[0]; uint8_t i; for (i 0; i len; i) { if (buf[i] max) max buf[i]; if (buf[i] min) min buf[i]; sum buf[i]; } sum sum - max - min; return sum / (len - 2); }联动控制策略是整个系统的灵魂。我只写了最实用的一套逻辑温度高于 35℃ 时打开风扇低于 20℃ 时关闭风扇并打开加热器通过继电器控制土壤湿度过低时打开水泵补水 10 秒后自动停止光照低于阈值时打开补光灯。这些阈值都定义在代码顶部的宏里方便现场调试时修改#define TEMP_FAN_ON_THRESHOLD 35.0f // 温度高于此值开启风扇 #define TEMP_FAN_OFF_THRESHOLD 28.0f // 温度低于此值关闭风扇 #define SOIL_HUMIDITY_LOW 25.0f // 土壤湿度低于此值开启水泵 #define LIGHT_LOW_THRESHOLD 200 // 光照低于此值开启补光灯注意温控这里我特意做了滞回控制也就是开启阈值和关闭阈值不一样。如果不加滞回风扇会在 35℃ 附近反复开关继电器一天能啪嗒啪嗒响几百次触点寿命堪忧。这个细节看似简单却是很多新手容易忽略的。3.4 串口协议与数据上报实现为了实现上位机对数据的可靠解析我自定义了一个简单的帧格式帧头(0xAA) 数据长度 数据ID 数据负载 CRC8校验 帧尾(0x55)这套协议在 app_uart_protocol.c 中实现封装了打包和解析函数。发送端每 2 秒发送一次完整数据帧包含温度、湿度、土壤湿度、光照值和设备状态。上位机收到帧后先校验 CRC校验通过才解析数据这样能避免传输过程中的噪声导致误码。CRC8 的代码实现不复杂但手写容易出错直接用一个现成的查表版本就行uint8_t CRC8_Calc(uint8_t *buf, uint8_t len) { uint8_t crc 0x00; while (len--) { crc ^ *buf; for (uint8_t i 0; i 8; i) { if (crc 0x80) crc (crc 1) ^ 0x07; else crc 1; } } return crc; }4. 实操过程与核心步骤记录4.1 开发环境搭建与工程配置源码用的是 HAL 库加 STM32CubeMX 生成框架开发环境是 Keil MDK 5。我强烈建议你先用 CubeMX 生成底层初始化代码再往里面添加业务逻辑这样时钟树、GPIO 初始化、外设配置都是 CubeMX 帮你算好的能少写几百行配置代码也基本不会出现时钟配置错误导致的芯片锁死。CubeMX 里的配置项我直接把这次项目的关键参数列出来外设关键配置参数值RCCHSE 外部晶振8MHzADC1采样分辨率12 位ADC1采样通道PA0, PA1USART1波特率115200I2C1标准模式100kHzTIM2定时器时钟72MHz有一个配置细节值得注意ADC 的采样时间我设置成了 55.5 个周期而不是默认的 1.5 个周期。因为土壤湿度传感器的输出阻抗比较高如果采样时间太短ADC 内部的采样电容充不满测出来的电压值会偏低且不稳定。把采样时间拉长后读数稳定度肉眼可见地提升了。4.2 源码烧录与运行验证编译工程前记得在 Keil 的 Options for Target 里选择正确的芯片型号STM32F103C8并在 Debug 选项卡里配置好 ST-Link。烧录步骤很简单用 ST-Link 连接 SWDIO、SWCLK、GND 三个引脚点击 Load 按钮进度条跑完就说明烧录成功。踩坑提醒STM32F103C8T6 的核心板如果你之前烧录过别的程序导致芯片锁死常见表现是下载时提示 RDDI-DAP Error先在 ST-Link Utility 里执行 Connect under reset再执行 Full chip erase就能把芯片恢复正常。这个操作救了我好几块板子。烧录完成后系统默认会自动开始采集和显示。OLED 屏幕上第一行显示大气温度第二行显示大气湿度第三行显示土壤湿度和光照值第四行显示设备状态风扇/水泵/补光灯是否开启。串口助手那边以 115200 波特率连接可以看到每 2 秒输出一条完整的数据帧。4.3 关键参数计算与调试记录ADC 采集的原始值是 0~4095 的整数需要转换成实际的物理量。我在调试时遇到一个典型问题土壤湿度模块在干燥空气中的输出是 2.9V完全泡在水里是 1.6V电压和湿度成反比且不是完全线性的。所以不能直接用线性公式换算成百分之多少。我在代码里做了标定先记录干燥时的 ADC 值对应湿度 0%和泡水时的 ADC 值对应湿度 100%然后用差值做比例映射uint8_t calc_soil_humidity(uint16_t adc_value) { #define SOIL_DRY_ADC 3480 // 干燥实测 ADC 值 #define SOIL_WET_ADC 1920 // 泡水实测 ADC 值 float percent (float)(SOIL_DRY_ADC - adc_value) * 100.0f / (SOIL_DRY_ADC - SOIL_WET_ADC); if (percent 0) percent 0; if (percent 100) percent 100; return (uint8_t)percent; }这套代码在实际调试时要注意不同批次电容式传感器的输出电压值会有差异所以不能直接照搬我的标定值一定要在实际环境中重新标定。具体做法是拿传感器分别放在干燥土壤和饱和泥浆里读取两个 ADC 值填入宏定义中。每台设备唯一标定是这种低成本传感器保证精度的重要一步很多人忽略这个导致数据偏差非常大。5. 常见问题与排查技巧实录5.1 传感器数据异常类问题现象一DHT11 读取数据一直不变或者偶发超时这个问题的根源大半出在时序上。排查步骤先确认两次读取间隔是否大于 1s其次检查微秒延时函数是否准这里用逻辑分析仪或示波器看总线波形最直观最后看代码里有没有中断处理打断 DHT11 的读时序——DHT11 的位读取最长时间只有 70us 左右如果有 - 个高优先级中断恰好在这个窗口里跑了几十微秒数据必然出错。我的解决方案是读 DHT11 前关闭全局中断读完再开__disable_irq(); DHT11_Read_Data(temp, humi); __enable_irq();现象二ADC 数值跳动大土壤湿度读数忽高忽低先检查传感器的电源是否干净。如果系统里继电器动作时 ADC 读数发生突变那几乎可以肯定是电源噪声干扰。解决办法ADC 采样结果做软件滤波前面讲的中值平均滤波硬件上给传感器的供电脚加一颗 10uF 和 100nF 的去耦电容继电器供电和传感器供电在降压模块输出端要分离走线。5.2 通信与显示类问题现象三OLED 屏幕不亮或花屏绝大多数情况是 I2C 地址不对。我的 OLED 模块地址是 0x787 位地址为 0x3C但市面上有些模块是 0x7A。排查方法写一个扫描程序逐地址发送 I2C 探测命令看哪个地址有 ACK 应答一目了然。另外 OLED 的 SDA/SCL 如果和别的 I2C 设备共用总线要注意总线地址冲突最好加上拉电阻。现象四串口收到乱码先检查波特率是否真的配置成了 115200注意 CubeMX 里 AHBCLK 和 APB1CLK 的配置会影响 USART 波特率的计算。如果代码没有问题换一根 USB-TTL 线试试——我遇到过一次是劣质 CH340 线在高温下时钟漂移导致数据全是乱码换线后正常。5.3 稳定性与抗干扰问题大棚环境里最头疼的就是电磁干扰。继电器通断瞬间产生的电弧和感性负载尖峰很容易让单片机死机或复位。我的处理方案有几个层面软件看门狗开启 IWDG独立看门狗喂狗间隔设为 500ms即使程序跑飞也能自动复位恢复。继电器驱动隔离使用带光耦隔离的继电器模块控制端和输出端完全隔离防止感性负载反向电动势冲击芯片。电源入口防反接和 TVS 管在 12V 电源入口串联防反接二极管并联 TVS 管吸收瞬态尖峰。实操心得我曾经在测试时没有接光耦隔离直接用一个 5V 继电器模块去控制水泵结果每次继电器吸合的瞬间OLED 都会闪一下、串口数据也会丢一帧。加了光耦隔离并调整了继电器驱动引脚和 ADC 采样引脚的距离后问题彻底解决。实验室里看着正常一到工业现场就抽风——嵌入式开发中 99% 的诡异问题都出在电源和干扰上这句话一点不夸张。5.4 控制逻辑相关的调试经验最后一类问题是用户反馈风扇该开的时候不开之类的逻辑问题。排查这类问题我的经验是先打印传感器原始值确认数据源是否正确再检查判断条件里的变量类型特别是 ADC 值和阈值类型不一致导致的隐式转换问题。比如我早期写代码时土壤湿度阈值定义成了 uint8_t而计算出来的湿度值也是 uint8_t两者比较没问题但光照值我用了 float 和 int 直接比较编译器 warning 没注意结果比较结果始终是 false补光灯永远不亮。这类低级 bug 检查起来非常费时间所以我在工程里开启了警告视为错误的编译选项把所有潜在类型问题扼杀在编译阶段。还有一次用户说温度到了 37 度风扇不转我远程看日志发现问题出在滞回控制逻辑上——风扇在第一次达到 35 度时已经开启了但用户在 34 度时测试风扇自然是关闭的因为要等温度降到 28 度以下才会关。这个设计是刻意的但用户不理解后来我把设备状态直接显示在 OLED 和上位机上用户看到风扇已开启的状态提示后就再没有类似的疑问了。6. 从源码到落地的延伸思路这套源码其实只是整个系统的底座往上可以做很多扩展。如果你的大棚面积大、需要监控的点位多可以在同一套 STM32 程序里增加节点编号字段用 RS-485 组网把多个采集节点连起来每个节点一个地址上位机轮询各节点数据。需要远程监控的生产型大棚可以把 ESP8266 的链路换成 4G 模块或者 LoRa 网关数据直接推到云平台人在家里就能看到大棚的情况。我自己下一步的计划是给这套系统接一个小型 WiFi 摄像头模组把图像数据和环境数据一起上报做一个环境监测 图像采集的综合节点。STM32 的外设资源完全够这样折腾代码框架也允许这样继续生长。如果你拿到了源码建议不要只停留在点亮板子、看几个传感器数据就收工试着改一改报警阈值加一路传感器或者自己定义一个串口命令来控制水泵——这些折腾出来的功夫才是这个项目真正值钱的地方。本文还有配套的精品资源点击获取