最近在帮学弟学妹们看单片机毕业设计,发现“智能农田灌溉”这个选题真是经久不衰。想法都很好,但交上去的作品往往问题一堆:传感器数据飘忽不定、电池用两天就没电、手机App根本连不上……答辩现场直接翻车。今天,我就结合自己用STM32做的一个实际项目,把从硬件选型到代码调试的完整流程和避坑经验梳理出来,希望能帮你稳稳拿下毕业设计。

智能灌溉系统示意图

1. 毕业设计常见“翻车点”剖析

很多同学的项目停留在“点灯”阶段,一上实际场景就崩。主要问题集中在三个方面:

  • 传感器数据“跳舞”:这是最大的坑。直接用淘宝十几块的土壤湿度传感器,不校准、不滤波,在干燥和湿润状态下读数可能只差几十个ADC值,环境温度一变,数据全乱。答辩老师稍微问一句“你怎么保证数据准确性”,就哑火了。

  • 电池续航“见光死”:系统设计时只考虑功能,不考虑功耗。主控单片机一直全速运行,传感器持续供电,无线模块不断发射。一个号称“低功耗”的系统,用2000mAh的电池可能撑不了一个星期,这在实际农田场景中毫无意义。

  • 通信“随缘”:为了图方便,直接用蓝牙或者Wi-Fi,传输距离不到十米。稍微远点的农田中间,数据就传不回来了。或者协议设计脆弱,偶尔丢一两个数据包,整个系统状态就错乱了,远程控制失灵。

2. MCU选型:STM32、51、ESP32,农田场景该选谁?

这是项目开始的第一步,选错了后面全是坑。

  • 经典51单片机:优点是简单、便宜、资料多。但强烈不推荐用于毕业设计。原因很简单:性能孱弱,难以处理多传感器数据融合和复杂的通信协议;几乎没有原生低功耗模式支持,做“低功耗”设计等于自找麻烦;外设资源少,扩展能力差。用它来做,大概率只能实现一个非常基础的、连线复杂的演示模型。

  • ESP32系列:优点是集成Wi-Fi和蓝牙,开发简单,适合快速原型。如果你的设计重点是“手机App远程控制”,且农田附近有稳定的Wi-Fi覆盖(这在实际中很少见),可以考虑。但其功耗在持续联网状态下较高,且深度睡眠模式下的外设唤醒能力不如STM32灵活。

  • STM32系列(特别是STM32L系列)这是农田智能灌溉系统的首选。理由如下:

    1. 超低功耗:STM32L0/L4系列专为低功耗设计,提供多种睡眠、停机、待机模式,配合RTC定时唤醒,可以轻松实现uA级平均电流。
    2. 性能与资源平衡:拥有足够的计算能力处理数据滤波、协议封装,ADC精度高,外设丰富(UART, I2C, SPI, DMA等),方便连接各种传感器和通信模块。
    3. 开发生态成熟:HAL库和LL库大大降低了开发难度,调试工具完善,社区资源丰富,遇到问题容易找到解决方案。

结论:对于追求稳定性、低功耗和扩展性的农田毕业设计,STM32L系列是更专业、更容易出彩的选择。

3. 系统架构设计:核心是“低功耗”与“解耦”

一个好的架构是成功的一半。我们的系统不能是一个“大杂烩”,而应该是模块清晰、协同工作的“小分队”。

  • 整体架构:采用“采集-决策-执行-通信”的循环模式。主控(STM32)大部分时间处于深度睡眠。由RTC定时(例如每30分钟)唤醒,唤醒后依次执行:采集土壤湿度、空气温湿度;根据预设阈值和算法决定是否灌溉;控制电磁阀开关;将状态数据通过LoRa发送至远处的网关;最后再次进入深度睡眠。

  • 硬件模块解耦

    • 电源管理模块:独立设计,包含锂电池充电管理、升压/降压电路(为不同模块提供合适电压)、电源开关控制(用MOS管给传感器、LoRa模块单独供电,不用时彻底断电)。
    • 传感器模块:土壤湿度、温湿度传感器通过插接件连接,方便更换和校准。
    • 执行模块:电磁阀驱动电路与主控板隔离,防止感性负载干扰。
    • 通信模块:LoRa模块通过UART与主控连接,协议透明,可替换为其他NB-IoT等模块。
  • 软件驱动分层

    1. 底层驱动层(HAL/LL库):操作具体外设(ADC, I2C, UART, RTC)。
    2. 设备抽象层:封装传感器、执行器、无线模块的读写函数,提供统一的接口(如Soil_GetMoisture()LoRa_SendData())。
    3. 应用逻辑层:实现主状态机、数据滤波算法、灌溉决策逻辑。
    4. 中断服务层:处理RTC唤醒中断、按键中断等。

这种架构的好处是,任何一部分需要修改或调试(比如换一个传感器),影响范围都被限制在很小的范围内,不会牵一发而动全身。

4. 关键代码示例与注释

这里提供几个最核心的代码片段,遵循Clean Code原则,关键处有详细注释。

1. RTC定时唤醒与低功耗管理(main.c 节选)

/* 系统进入深度睡眠,等待RTC或外部中断唤醒 */
void Enter_StopMode(void)
{
    // 1. 关闭所有无需在睡眠中工作的外设时钟
    __HAL_RCC_GPIOA_CLK_DISABLE();
    // ... 关闭其他GPIO组时钟

    // 2. 配置唤醒源为RTC闹钟
    HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0x2000, RTC_WAKEUPCLOCK_RTCCLK_DIV16); // 约30分钟唤醒一次

    // 3. 设置所有未使用的GPIO为模拟输入以省电
    GPIO_Analog_Config();

    // 4. 进入STOP模式(保持SRAM和寄存器内容)
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

    // 5. 唤醒后,系统时钟会重置为HSI,需要重新配置系统时钟
    SystemClock_ReConfig();
}

int main(void)
{
    // 初始化HAL库、系统时钟、GPIO、RTC等
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_RTC_Init();

    // 主循环
    while (1)
    {
        // 执行一次完整的工作任务
        Task_CollectData();   // 采集数据
        Task_Decision();      // 灌溉决策
        Task_ControlValve();  // 控制执行
        Task_SendData();      // 发送数据

        // 任务完成,进入深度睡眠,等待下一次RTC唤醒
        Enter_StopMode();
    }
}

2. 土壤湿度ADC采样与软件滤波(sensor.c 节选)

#define SAMPLE_TIMES 10 // 采样次数,用于中位值平均滤波

/**
  * @brief  获取土壤湿度值(0-100%)
  * @param  None
  * @retval 湿度百分比,或错误码
  */
int8_t Soil_GetMoisture(void)
{
    uint32_t adc_sum = 0;
    uint16_t adc_buffer[SAMPLE_TIMES] = {0};
    uint16_t adc_value_filtered = 0;

    // 1. 给传感器供电(通过MOS管控制)
    HAL_GPIO_WritePin(SENSOR_PWR_GPIO_Port, SENSOR_PWR_Pin, GPIO_PIN_SET);
    HAL_Delay(50); // 等待传感器稳定

    // 2. 多次采样,存入数组
    for (int i = 0; i < SAMPLE_TIMES; i++)
    {
        HAL_ADC_Start(&hadc1);
        if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
        {
            adc_buffer[i] = HAL_ADC_GetValue(&hadc1);
        }
        HAL_Delay(5);
    }
    HAL_ADC_Stop(&hadc1);

    // 3. 关闭传感器电源以省电
    HAL_GPIO_WritePin(SENSOR_PWR_GPIO_Port, SENSOR_PWR_Pin, GPIO_PIN_RESET);

    // 4. 简单冒泡排序,取中位值
    Bubble_Sort(adc_buffer, SAMPLE_TIMES);
    adc_value_filtered = adc_buffer[SAMPLE_TIMES / 2];

    // 5. 将ADC值转换为百分比(需要根据传感器特性和校准结果调整公式)
    // 假设ADC满量程4095对应0%湿度(最湿),1000对应100%湿度(最干)
    int8_t moisture_percent = (int8_t)((adc_value_filtered - 1000) * 100 / (4095 - 1000));
    moisture_percent = 100 - moisture_percent; // 因为ADC值越大越湿

    // 6. 限幅处理,防止超出0-100范围
    if (moisture_percent > 100) moisture_percent = 100;
    if (moisture_percent < 0) moisture_percent = 0;

    return moisture_percent;
}

3. LoRa数据封包与发送(lora.c 节选)

// 自定义一个简单的应用层协议帧结构
typedef struct {
    uint8_t head;        // 帧头,固定0xAA
    uint8_t device_id;   // 设备ID
    int8_t  soil_moisture; // 土壤湿度
    int16_t air_temp;    // 空气温度(放大10倍)
    uint8_t air_humidity; // 空气湿度
    uint8_t valve_status; // 阀门状态
    uint8_t battery_level; // 电池电量百分比
    uint8_t check_sum;   // 校验和(简单求和取低8位)
} Lora_Frame_t;

/**
  * @brief  封装并发送一帧数据
  * @param  data: 包含各类传感器数据的结构体
  * @retval HAL status
  */
HAL_StatusTypeDef LoRa_SendData(Sensor_Data_t *data)
{
    Lora_Frame_t frame = {0};
    uint8_t send_buffer[sizeof(Lora_Frame_t)] = {0};

    // 1. 填充帧数据
    frame.head = 0xAA;
    frame.device_id = DEVICE_ID;
    frame.soil_moisture = data->soil_moisture;
    frame.air_temp = (int16_t)(data->air_temperature * 10); // 温度保留一位小数
    frame.air_humidity = (uint8_t)data->air_humidity;
    frame.valve_status = data->valve_status;
    frame.battery_level = Get_BatteryLevel();

    // 2. 计算校验和
    uint8_t *p = (uint8_t*)&frame;
    uint8_t sum = 0;
    for (int i = 0; i < sizeof(Lora_Frame_t) - 1; i++) // 不包括checksum本身
    {
        sum += p[i];
    }
    frame.check_sum = sum;

    // 3. 拷贝到发送缓冲区
    memcpy(send_buffer, &frame, sizeof(Lora_Frame_t));

    // 4. 给LoRa模块供电并发送
    HAL_GPIO_WritePin(LORA_PWR_GPIO_Port, LORA_PWR_Pin, GPIO_PIN_SET);
    HAL_Delay(100); // 等待模块启动

    // 使用UART发送数据,这里以阻塞式为例,实际建议用DMA+中断
    HAL_UART_Transmit(&huart2, send_buffer, sizeof(Lora_Frame_t), 1000);

    HAL_Delay(50); // 等待发送完成
    HAL_GPIO_WritePin(LORA_PWR_GPIO_Port, LORA_PWR_Pin, GPIO_PIN_RESET); // 关闭模块电源

    return HAL_OK;
}

5. 实测数据:功耗与距离

光说不练假把式,以下是我在项目中的实测数据,供你参考:

  • 功耗测试

    • 工作状态:STM32L051全速运行,所有传感器、LoRa模块供电,瞬时电流约45mA。
    • 发送状态:仅LoRa模块发射时,峰值电流约120mA(持续约1秒)。
    • 睡眠状态:STM32进入Stop模式,仅RTC运行,所有外围电路断电。实测电流 1.8μA
    • 平均电流估算:假设每30分钟唤醒工作10秒(采集+发送),计算平均电流约为:(10s * 45mA + 1s * 120mA) / (1800s) ≈ 0.36mA
    • 续航估算:使用一节2000mAh的18650锂电池,理论续航:2000mAh / 0.36mA ≈ 5555小时,即231天。考虑到电池自放电、电路损耗,实际稳定工作半年以上没问题。
  • 通信距离测试

    • LoRa模块:采用SX1278芯片,功率20dBm,空中速率0.3kbps。
    • 城市环境(有楼房遮挡):稳定传输距离约 800米-1.2公里
    • 郊区农田(视距条件较好):稳定传输距离可达 2公里以上
    • 注意:通信距离受天线、高度、地形影响极大。务必在项目现场进行实地测试,并预留足够的链路裕量。

6. 生产环境“避坑”指南

毕业设计可以“理想化”,但如果你想让作品更贴近实用,甚至为以后工作打基础,这些现实问题必须考虑:

  • 防雷击与防静电:农田空旷,设备易受雷击感应电压影响。即使不做专业防雷,也应在电源入口和信号线入口处加入TVS管和压敏电阻,成本不到一元,能极大提高系统可靠性。外壳接大地也是好习惯。

  • 防水与接线:传感器和主控箱的接线处是进水重灾区。一定要使用防水接线盒,接头处用防水胶带+绝缘胶带+热缩管多层处理。硅胶密封圈和灌封胶(如704硅橡胶)是你的好朋友。

  • 电源稳定性:电池供电时,电压会逐渐下降。确保你的DC-DC电路或LDO在电池整个工作电压范围内都能稳定输出。同时,在代码中增加电池电压检测,电量过低时发送报警并优雅关机,防止数据丢失。

  • 固件OTA升级可行性:虽然毕业设计不要求,但这是一个重要的拓展点。STM32支持IAP(在应用编程)。可以规划好Flash分区,预留一个Bootloader区。通过LoRa接收新的固件包,校验后写入,实现远程无线升级。这会是答辩时的一个亮点。

  • 传感器长期稳定性:土壤湿度传感器长期埋在地下,探头易腐蚀、电解。选择抗腐蚀电极的型号,或采用**频域反射(FDR)**原理的传感器,虽然贵点但更耐用。定期(如在代码中设定每运行100次)进行一次“自校准”流程也是个思路。

农田实地部署

结语与拓展思考

通过以上步骤,你应该能搭建出一个稳定、低功耗、可演示的智能灌溉系统核心。这个项目本身已经是一个合格的毕业设计。但如果你想做得更出彩,可以思考以下拓展方向:

  1. 虫情监测扩展:在系统中增加一个摄像头模块(如OV2640),结合简单的图像识别算法(可以在网关端或云端进行),识别常见害虫,实现“水肥药”一体化管理的雏形。
  2. 太阳能供电系统:设计一个MPPT太阳能充电控制器,搭配蓄电池,实现真正的能源自给自足,让系统可以部署在无市电的偏远农田。
  3. 数据可视化与决策支持:将网关接收的数据上传到云平台(如阿里云、腾讯云),利用其可视化工具生成土壤墒情变化曲线、灌溉历史报表,甚至结合天气预报数据,给出更科学的灌溉建议。

做毕业设计的过程,就是将一个想法层层落地、不断解决问题的过程。遇到传感器数据不准、程序跑飞、通信中断这些“坑”时,别灰心,那正是你真正在学习和成长的时刻。希望这篇长文能为你铺平一些道路,祝你设计顺利,答辩成功!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐