
简介基于单片机的智能花盆设计源码包是一套完整的毕业设计参考资料面向单片机初学者、嵌入式开发者及需要完成课设/毕设的学生解决自动浇水、环境监测等植物养护智能化问题涵盖硬件、软件到实物调试的完整流程。压缩包共72个文件约200.7MB主体为C语言源码、H头文件、Keil工程文件及Hex烧录文件另含原理图与PCB设计文档、系统框图、元器件清单Excel、程序烧录与CH340驱动安装说明、实物与原理图视频讲解等类型覆盖开发、调试、学习全环节。已有46人学习。资源内按“实物视频讲解—原理图PCB—实物程序—程序烧录—主要模块文档”等模块组织目录并提供了主要模块文档资料包方便对照排查问题本地编译可运行环境配置后即可上手适合作为完整项目方案参考。1. 基于单片机的智能花盆第一次做翻车的往往不是电路而是状态逻辑一盆绿萝在办公桌上从绿变黄多数时候不是因为买花的人懒而是因为“每天浇一次”这种固定节奏和花盆里真实的土壤含水率从来不在一条线上。基于单片机的智能花盆设计就是把“该不该浇水”从一个模糊的经验判断变成一个可以用采集、阈值、执行三个环节闭环的工程问题传感器读出土壤湿度主控芯片比较阈值驱动水泵决定浇多少秒。这个项目之所以在毕业设计和课程设计里高频出现是因为它同时踩中了 ADC 采集、单总线时序、定时器调度、电机驱动和低功耗这几块单片机开发的必修内容一块 STC 或者 STM32 芯片就能全部覆盖。我在帮人审这类源码时最常见的翻车点不在传感器选型也不在最简单的“湿度低就浇水”逻辑而是忘了处理三个边界传感器断电后重新上电的抖动状态、水泵启动瞬间拉低主控电压导致的复位、以及土壤湿度在浇完水后的两分钟里数值先冲高再回落的假信号。这篇文就从系统架构开始把采集、控制、显示、调试到接入 Modbus 的完整路径走一遍中间给的代码都是可以直接烧进裸机工程的写法。2. 传感器选型和系统架构先定“感知-判断-执行”三环再写代码智能花盆的硬件拓扑并不复杂但元器件选型决定了后续代码的复杂程度。我的经验是先把系统切成三个环节感知环负责湿度和温度采集判断环在主控内部完成阈值比较和状态切换执行环驱动水泵和显示。任何一个安装在阳台或室内的花盆都可以用这套框架去拆需求。2.1 土壤湿度传感器选型电阻式还是电容式市场上最容易买到的土壤湿度传感器是 YL-69 和配套的 LM393 比较器模块。这类电阻式传感器通过两根裸露的金属探针插入土壤利用土壤含水量不同导致电阻值变化来输出电压。它的优点是便宜、接口简单模块自带一个电位器可以调阈值直接输出数字量也可以跳线改成模拟量输出。缺点也很明显金属探针长时间通电会发生电解腐蚀在偏酸性土壤里尤其严重而且探针表面一旦结垢测量值会持续漂移。电容式传感器比如常见的手工 PCB 版本通过测量土壤介电常数变化来反映含水量探针表面有绝缘涂层不会被电解腐蚀。它的价格大约是电阻式的三到五倍但稳定性和寿命明显更好适合要做长期数据记录的项目。如果你是做毕设且后续要写论文里的“实验数据”章节建议优先选电容式因为连续七天采集的数据曲线不会每天往下掉。2.2 主控选型STC89C52 与 STM32F103 的分界线主控芯片的选择不需要追逐最新的 ARM 内核反而应该看手里外设够不够用。STC89C52 是 51 内核的老将片内没有 ADC只能用 LM393 模块输出的数字电平来判断“干了还是湿了”做不了细粒度湿度百分比。它的优势是教学资源极大随便搜“51单片机毕业设计”都能找到同型号的参考电路图烧录用串口就能完成不需要额外下载器。STM32F103C8T6 则自带 12 位逐次逼近型 ADC、多个定时器和 USART可以直接采集模拟量输出型传感器的电压并精确定时控制水泵后续如果接 ESP8266 做联网也方便。如果只做一个最小功能的“干湿判断 浇水”STC89C52 够用只要你打算显示湿度百分比或者预留联网能力直接上 STM32F103 省掉二次返工。2.3 系统架构与引脚分配表以 STM32F103C8T6 为例我一般会把引脚按下面这张表示意分配。注意水泵驱动不要直接接在 MCU 引脚上必须经过 ULN2003 达林顿管或者 L9110S 电机驱动模块原因是单片机的 GPIO 输出电流只有 8mA 左右而 5V 微型潜水泵的启动电流可以达到 300mA 甚至更高直接驱动会把引脚烧坏。功能模块引脚说明土壤湿度传感器模拟输出PA0ADC1_CH0采集电压 0~3.3VDHT11 温湿度传感器PB0单总线数据线需 4.7kΩ 上拉LCD1602 数据/控制引脚PB1~PB78 位数据线加 RS/RW/EN水泵控制经 L9110SPA2PWM通过 PWM 占空比调节浇水强度供水水位检测可选PA3数字量输入低电平无水状态 LEDPA4指示浇灌状态这张表有一个容易被忽略的设计点土壤湿度传感器的模拟输出在 3.3V 供电时输出电压范围是 0~3.3V而 STM32 的 ADC 参考电压也是 3.3V所以可以直接采集。如果你用的是 5V 供电的传感器模块输出高电平会接近 5V直接进 PA0 会把 ADC 采样保持电容充坏需要加一个分压电阻网络把电压降到 3.3V 以内。3. 从原理图到 C 代码智能花盆的采集与浇灌逻辑拿到原理图之后最忌讳的是上来就写主函数循环体。我习惯按照“采集驱动 → 控制逻辑 → 人机交互”三层往下落代码每一层单独用一个 .c 文件组织这样后续加功能时不用把全部逻辑翻一遍。3.1 数据采集层ADC 采样与 DHT11 时序土壤湿度的模拟量读取在 STM32 的 HAL 库里非常直接但要注意两步一是开启 ADC 校准二是对连续多次采样取平均减少传感器探头接触不均带来的随机抖动。#include adc.h #include dht11.h // 3.3V供电ADC分辨率为12位 // 标定空气中读取约2600浸入水中读取约1200 uint16_t soil_adc_read(void) { uint16_t sum 0; for (uint8_t i 0; i 16; i) { HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { sum HAL_ADC_GetValue(hadc1); } } return sum / 16; // 16次平均滤掉接触噪声 } // 将ADC原始值映射为土壤湿度百分比0~100 uint8_t soil_moisture_percent(void) { uint16_t raw soil_adc_read(); const uint16_t DRY_VALUE 2600; const uint16_t WET_VALUE 1200; if (raw DRY_VALUE) return 0; if (raw WET_VALUE) return 100; // 线性映射到百分比 return (uint8_t)((uint32_t)(DRY_VALUE - raw) * 100 / (DRY_VALUE - WET_VALUE)); }这段代码里的 16 次采样取平均是必要的因为土壤探针插入的角度和压实程度会导致每次接触电阻略有差异单次采样会在某个数值附近跳动 ±50 左右。DRY_VALUE 和 WET_VALUE 这两个常数不能写死要用实际测试得到的值替换具体标定方法在第四章里展开。ADC 的采样时间参数也要留意在 CubeMX 里把采样周期设置为 239.5 周期以上保证传感器内阻与采样电容的充电时间匹配否则读取值会系统性偏低。DHT11 的读取是另一个容易卡住的环节。它的单总线协议要求主机先拉低 18ms 发起启动信号然后释放总线等待从机响应接下来按 bit 读取 40 位数据。每一位的“0”或“1”是通过高电平持续时间区分的26~28μs 为逻辑 070μs 左右为逻辑 1。// 延时函数基于DWT或系统滴答精确到微秒 static void delay_us(uint32_t us) { ... } uint8_t dht11_read_data(uint8_t *humidity, uint8_t *temperature) { uint8_t data[5] {0}; uint8_t i, j; // 主机拉低总线至少18ms触发从机应答 GPIO_InitTypeDef gpio {0}; gpio.Mode GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(DHT11_PORT, gpio); HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); HAL_Delay(20); HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); delay_us(30); // 切换为输入模式等待从机拉低应答 gpio.Mode GPIO_MODE_INPUT; gpio.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_PORT, gpio); if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET) { // 等待80us低电平结束后再等待80us高电平 while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET); for (i 0; i 5; i) { for (j 0; j 8; j) { while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET); delay_us(40); // 采样点选在高电平40us处 if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { data[i] | (0x80 j); while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET); } } } // 校验字节data[4]应等于前四个字节之和的低8位 if ((uint8_t)(data[0] data[1] data[2] data[3]) data[4]) { *humidity data[0]; *temperature data[2]; return 0; // 成功 } } return 1; // 通信失败 }代码里最关键的是那个 40μs 的延迟采样点。DHT11 输出的高电平短则 26μs 长则 70μs如果在高电平开始后的 40μs 时刻读引脚逻辑 0 已经结束变成低电平逻辑 1 仍然保持高电平这样就能准确区分。实际测试中常见的错误是采样点太早在 30μs 左右读部分逻辑 0 的尾巴还挂着高电平导致数据位误判。3.2 控制层阈值判断与水泵驱动采集到湿度百分比之后最简单的控制策略就是“低于阈值就浇高于阈值就停”。但在真实花盆里这个策略需要加两个约束最小浇灌间隔和浇灌后的抑制时间。如果每分钟都在检测刚浇完水土壤湿度还没扩散均匀传感器可能仍然读到低值水泵就会反复启停既浪费水又可能把花根泡烂。typedef struct { uint8_t moisture_threshold; // 浇水阈值 uint8_t moisture_sensor; // 当前湿度百分比 uint16_t pump_duration_sec; // 单次浇灌时长 uint16_t suppress_time_min; // 浇灌后抑制时间 uint32_t last_water_time; // 上次浇灌的时间戳分钟 } smart_pot_t; smart_pot_t pot { .moisture_threshold 30, .pump_duration_sec 8, .suppress_time_min 30, .last_water_time 0 }; void smart_pot_task_run(void) { uint32_t now_min HAL_GetTick() / 60000; uint8_t m soil_moisture_percent(); // 自动模式湿度低于阈值且距离上次浇水超过抑制时间 if (m pot.moisture_threshold (now_min - pot.last_water_time) pot.suppress_time_min) { water_pump_on(pot.pump_duration_sec); pot.last_water_time now_min; } } void water_pump_on(uint16_t seconds) { // 水位检测无水时禁止干烧 if (HAL_GPIO_ReadPin(WATER_LEVEL_PORT, WATER_LEVEL_PIN) GPIO_PIN_RESET) { pump_set_buzzer(1); // 低电平表示无水报警 return; } HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, 500); // 50% 占空比 HAL_Delay(seconds * 1000); HAL_TIM_PWM_Stop(htim2, TIM_CHANNEL_1); }这里有两个参数值得细说。抑制时间 suppress_time_min 我通常设为 30 分钟原因是土壤是个多孔介质液态水从表层渗透到传感器所在深度需要时间通常 10 到 30 分钟才能达到水分分布相对均匀的状态。占空比 50% 是给直流微型水泵预留的软启动裕量如果直接以 100% 占空比启动电机瞬间电流可能达到稳态电流的 5 倍在 USB 供电场景下会把 5V 电压拉低到 4.2V导致 STM32 掉电复位。更稳妥的做法是把占空比从 0 逐步爬升到目标值也就是软件软启动但 8 秒的浇水窗口内爬升时间不宜超过 1 秒否则水量不够。3.3 显示层LCD1602 还是 OLED显示模块的选择会影响代码里调试信息的输出效率。LCD1602 用 8 根数据线加 3 根控制线占用引脚多但显示内容在阳光下可读性强。OLED 用 I2C 接口只占两根线能同时显示湿度、温度和泵状态但驱动库的体积比 LCD1602 大不少。毕设场景我倾向 OLED因为 I2C 总线上还可以挂 BH1750 光照传感器一个接口解决两个外设的扩展问题而且后续接 ESP8266 做物联网网关时I2C 外设可以原样复用。参考下面这段用 HAL 库驱动 I2C OLED 显示最小例程#include ssd1306.h void display_panel_refresh(void) { uint8_t m soil_moisture_percent(); char line1[16], line2[16]; snprintf(line1, sizeof(line1), Moist:%3d%%, m); snprintf(line2, sizeof(line2), Temp:%2dC Humi:%2d%%, dht11_temp, dht11_humi); ssd1306_SetCursor(0, 0); ssd1306_WriteString(line1, Font_6x8, White); ssd1306_SetCursor(0, 2); ssd1306_WriteString(line2, Font_6x8, White); ssd1306_UpdateScreen(); }注意这里的 ssd1306_WriteString 是常见的开源驱动库函数移植时只需要把底层的 I2C 发送函数替换成你项目里的 HAL_I2C_Mem_Write 调用其余的 Bresenham 画点、字符表都不用改。OLED 刷新率不需要太高每秒刷新一次已经足够因为土壤湿度的变化尺度是分钟级的过频繁的刷新反而会在 I2C 总线上产生无意义的占用挤占后续 Modbus 通信的带宽。4. 调试与参数设定湿度阈值标定、电源噪声与故障定位系统能跑起来和能稳定跑是两码事。智能花盆在实验室桌面上测试十次有九次正常放到真实阳台环境就出各种怪现象问题的根源往往在标定方法、供电拓扑和传感器引线长度这三个方面。4.1 湿度阈值标定方法把整个系统做好后先用目标植物实际生长的土壤来做标定而不是直接用传感器在空气中测一个值就当作干燥基准。具体操作分四步把传感器插入准备种花的同批次土壤中浇透水等 30 分钟让水分均匀分布读取此时 ADC 值和百分比值记录为湿态基准然后把花盆放置在通风处连续三天不浇水每天定时记录 ADC 值第三取其中一天的数据画出湿度随时间的变化曲线找到“表面干但深层仍湿润”和“整体干透”的分界点这个分界点对应的百分比就是你想要的浇水阈值下限最后在代码里把 threshold 设置成这个分界值加上 5% 的余量避免到达临界值后水分继续下降产生滞后。我在帮别人调试时还会加一个小的打印逻辑用串口每隔一分钟输出当前湿度百分比直接在串口助手里观察标定过程printf(Soil moisture: %d%%, ADC raw: %d\r\n, soil_moisture_percent(), soil_adc_read());这里 printf 的重定向需要把 fputc 重写到 USART2 发送寄存器这是最常用的调试手段没有之一。别依赖 OLED 显示来做标定在连续三天的数据记录里手动按键翻屏看参数是非常低效的做法。4.2 常见故障排查表我整理了一张排查表基本覆盖我在各类源码评审里见到的高频问题。排查顺序遵循“先供电后信号先硬件后软件”的原则。现象可能原因排查步骤ADC 读数恒为 4095ADC 引脚悬空或配置错误万用表测 PA0 对地电压检查 CubeMX 里 ADC 通道是否开了连续采样ADC 读数恒为 0传感器供电脚未接 / 探针短路断开传感器测空载电压检查 GND 回路水泵一转MCU 就复位电源瞬间跌落芯片 BOD 触发在电源端并联 470μF 电解电容改用 USB 外接供电不共用主控稳压器DHT11 一直返回超时总线缺少上拉电阻 / 引脚配置成开漏后无外部上拉检查 PA4.7k 电阻输入模式必须配置为 GPIO_PULLUPLCD 显示乱码引脚接线顺序错误 / 初始化延时不够检查 RS/EN 引脚连接初始化后延时 50ms 再发命令浇完水湿度反而下降传感器在花盆边缘浇水流到另一边把传感器移到浇灌点垂直下方 3cm 处或者参考 3.2 节抑制时间的延长4.3 电源拓扑与水泵干扰电源问题是最隐蔽的坑。如果整个系统由一个 USB 口供电USB 口同时给主控板和 5V 潜水泵供电水泵一转线上噪声会使主控 ADC 采集到的电压值产生约 50 到 80mV 的毛刺折算到湿度百分比上大约是 3% 到 5% 的跳变。解决方法是把电源分成两级5V 输入先经过一个低压差线性稳压器比如 AMS1117-3.3给主控供电水泵直接接在 5V 走线上。两个回路在电源入口处用一颗 10μF 陶瓷电容和一颗 470μF 电解电容做解耦形成从水泵到大电容再进主控的分支结构而不是让水泵和主控抢同一条细线。另一个经验是给传感器单独加一个 MOSFET 开关只在需要采集的 500ms 内给传感器通电其余时间断电。这能彻底消除电阻式传感器探针电解腐蚀的问题同时减少多孔介质中的电极极化效应让读数更稳定。实现上就是把传感器 VCC 接到主控 GPIO 控制的 NPN 三极管或 P-MOS 开关上采集前拉高采集完拉低即可。5. 进阶把智能花盆数据接入 Modbus RTU 做远程采集当单个花盆做好以后下一步自然是从“有人浇水”变成“数据上云”或者在实验室环境里把多个花盆拧成一个集群统一管理。Modbus RTU 是工业场景最通用的协议普通单片机通过 USART 加一个 RS485 收发器譬如 MAX485就能接入 PLC 或上位机。这里不是让你给阳台花盆拉 485 总线而是把 Modbus 从站协议栈嵌进裸机工程用一台网关采集多盆数据为你后续做毕设的系统性展示提供一条可扩展的路径。5.1 Modbus RTU 从站状态机实现Modbus 协议栈的移植不需要引入完整开源库对智能花盆这种从站功能码集中的场景自己实现一个精简状态机更可控。核心是处理接收帧的边界问题RTU 模式帧与帧之间需要有至少 3.5 个字符时间的静默期。在 9600 波特率下1 字节传输时间是 1.04ms3.5 字节即约 3.64ms所以可以用一个定时器中断检查接收间隙。// 简化版 Modbus 从站帧接收状态机 typedef enum { FRAME_IDLE, // 等待第一个字节 FRAME_RECEIVING, // 接收数据中 FRAME_COMPLETE, // 一帧接收完成 FRAME_ERROR // CRC 或长度校验失败 } frame_state_t; frame_state_t mb_state FRAME_IDLE; uint8_t mb_rx_buf[256]; uint8_t mb_rx_len; void modbus_timer_interval_handler(void) // 每 1ms 调用一次 { // 记录当前时间戳供主循环检查 3.5 字符静默 } void modbus_uart_rx_isr(uint8_t byte) { if (mb_state FRAME_IDLE) { mb_rx_len 0; mb_rx_buf[mb_rx_len] byte; mb_state FRAME_RECEIVING; } else if (mb_state FRAME_RECEIVING) { if (mb_rx_len sizeof(mb_rx_buf)) { mb_rx_buf[mb_rx_len] byte; } // 地址匹配判断本机地址 0x01广播地址 0x00 if (mb_rx_len 1 mb_rx_buf[0] ! 0x01 mb_rx_buf[0] ! 0x00) { mb_state FRAME_IDLE; // 不是给本机的帧直接丢弃 } } }帧接收完整后主循环里做 CRC16 校验校验通过则按功能码分发。最常用的功能码是 0x03读保持寄存器和 0x04读输入寄存器我们把湿度、温度、水箱液位和泵运行状态写进这些寄存器地址上位机就能以标准 Modbus 报文读取。// 寄存器地址映射表 // 40001: 土壤湿度百分比 (0-100) // 40002: 环境温度 (整数部分) // 40003: 环境湿度 (整数部分) // 40004: 泵状态 (0停止 1运行) void modbus_handle_read_input_regs(uint16_t start_addr, uint16_t reg_count) { // 从内部变量表读取并填充响应缓冲区 for (uint16_t i 0; i reg_count; i) { switch (start_addr i) { case 0x0000: mb_holding_regs[i] soil_moisture_percent(); break; case 0x0001: mb_holding_regs[i] dht11_temp; break; case 0x0002: mb_holding_regs[i] dht11_humi; break; case 0x0003: mb_holding_regs[i] pump_status; break; default: break; } } // 组帧发送省略CRC16计算实现 }5.2 远程监控与上位机对接如果不想走 485 物理链路也可以把花盆的 USART 直接接到 WiFi 模块ESP8266上在局域网内通过 TCP 转发 Modbus RTU 帧。这时要注意一个实际参数Modbus RTU 的字节超时是 1.5 字符时间TCP 转发会产生网络延迟和粘包问题需要在主控侧把帧接收的超时裕量放大到 20ms 以上同时依赖 CRC 来丢弃非法帧。用现成的 Modbus 调试工具例如 Modbus Poll连接时从站地址、波特率和校验方式三者必须完全一致。我一般把从站地址设为 0x01波特率 9600无校验数据位 8停止位 1。这样一组参数在 RS485 总线上的最大可靠传输距离能达到 100 米以上足够覆盖一栋教学楼的多个花盆节点。上位机每 10 秒轮询一次所有从站的花盆数据在界面上画出湿度趋势曲线你就能直观看到浇灌策略是否合理。最后一个工程细节上位机轮询时会把从站地址广播出去所以每台花盆的从站地址必须用拨码开关错开从 1 编到 N不然两台回包就会在总线上冲突。本文还有配套的精品资源点击获取