简介:本资源是一套基于STM32F1系列的蔬菜大棚智能环境控制系统完整开发包,面向嵌入式初学者、课程设计学生及农业物联网实践者,解决温湿度与土壤墒情协同监测与自动调控的实际工程问题。压缩包共100个文件,含Keil工程核心文件(uvprojx/uvoptx)、C/H源码(11个.c、10个.h)、编译中间文件(.o/.d/.axf/.hex)、OLED/DHT11/ADC/继电器等模块驱动代码(预览可见dht11.__i、oled.__i、adc.__i等),以及PDF说明文档和DOCX设计报告,整体11.74MB,结构清晰、模块解耦度高。已有632人学习下载,资料覆盖从硬件选型(含模块型号与采购链接)、接线图、阈值按键设置逻辑到风扇/灌溉双路自动控制策略,配套文档详述程序设计思路与调试要点,开箱即用——按说明购置DHT11、土壤ADC传感器、5V风扇、抽水电机及OLED屏后,烧录代码即可实现温湿监测、土壤缺水自动浇水、超温自动通风与本地数据显示全流程功能。
1. 这不是又一个“点亮LED”的STM32例程:它是一套可直接部署到真实大棚的温湿度闭环控制系统
你手头这份基于STM32的蔬菜大棚温湿度智能控制系统设计-源代码.zip,本质是面向农业物联网场景落地的完整嵌入式工程,不是教学Demo,也不是仿真验证。它用 STM32F103C8T6(常见“蓝 pill”核心板)作为主控,把 DHT11 温湿度、土壤 ADC 采样、继电器驱动风扇/水泵、OLED 实时显示、按键阈值设定全部串成一条可运行的控制链路。系统在无上位机、无网络模块、不依赖云平台的前提下,仅靠本地传感器输入和预设逻辑,就能完成“检测→判断→执行→反馈”的闭环动作:当土壤 ADC 值低于用户通过 KEY 设置的阈值,灌溉电机立即启动;当 DHT11 返回温度 >32℃,通风风扇自动吸风降温。所有模块均采用嘉立创可采购型号(文档中明确列出 SKU 和接线图),代码已适配 Keil MDK-ARM v5.37+,编译后.axf文件可直接烧录至 ST-Link V2。适合农科院技术员快速搭建试验棚、高校电子类毕业设计实物验证、或嵌入式工程师复现低成本环境控制方案——它解决的不是“怎么让单片机跑起来”,而是“怎么让单片机在潮湿、多尘、电压不稳的棚内连续稳定工作72小时以上”。
2. 从硬件信号链到软件状态机:理解DHT11与土壤ADC的协同采集逻辑
2.1 DHT11协议解析与STM32 GPIO模拟时序的关键约束
DHT11 是单总线数字传感器,不支持标准 I²C/SPI,必须由 MCU 软件模拟时序。本工程使用dht11.c中的DHT11_Read_Data()函数实现完整通信流程:主机拉低 ≥18ms 启动信号 → 拉高 20–40μs 等待响应 → 传感器拉低 80μs 表示存在 → 拉高 80μs 后发送 40bit 数据(8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和)。关键点在于:所有延时必须用delay_us()级别精度实现,且不能被中断打断。工程中delay.c的delay_us()基于 SysTick 定时器,配置为 72MHz 系统时钟下每 1us 计数 1 次(SysTick_Config(SystemCoreClock / 1000000)),确保for(i=0;i<10;i++)类循环能精确对应微秒级等待。
提示:若实测读取失败率高,优先检查
DHT11_IO_IN()和DHT11_IO_OUT()宏定义是否正确映射到实际引脚(默认为 PB12),并确认该引脚未被其他外设(如 USART1_TX)复用。DHT11 对电源噪声敏感,建议在传感器 VDD 与 GND 间并联 100nF 陶瓷电容。
2.2 土壤湿度ADC采样:为何不用内部参考电压而选VREF+
土壤湿度模块本质是电阻分压式传感器(湿土阻值低,干土阻值高),输出模拟电压接入 STM32 的 ADC1_IN0(PA0)。本工程未使用默认的 VDDA 作为参考电压,而是在adc.c初始化中显式调用ADC_TempSensorVrefintCmd(ENABLE)并设置ADC_ExternalTrigConvCmd(ADC1, ENABLE),但真正起作用的是ADC_DeInit(ADC1)后重新配置的ADC_InitTypeDef结构体:
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; 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_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); // 关键:选择 VREF+ 作为参考电压,非 VDDA RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADCCLK = 72MHz/6 = 12MHz ADC_TempSensorVrefintCmd(ENABLE); // 启用内部参考通道(非必须,但文档要求) ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55_5Cycles); // PA0注意:VREF+ 引脚(PA0 复用功能)需外接 3.3V 稳压源(如 AMS1117-3.3 输出),否则 ADC 读数漂移严重。实测中,当 VREF+ 波动 ±50mV,土壤 ADC 值变化可达 120 码(12-bit 分辨率下满量程 4095),直接影响灌溉触发阈值准确性。
2.3 温湿度与土壤数据融合:状态机驱动的双阈值决策模型
系统未采用简单 if-else 判断,而是构建了control_state_t枚举状态机,在main.c的while(1)主循环中每 2 秒执行一次:
typedef enum { CONTROL_IDLE, CONTROL_FAN_ON, CONTROL_FAN_OFF, CONTROL_IRRIGATE_ON, CONTROL_IRRIGATE_OFF, CONTROL_ALARM } control_state_t; static control_state_t current_state = CONTROL_IDLE; static uint16_t last_fan_time = 0; static uint16_t last_irrigate_time = 0; void control_task(void) { static uint32_t last_exec_ms = 0; if (millis() - last_exec_ms < 2000) return; // 2s 周期 last_exec_ms = millis(); uint8_t temp = dht11_data.temp; uint8_t humi = dht11_data.humi; uint16_t soil_adc = get_soil_moisture(); // 返回 0~4095 switch(current_state) { case CONTROL_IDLE: if (temp > TEMP_THRESHOLD && !fan_status) { current_state = CONTROL_FAN_ON; relay_fan_on(); last_fan_time = millis(); } else if (soil_adc < SOIL_THRESHOLD && !irrigate_status) { current_state = CONTROL_IRRIGATE_ON; relay_irrigate_on(); last_irrigate_time = millis(); } break; case CONTROL_FAN_ON: if (temp <= TEMP_THRESHOLD - 2) { // 回差2℃防抖 current_state = CONTROL_FAN_OFF; relay_fan_off(); } break; // ... 其他状态分支 } }该设计引入回差(Hysteresis)机制:风扇启动阈值为 32℃,但关闭阈值设为 30℃;灌溉启动阈值为土壤 ADC=1800,关闭阈值为 2200。避免因传感器噪声导致继电器频繁吸合,延长设备寿命。
3. OLED显示与按键交互:如何让农业用户无需看代码也能调整参数
3.1 SSD1306驱动的内存映射优化:减少刷新延迟提升可读性
OLED 模块采用 SSD1306 控制器,分辨率为 128×64。工程中oled.c未使用逐像素写入,而是开辟了u8 OLED_GRAM[128][8]的显存数组(8 行 × 128 列,每字节表示 8 行垂直像素),所有文本/图标绘制先写入该数组,再一次性调用OLED_WR_Byte()发送整屏数据。关键优化在于OLED_Display_On()函数中禁用了屏幕滚动:
// 屏幕初始化时关闭滚动,避免文字闪烁 OLED_WR_Byte(0x2E, OLED_CMD); // 关闭滚动 OLED_WR_Byte(0x26, OLED_CMD); // 水平向左滚动 OLED_WR_Byte(0x00, OLED_CMD); // 滚动起始页 0 OLED_WR_Byte(0x00, OLED_CMD); // 滚动间隔时间(0帧) OLED_WR_Byte(0x07, OLED_CMD); // 滚动结束页 7 OLED_WR_Byte(0x07, OLED_CMD); // 滚动起始页 7 OLED_WR_Byte(0xFF, OLED_CMD); // 滚动持续时间(无限) OLED_WR_Byte(0x2F, OLED_CMD); // 开启滚动 // → 替换为: OLED_WR_Byte(0x2E, OLED_CMD); // 显式关闭滚动(即使默认关闭也重置)提示:若 OLED 显示乱码或部分区域不亮,检查
OLED_IIC_Init()中 SCL/SDA 引脚是否与硬件一致(默认 PB6/PB7),并确认 I²C 时钟频率设为 100kHz(I2C_InitTypeDef.I2C_ClockSpeed = 100000)。SSD1306 对时序敏感,过高的速率会导致 ACK 失败。
3.2 按键阈值设置:长按+短按组合操作的防误触实现
系统提供 3 个独立按键(KEY0/KEY1/KEY2),分别对应“进入设置”、“数值+1”、“数值-1”。阈值修改流程为:长按 KEY0(≥2s)进入设置模式 → OLED 显示当前温度阈值(如TEMP:32℃)→ 短按 KEY1/KEY2 修改数值 → 再次长按 KEY0 保存并退出。防误触逻辑在key.c的KEY_Scan()中实现:
#define KEY_LONG_PRESS_MS 2000 uint32_t key0_press_start = 0; uint8_t key0_long_pressed = 0; u8 KEY_Scan(u8 mode) { static u8 key_up = 1; if(mode == 1) key_up = 1; if(key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0)) { delay_ms(10); // 消抖 if(KEY0 == 0) { key0_press_start = millis(); while(KEY0 == 0) { // 等待释放 if(millis() - key0_press_start >= KEY_LONG_PRESS_MS) { key0_long_pressed = 1; break; } } } key_up = 0; } else if(key_up == 0 && (KEY0 == 1 && KEY1 == 1 && KEY2 == 1)) { key_up = 1; if(key0_long_pressed) { key0_long_pressed = 0; return KEY0_PRES; // 长按事件 } else { return KEY0_CLICK; // 短按事件 } } return 0; }该实现将长按判定与按键释放解耦:只要按下时间超限即标记key0_long_pressed,后续无论是否继续按住,松开时都返回长按事件。避免用户因手抖提前松开导致设置失败。
3.3 参数持久化:利用STM32内置Flash模拟EEPROM存储阈值
温度/土壤阈值需断电保存,工程未外扩 EEPROM,而是使用 STM32F103 的 64KB Flash(地址 0x0800FC00 开始的最后 1KB)模拟。flash.c中FLASH_Write_U16()函数实现擦除+写入:
#define FLASH_SAVE_ADDR 0x0800FC00 void FLASH_Write_U16(u32 addr, u16 data) { FLASH_Unlock(); // 解锁Flash FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); if(FLASH_ErasePage(FLASH_SAVE_ADDR) != FLASH_COMPLETE) return; // 写入2字节(阈值为uint8_t,但按半字对齐) if(FLASH_ProgramHalfWord(addr, data) != FLASH_COMPLETE) return; FLASH_Lock(); // 上锁 } // 读取时直接访问地址 u16 FLASH_Read_U16(u32 addr) { return *(vu16*)addr; }注意:Flash 擦除以页(1KB)为单位,因此每次修改阈值都会擦除整个 1KB 区域。实际应用中应将多个参数(温阈值、土阈值、报警开关等)打包写入同一地址段,减少擦写次数。本工程仅存储两个 uint8_t,故
FLASH_Save_Param()将其合并为((temp_th << 8) | soil_th)写入单个半字。
4. 继电器驱动与电源管理:让5V风扇和12V水泵在STM32 GPIO下安全启停
4.1 光耦隔离电路设计:为什么必须用PC817而非直连MOSFET
通风风扇(5V DC)和灌溉水泵(12V DC)均由继电器模块控制,而继电器线圈驱动采用PC817 光耦 + S8050 NPN 三极管方案(原理图见文档第 7 页)。关键设计点在于:STM32 GPIO(3.3V)无法直接驱动继电器线圈(通常需 5V/10mA),且继电器吸合瞬间的反向电动势会击穿 MCU 引脚。PC817 将控制侧(MCU)与负载侧(继电器)电气隔离,其输入端串联 1kΩ 限流电阻(保证 IF=3.3V/1kΩ≈3.3mA),输出端接 S8050 基极,S8050 集电极接继电器线圈一端,线圈另一端接 5V/12V 电源,发射极接地。S8050 集电极需并联续流二极管(1N4007)吸收线圈断电时的反峰电压。
提示:若继电器吸合无声或不稳定,用万用表测量 PC817 输出端电压——正常吸合时应为 0.2V(饱和导通),若 >1V 则光耦失效或 S8050 β 值不足,需更换为 SS8050(β≥200)。
4.2 电源路径规划:双路供电下的地线干扰抑制
系统同时存在 3.3V(MCU/OLED)、5V(风扇/部分传感器)、12V(水泵)三路电源。文档强调必须采用单点接地(Star Ground):所有电源地线(GND)最终汇聚至 PCB 板上一点,再连接电池负极。禁止将 12V 地与 3.3V 地在模块端子处直接短接。实测中,若忽略此点,OLED 显示会出现横纹干扰,DHT11 读数跳变±5%。原因在于水泵启停时大电流在地线上产生 mV 级压降,叠加到 STM32 的 VSSA(模拟地)上,污染 ADC 参考地。
4.3 继电器控制信号电平匹配:GPIO推挽输出模式配置
控制继电器的 GPIO(如 PA1 控制风扇)必须配置为推挽输出(GPIO_Mode_Out_PP),而非开漏(OD)。因为光耦输入端需要明确的高/低电平:PA1=0 时,PC817 输入端正向导通,继电器吸合;PA1=1 时,输入端截止,继电器释放。若设为开漏,上拉电阻(通常 10kΩ)会导致关断时电压缓慢下降,继电器释放延迟。配置代码如下:
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 必须推挽! GPIO_Init(GPIOA, &GPIO_InitStructure);5. 工程编译与烧录排错:解决“No STM32 target found!”及常见链接错误
5.1 Keil MDK 环境配置:芯片包、调试器与分散加载文件的三重校验
当 Keil 编译后点击 Debug 报错Error: No STM32 target found! If your product embeds debug authentication...,本质是调试器(ST-Link)无法与目标芯片建立 SWD 连接。按以下顺序排查:
- 芯片包版本:Keil 安装
ARM::CMSIS和STMicroelectronics::STM32F1xx_DFP(v2.3.0 或更高),在Project → Options for Target → Device中确认芯片型号为STM32F103C8; - 调试器设置:
Project → Options for Target → Debug → Settings中,Port选SW,SW Device应自动识别为STM32F103C8,若显示Unknown Device,点击Search重刷; - 分散加载文件(Scatter):
Project → Options for Target → Linker → Use Memory Layout from Target Dialog必须勾选,且RO/RW/Stack地址与STM32F103C8的 Flash(0x08000000,64KB)和 RAM(0x20000000,20KB)匹配。本工程STM32F103C8.sct内容为:LR_IROM1 0x08000000 0x00010000 { ; load region size_region ER_IROM1 0x08000000 0x00010000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00005000 { ; RW data .ANY (+RW +ZI) } }
提示:若
Build Output窗口出现L6218E: Undefined symbol xxx,说明函数声明与定义不匹配(如dht11.h声明uint8_t DHT11_Read_Data(void),但dht11.c中定义为void DHT11_Read_Data(void)),需严格核对头文件包含路径(Options for Target → C/C++ → Include Paths添加./USER,./HARDWARE等)。
5.2 ST-Link Utility 烧录失败:时钟配置与NRST引脚的物理检查
若 Keil Debug 失败,改用 ST-Link Utility 烧录.hex文件仍报错,需检查硬件:
- NRST 引脚:确认开发板 NRST 引脚(PA0 复用)未被其他电路拉低。用万用表测 NRST 对 GND 电压,正常待机应为 3.3V,按复位键时应瞬时为 0V;
- 晶振电路:STM32F103 依赖 8MHz 外部晶振(HSE)作为系统时钟源。用示波器测 OSC_IN(PH0)引脚,应有 8MHz 正弦波;若无,检查晶振两端 22pF 负载电容是否焊接良好;
- SWD 接线:ST-Link 的 SWDIO(PA13)、SWCLK(PA14)、GND、3.3V 四线必须与目标板一一对应,禁止接 VCC(5V),否则烧毁 ST-Link。
5.3 运行时异常定位:利用Keil的System Viewer查看寄存器实时状态
系统上电后 OLED 不亮或 DHT11 无响应,可在 Keil Debug 模式下打开View → System Viewer → NVIC,观察EXTI0_IRQn(按键中断)、SysTick_IRQn(毫秒定时)是否使能并计数;打开Peripherals → GPIO查看 PA0(土壤 ADC)、PB12(DHT11)引脚电平是否随操作变化;打开Peripherals → ADC确认ADSTR(转换开始)和EOC(转换结束)标志位是否置位。若EOC常为 0,说明 ADC 未启动,检查ADC_Cmd(ADC1, ENABLE)是否执行。
注意:若
SysTick_Handler()中millis()计数停滞,检查SysTick_Config()返回值是否为1(成功),若为0说明系统时钟未就绪,需确认RCC->CFGR中 PLL 使能位已置位(RCC_PLLCmd(ENABLE))且RCC_WaitForHSEStartUp()返回SUCCESS。
6. 实战部署技巧:在真实大棚环境中提升72小时连续运行稳定性
6.1 传感器防护:DHT11与土壤探针的物理封装方案
DHT11 在大棚高湿(>90% RH)环境下易结露导致读数失真。实测有效方案:将 DHT11 传感器置于带透气孔的塑料盒内(孔径 ≤0.5mm),盒内放置硅胶干燥剂(每 10cm³ 盒体放 1g),盒体用热熔胶密封固定于大棚顶部通风处。土壤湿度探针则需规避化肥腐蚀——采用镀锡铜线替代裸露铜线,探针间距扩大至 5cm,并在探针表面涂覆环氧树脂绝缘层(仅尖端 2mm 裸露)。
6.2 电源冗余设计:锂电池+太阳能板的无缝切换电路
大棚常无市电,推荐采用 12V/7Ah 铅酸电池 + 20W 太阳能板方案。关键在电源管理:使用TP4056充电模块为 3.7V 锂电池充电,再经MT3608升压模块输出 12V 驱动水泵;同时用XL4015降压模块将 12V 转为 5V 供 STM32 和风扇。两路 5V 输出并联前需加肖特基二极管(SS34)防倒灌,确保电池耗尽时太阳能板仍可供电。
6.3 故障自检清单:一份贴在控制箱内的快速排错表
| 现象 | 可能原因 | 快速验证方法 |
|---|---|---|
| OLED 全黑 | ① I²C 通信失败 ② OLED 供电不足(<3.0V) | 用万用表测 VCC-GND;短接 OLED 的 RES 引脚至 GND 1s 复位 |
| DHT11 读数恒为 0xFF | ① PB12 引脚虚焊 ② 传感器损坏 | 用示波器测 PB12 波形,应有 80μs 低电平脉冲;换新 DHT11 测试 |
| 风扇不启动但继电器有声 | ① 继电器触点氧化 ② 风扇电机卡死 | 断电后用万用表通断档测继电器输出端;手动拨动风扇叶片 |
| 土壤 ADC 值始终为 0 | ① PA0 引脚短路到 GND ② VREF+ 未接入 | 测 PA0 对 GND 电压(应为 0~3.3V);测 VREF+ 对 GND 是否为 3.3V |
将此表打印后贴于控制箱内侧,农技员无需懂代码即可完成 80% 现场故障定位。
本文还有配套的精品资源,点击获取