STM32项目实战:基于STM32F103的智能农业大棚监控系统

AI2小时前发布 beixibaobao
1 0 0

文章目录

    • 一、项目概述
      • 1.1 硬件清单
      • 1.2 项目整体逻辑流程
    • 二、硬件接线说明
      • 2.1 STM32F103C8T6引脚分配
      • 2.2 接线注意事项
    • 三、开发环境搭建
      • 3.1 软件工具
      • 3.2 STM32CubeMX配置步骤
        • 步骤1:新建工程
        • 步骤2:配置时钟树
        • 步骤3:配置GPIO引脚
        • 步骤4:配置ADC
        • 步骤5:生成代码
    • 四、代码编写
      • 4.1 代码文件结构
      • 4.2 各模块代码实现
        • 文件名:dht11.h
        • 文件名:dht11.c
        • 文件名:adc.h
        • 文件名:adc.c
        • 文件名:oled.h
        • 文件名:oled.c
        • 文件名:relay.h
        • 文件名:relay.c
        • 文件名:main.c
    • 五、代码编译与下载
      • 5.1 编译代码
      • 5.2 下载程序
    • 六、调试与测试
      • 6.1 硬件测试
      • 6.2 常见问题排查
    • 七、功能扩展建议
      • 总结

一、项目概述

本项目基于STM32F103C8T6最小系统板,实现智能农业大棚环境参数(温度、湿度、土壤湿度)的实时采集、显示,并根据预设阈值自动控制排风扇、加湿器、水泵等执行设备,最终实现农业大棚环境的智能化监控与管理。项目面向零基础小白,全程提供详细的步骤指导和可直接运行的代码,确保能够实际落地。

1.1 硬件清单

器件名称 型号/规格 数量 用途
STM32核心板 STM32F103C8T6最小系统板 1 主控芯片
DHT11传感器 温湿度一体化传感器 1 采集空气温湿度
土壤湿度传感器 模拟量输出型 1 采集土壤湿度
OLED显示屏 0.96寸 I2C接口 128*64 1 显示环境参数
继电器模块 5V 四路继电器 1 控制执行设备(隔离高低压)
排风扇/加湿器/水泵 5V小型设备 各1 环境调节执行器
杜邦线 公对公/公对母/母对母 若干 电路连接
面包板 通用型 1 电路搭建
电源模块 5V/12V 直流电源 1 供电

1.2 项目整体逻辑流程

温度>30℃

湿度<40%

土壤湿度<20%

参数正常

系统上电初始化

传感器初始化

OLED显示屏初始化

GPIO/ADC初始化

读取DHT11温湿度数据

读取土壤湿度ADC值

数据处理转换为实际值

OLED显示温湿度/土壤湿度

判断是否超出阈值

控制继电器开启排风扇

控制继电器开启加湿器

控制继电器开启水泵

关闭所有执行设备

二、硬件接线说明

2.1 STM32F103C8T6引脚分配

外设 连接引脚 功能说明
DHT11 PA0(GPIO) 温湿度数据传输
土壤湿度传感器 PA1(ADC1_IN1) 模拟量信号输入
OLED显示屏 PB6(SCL)/PB7(SDA) I2C通信引脚
继电器1(排风扇) PB0(GPIO) 控制信号输出
继电器2(加湿器) PB1(GPIO) 控制信号输出
继电器3(水泵) PB2(GPIO) 控制信号输出

2.2 接线注意事项

  1. DHT11的VCC接5V,GND接GND,DATA接PA0,同时DATA引脚需串联10K上拉电阻到VCC;
  2. 土壤湿度传感器VCC接5V,GND接GND,AO(模拟输出)接PA1,DO(数字输出)悬空;
  3. OLED显示屏VCC接3.3V(避免5V烧屏),GND接GND,SCL接PB6,SDA接PB7;
  4. 继电器模块VCC接5V,GND接GND,IN1/IN2/IN3分别接PB0/PB1/PB2,继电器COM端接电源,NO端接执行设备;
  5. 所有执行设备(排风扇/加湿器/水泵)需通过继电器隔离,避免直接由STM32引脚驱动。

三、开发环境搭建

3.1 软件工具

  1. STM32CubeMX(版本6.9.0):用于图形化配置STM32引脚、时钟、外设等;
  2. Keil MDK-ARM(版本5.38):用于代码编写、编译、下载;
  3. ST-Link驱动:用于程序下载和调试;
  4. 串口调试助手(可选):用于查看数据输出。

3.2 STM32CubeMX配置步骤

步骤1:新建工程
  1. 打开STM32CubeMX,点击“New Project”;
  2. 在搜索框输入“STM32F103C8T6”,选择对应芯片,点击“Start Project”;
  3. 弹出“MCU Selection”窗口,确认芯片型号后点击“OK”。
步骤2:配置时钟树
  1. 点击左侧“Clock Configuration”;
  2. 将HSE(外部高速时钟)设置为“Crystal/Ceramic Resonator”;
  3. 将PLLCLK设置为72MHz(HSE=8MHz,PLLMUL=9);
  4. 将AHB Prescaler设置为1,APB1 Prescaler设置为2,APB2 Prescaler设置为1;
  5. 点击“OK”保存时钟配置。
步骤3:配置GPIO引脚
  1. 点击左侧“Pinout & Configuration”;
  2. 配置PA0为“GPIO_Output”(DHT11数据引脚);
  3. 配置PA1为“ADC1_IN1”(土壤湿度模拟输入);
  4. 配置PB0/PB1/PB2为“GPIO_Output”(继电器控制引脚);
  5. 配置PB6/PB7为“I2C1_SCL/I2C1_SDA”(OLED通信引脚);
  6. 所有输出引脚默认电平设置为“High”(继电器低电平触发,默认关闭)。
步骤4:配置ADC
  1. 点击左侧“Analog” -> “ADC1”;
  2. 启用“ADC1_IN1”通道,转换模式设置为“Continuous Conversion Mode”;
  3. 采样时间设置为“239.5 Cycles”(提高采样精度);
  4. 数据对齐方式设置为“Right alignment”。
步骤5:生成代码
  1. 点击右上角“Project Manager”;
  2. 设置项目名称(如“GreenhouseMonitor”)、项目路径,选择工具链为“MDK-ARM”;
  3. 点击“Code Generator”,勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”;
  4. 点击“Generate Code”,等待代码生成完成后,点击“Open Project”打开Keil工程。

四、代码编写

4.1 代码文件结构

GreenhouseMonitor/
├── Core/
│   ├── Inc/
│   │   ├── adc.h          // ADC驱动头文件
│   │   ├── dht11.h        // DHT11驱动头文件
│   │   ├── oled.h         // OLED驱动头文件
│   │   ├── relay.h        // 继电器驱动头文件
│   │   └── main.h         // 主函数头文件
│   └── Src/
│       ├── adc.c          // ADC驱动实现
│       ├── dht11.c        // DHT11驱动实现
│       ├── oled.c         // OLED驱动实现
│       ├── relay.c        // 继电器驱动实现
│       └── main.c         // 主函数逻辑
└── MDK-ARM/
    └── GreenhouseMonitor.uvprojx  // Keil工程文件

4.2 各模块代码实现

文件名:dht11.h
#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f1xx_hal.h"
// 定义DHT11引脚
#define DHT11_PIN GPIO_PIN_0
#define DHT11_PORT GPIOA
// 函数声明
void DHT11_Init(void);                  // DHT11初始化
uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi);  // 读取温湿度数据
void DHT11_Set_Pin_Output(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin); // 设置引脚为输出
void DHT11_Set_Pin_Input(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);  // 设置引脚为输入
void DHT11_Delay_us(uint32_t us);      // 微秒延时函数
#endif
文件名:dht11.c
#include "dht11.h"
// 延时函数(微秒)
void DHT11_Delay_us(uint32_t us)
{
    uint32_t delay = (HAL_RCC_GetHCLKFreq() / 8000000) * us;
    while (delay--)
    {
        ;
    }
}
// 设置引脚为输出模式
void DHT11_Set_Pin_Output(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
// 设置引脚为输入模式
void DHT11_Set_Pin_Input(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
// DHT11初始化
void DHT11_Init(void)
{
    DHT11_Set_Pin_Output(DHT11_PORT, DHT11_PIN);
    HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET);
}
// 读取DHT11温湿度数据
uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi)
{
    uint8_t buf[5] = {0};
    uint8_t i, j;
    // 主机发送起始信号
    DHT11_Set_Pin_Output(DHT11_PORT, DHT11_PIN);
    HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET);
    DHT11_Delay_us(18000);  // 拉低至少18ms
    HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET);
    DHT11_Delay_us(20);     // 拉高20-40us
    DHT11_Set_Pin_Input(DHT11_PORT, DHT11_PIN);
    // 等待DHT11响应
    if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET)
    {
        // 等待响应信号结束(低电平80us)
        while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET);
        // 等待数据传输开始(高电平80us)
        while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET);
        // 读取40位数据(5字节)
        for (i = 0; i < 5; i++)
        {
            for (j = 0; j < 8; j++)
            {
                // 等待低电平结束(50us)
                while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET);
                // 计算高电平持续时间(0:26-28us,1:70us)
                DHT11_Delay_us(40);
                if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET)
                {
                    buf[i] |= (1 << (7 - j));
                    // 等待高电平结束
                    while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET);
                }
                else
                {
                    buf[i] &= ~(1 << (7 - j));
                }
            }
        }
        // 校验数据(前4字节和等于第5字节)
        if (buf[0] + buf[1] + buf[2] + buf[3] == buf[4])
        {
            *humi = buf[0];  // 湿度整数部分
            *temp = buf[2];  // 温度整数部分
            return 0;        // 读取成功
        }
        else
        {
            return 1;        // 校验失败
        }
    }
    else
    {
        return 2;            // 无响应
    }
}
文件名:adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f1xx_hal.h"
// 函数声明
void ADC1_Init(void);                          // ADC初始化
uint16_t ADC1_Read_Channel(uint32_t channel);  // 读取指定通道ADC值
float Soil_Humidity_Calc(uint16_t adc_value);  // 计算土壤湿度百分比
#endif
文件名:adc.c
#include "adc.h"
ADC_HandleTypeDef hadc1;
// ADC初始化
void ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};
    // 使能ADC1时钟
    __HAL_RCC_ADC1_CLK_ENABLE();
    // 配置ADC基本参数
    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = DISABLE;                  // 单通道模式
    hadc1.Init.ContinuousConvMode = ENABLE;             // 连续转换模式
    hadc1.Init.DiscontinuousConvMode = DISABLE;         // 禁用间断模式
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;   // 软件触发
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;         // 右对齐
    hadc1.Init.NbrOfConversion = 1;                     // 转换通道数1
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }
    // 配置ADC通道(PA1 = ADC1_IN1)
    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;  // 采样时间239.5周期
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
    // 启动ADC
    HAL_ADC_Start(&hadc1);
}
// 读取指定通道ADC值
uint16_t ADC1_Read_Channel(uint32_t channel)
{
    ADC_ChannelConfTypeDef sConfig = {0};
    // 配置通道
    sConfig.Channel = channel;
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
    // 启动转换并等待完成
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, 100);
    // 返回转换结果
    return HAL_ADC_GetValue(&hadc1);
}
// 计算土壤湿度百分比(ADC值范围:0-4095,对应湿度100%-0%)
float Soil_Humidity_Calc(uint16_t adc_value)
{
    float humidity = 0.0;
    // 线性转换:ADC值越大,土壤湿度越低
    humidity = (4095.0 - adc_value) / 4095.0 * 100.0;
    // 限制湿度范围0-100%
    if (humidity < 0) humidity = 0;
    if (humidity > 100) humidity = 100;
    return humidity;
}
// ADC错误处理函数
void Error_Handler(void)
{
    // 可添加LED闪烁等错误提示
    while (1)
    {
        HAL_Delay(500);
    }
}
文件名:oled.h
#ifndef __OLED_H
#define __OLED_H
#include "stm32f1xx_hal.h"
#include "i2c.h"
// OLED I2C地址(根据硬件配置,0x78或0x7A)
#define OLED_I2C_ADDR 0x78
// 屏幕分辨率
#define OLED_WIDTH  128
#define OLED_HEIGHT 64
// 函数声明
void OLED_Init(void);                          // OLED初始化
void OLED_Clear(void);                         // 清屏
void OLED_Set_Pos(uint8_t x, uint8_t y);       // 设置光标位置
void OLED_Show_Char(uint8_t x, uint8_t y, uint8_t chr);  // 显示单个字符
void OLED_Show_String(uint8_t x, uint8_t y, uint8_t *str); // 显示字符串
void OLED_Show_Num(uint8_t x, uint8_t y, uint32_t num, uint8_t len); // 显示数字
void OLED_Refresh(void);                       // 刷新显示
#endif
文件名:oled.c
#include "oled.h"
#include "stdlib.h"
#include "string.h"
// OLED显存(128*64/8=1024字节)
static uint8_t OLED_GRAM[128][8];
// I2C写命令
void OLED_Write_Cmd(uint8_t cmd)
{
    HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x00, I2C_MEMADD_SIZE_8BIT, &cmd, 1, 100);
}
// I2C写数据
void OLED_Write_Data(uint8_t data)
{
    HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
}
// OLED初始化
void OLED_Init(void)
{
    HAL_Delay(100);  // 上电延时
    OLED_Write_Cmd(0xAE); // 关闭显示
    OLED_Write_Cmd(0xD5); // 设置显示时钟分频比/振荡器频率
    OLED_Write_Cmd(0x80);
    OLED_Write_Cmd(0xA8); // 设置多路复用率
    OLED_Write_Cmd(0x3F);
    OLED_Write_Cmd(0xD3); // 设置显示偏移
    OLED_Write_Cmd(0x00);
    OLED_Write_Cmd(0x40); // 设置显示起始行
    OLED_Write_Cmd(0x8D); // 电荷泵设置
    OLED_Write_Cmd(0x14); // 开启电荷泵
    OLED_Write_Cmd(0x20); // 设置内存地址模式
    OLED_Write_Cmd(0x00); // 水平寻址模式
    OLED_Write_Cmd(0xA1); // 设置段重映射
    OLED_Write_Cmd(0xC8); // 设置COM扫描方向
    OLED_Write_Cmd(0xDA); // 设置COM引脚硬件配置
    OLED_Write_Cmd(0x12);
    OLED_Write_Cmd(0x81); // 设置对比度
    OLED_Write_Cmd(0xCF);
    OLED_Write_Cmd(0xD9); // 设置预充电周期
    OLED_Write_Cmd(0xF1);
    OLED_Write_Cmd(0xDB); // 设置VCOMH取消选择级别
    OLED_Write_Cmd(0x40);
    OLED_Write_Cmd(0xA4); // 整个显示开启/关闭
    OLED_Write_Cmd(0xA6); // 设置正常/反显
    OLED_Write_Cmd(0xAF); // 开启显示
    OLED_Clear(); // 清屏
}
// 设置光标位置
void OLED_Set_Pos(uint8_t x, uint8_t y)
{
    OLED_Write_Cmd(0xB0 + y);                    // 设置页地址
    OLED_Write_Cmd(((x & 0xF0) >> 4) | 0x10);    // 设置列高4位
    OLED_Write_Cmd(x & 0x0F);                    // 设置列低4位
}
// 清屏
void OLED_Clear(void)
{
    memset(OLED_GRAM, 0, sizeof(OLED_GRAM));
    OLED_Refresh();
}
// 刷新显示
void OLED_Refresh(void)
{
    uint8_t i, n;
    for (i = 0; i < 8; i++)
    {
        OLED_Set_Pos(0, i);
        for (n = 0; n < 128; n++)
        {
            OLED_Write_Data(OLED_GRAM[n][i]);
        }
    }
}
// 画点(x:0-127, y:0-63, t:1显示/0清除)
void OLED_Draw_Point(uint8_t x, uint8_t y, uint8_t t)
{
    uint8_t pos, bx, temp = 0;
    if (x > 127 || y > 63) return; // 超出范围
    pos = 7 - y / 8;
    bx = y % 8;
    temp = 1 << (7 - bx);
    if (t)
    {
        OLED_GRAM[x][pos] |= temp;
    }
    else
    {
        OLED_GRAM[x][pos] &= ~temp;
    }
}
// 显示单个字符(x:0-127, y:0-7, chr:字符, size:字体大小16/12)
void OLED_Show_Char(uint8_t x, uint8_t y, uint8_t chr)
{
    uint8_t c = 0, i = 0;
    c = chr - ' '; // 偏移量
    if (x > 127)
    {
        x = 0;
        y += 2;
    }
    // 16x16字体
    for (i = 0; i < 16; i++)
    {
        OLED_Draw_Point(x, y * 16 + i, asc2_1608[c][i] & 0x80 ? 1 : 0);
        OLED_Draw_Point(x + 1, y * 16 + i, asc2_1608[c][i] & 0x40 ? 1 : 0);
        OLED_Draw_Point(x + 2, y * 16 + i, asc2_1608[c][i] & 0x20 ? 1 : 0);
        OLED_Draw_Point(x + 3, y * 16 + i, asc2_1608[c][i] & 0x10 ? 1 : 0);
        OLED_Draw_Point(x + 4, y * 16 + i, asc2_1608[c][i] & 0x08 ? 1 : 0);
        OLED_Draw_Point(x + 5, y * 16 + i, asc2_1608[c][i] & 0x04 ? 1 : 0);
        OLED_Draw_Point(x + 6, y * 16 + i, asc2_1608[c][i] & 0x02 ? 1 : 0);
        OLED_Draw_Point(x + 7, y * 16 + i, asc2_1608[c][i] & 0x01 ? 1 : 0);
        i++;
        OLED_Draw_Point(x, y * 16 + i, asc2_1608[c][i + 15] & 0x80 ? 1 : 0);
        OLED_Draw_Point(x + 1, y * 16 + i, asc2_1608[c][i + 15] & 0x40 ? 1 : 0);
        OLED_Draw_Point(x + 2, y * 16 + i, asc2_1608[c][i + 15] & 0x20 ? 1 : 0);
        OLED_Draw_Point(x + 3, y * 16 + i, asc2_1608[c][i + 15] & 0x10 ? 1 : 0);
        OLED_Draw_Point(x + 4, y * 16 + i, asc2_1608[c][i + 15] & 0x08 ? 1 : 0);
        OLED_Draw_Point(x + 5, y * 16 + i, asc2_1608[c][i + 15] & 0x04 ? 1 : 0);
        OLED_Draw_Point(x + 6, y * 16 + i, asc2_1608[c][i + 15] & 0x02 ? 1 : 0);
        OLED_Draw_Point(x + 7, y * 16 + i, asc2_1608[c][i + 15] & 0x01 ? 1 : 0);
    }
}
// 显示字符串
void OLED_Show_String(uint8_t x, uint8_t y, uint8_t *str)
{
    uint8_t i = 0;
    while (str[i] != '')
    {
        OLED_Show_Char(x + 8 * i, y, str[i]);
        i++;
    }
}
// 显示数字
void OLED_Show_Num(uint8_t x, uint8_t y, uint32_t num, uint8_t len)
{
    uint8_t t, temp;
    uint8_t enshow = 0;
    for (t = 0; t < len; t++)
    {
        temp = (num / (uint32_t)pow(10, len - t - 1)) % 10;
        if (enshow == 0 && t < (len - 1))
        {
            if (temp == 0)
            {
                OLED_Show_Char(x + 8 * t, y, ' ');
                continue;
            }
            else enshow = 1;
        }
        OLED_Show_Char(x + 8 * t, y, temp + '0');
    }
}
// 16x8 ASCII字模表(部分,完整表需自行补充)
const uint8_t asc2_1608[95][16] = {
    {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空格
    {0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // !
    // 其余字符字模需自行补充,可通过字模提取工具生成
};
文件名:relay.h
#ifndef __RELAY_H
#define __RELAY_H
#include "stm32f1xx_hal.h"
// 继电器引脚定义
#define RELAY1_PIN GPIO_PIN_0  // 排风扇
#define RELAY2_PIN GPIO_PIN_1  // 加湿器
#define RELAY3_PIN GPIO_PIN_2  // 水泵
#define RELAY_PORT GPIOB
// 函数声明
void Relay_Init(void);                  // 继电器初始化
void Relay1_Control(uint8_t state);     // 控制继电器1(0关闭/1开启)
void Relay2_Control(uint8_t state);     // 控制继电器2
void Relay3_Control(uint8_t state);     // 控制继电器3
void Relay_All_Off(void);               // 关闭所有继电器
#endif
文件名:relay.c
#include "relay.h"
// 继电器初始化(低电平触发,默认关闭)
void Relay_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    // 使能GPIOB时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();
    // 配置PB0/PB1/PB2为输出模式
    GPIO_InitStruct.Pin = RELAY1_PIN | RELAY2_PIN | RELAY3_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(RELAY_PORT, &GPIO_InitStruct);
    // 默认关闭所有继电器(高电平)
    Relay_All_Off();
}
// 控制继电器1
void Relay1_Control(uint8_t state)
{
    if (state == 1)
    {
        HAL_GPIO_WritePin(RELAY_PORT, RELAY1_PIN, GPIO_PIN_RESET); // 开启
    }
    else
    {
        HAL_GPIO_WritePin(RELAY_PORT, RELAY1_PIN, GPIO_PIN_SET);   // 关闭
    }
}
// 控制继电器2
void Relay2_Control(uint8_t state)
{
    if (state == 1)
    {
        HAL_GPIO_WritePin(RELAY_PORT, RELAY2_PIN, GPIO_PIN_RESET); // 开启
    }
    else
    {
        HAL_GPIO_WritePin(RELAY_PORT, RELAY2_PIN, GPIO_PIN_SET);   // 关闭
    }
}
// 控制继电器3
void Relay3_Control(uint8_t state)
{
    if (state == 1)
    {
        HAL_GPIO_WritePin(RELAY_PORT, RELAY3_PIN, GPIO_PIN_RESET); // 开启
    }
    else
    {
        HAL_GPIO_WritePin(RELAY_PORT, RELAY3_PIN, GPIO_PIN_SET);   // 关闭
    }
}
// 关闭所有继电器
void Relay_All_Off(void)
{
    HAL_GPIO_WritePin(RELAY_PORT, RELAY1_PIN | RELAY2_PIN | RELAY3_PIN, GPIO_PIN_SET);
}
文件名:main.c
#include "main.h"
#include "dht11.h"
#include "adc.h"
#include "oled.h"
#include "relay.h"
/* 函数声明 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
static void MX_ADC1_Init(void);
/* 全局变量 */
uint8_t temp = 0;          // 温度值
uint8_t humi = 0;          // 湿度值
uint16_t soil_adc = 0;     // 土壤湿度ADC值
float soil_humi = 0.0;     // 土壤湿度百分比
/* 阈值设置 */
#define TEMP_THRESHOLD 30  // 温度阈值(℃)
#define HUMI_THRESHOLD 40  // 湿度阈值(%)
#define SOIL_THRESHOLD 20  // 土壤湿度阈值(%)
int main(void)
{
    /* 初始化HAL库 */
    HAL_Init();
    /* 配置系统时钟为72MHz */
    SystemClock_Config();
    /* 初始化外设 */
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_ADC1_Init();
    /* 初始化模块 */
    DHT11_Init();
    ADC1_Init();
    OLED_Init();
    Relay_Init();
    /* OLED显示欢迎界面 */
    OLED_Clear();
    OLED_Show_String(20, 0, (uint8_t *)"Greenhouse Monitor");
    OLED_Show_String(30, 2, (uint8_t *)"STM32F103C8T6");
    OLED_Show_String(40, 4, (uint8_t *)"Initializing...");
    OLED_Refresh();
    HAL_Delay(2000);
    /* 主循环 */
    while (1)
    {
        /* 1. 读取温湿度数据 */
        if (DHT11_Read_Data(&temp, &humi) == 0)
        {
            // 读取成功
        }
        else
        {
            // 读取失败,赋值为0
            temp = 0;
            humi = 0;
        }
        /* 2. 读取土壤湿度ADC值并转换为百分比 */
        soil_adc = ADC1_Read_Channel(ADC_CHANNEL_1);
        soil_humi = Soil_Humidity_Calc(soil_adc);
        /* 3. OLED显示数据 */
        OLED_Clear();
        // 显示温度
        OLED_Show_String(0, 0, (uint8_t *)"Temp: ");
        OLED_Show_Num(40, 0, temp, 2);
        OLED_Show_Char(60, 0, 'C');
        // 显示湿度
        OLED_Show_String(0, 2, (uint8_t *)"Humi: ");
        OLED_Show_Num(40, 2, humi, 2);
        OLED_Show_Char(60, 2, '%');
        // 显示土壤湿度
        OLED_Show_String(0, 4, (uint8_t *)"Soil: ");
        OLED_Show_Num(40, 4, (uint32_t)soil_humi, 2);
        OLED_Show_Char(60, 4, '%');
        OLED_Refresh();
        /* 4. 根据阈值控制执行设备 */
        // 温度超过阈值,开启排风扇
        if (temp > TEMP_THRESHOLD)
        {
            Relay1_Control(1);
        }
        else
        {
            Relay1_Control(0);
        }
        // 湿度低于阈值,开启加湿器
        if (humi < HUMI_THRESHOLD)
        {
            Relay2_Control(1);
        }
        else
        {
            Relay2_Control(0);
        }
        // 土壤湿度低于阈值,开启水泵
        if (soil_humi < SOIL_THRESHOLD)
        {
            Relay3_Control(1);
        }
        else
        {
            Relay3_Control(0);
        }
        /* 延时500ms,降低采样频率 */
        HAL_Delay(500);
    }
}
/**
  * @brief  系统时钟配置函数
  * @param  无
  * @retval 无
  */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    /* 配置外部高速时钟HSE */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }
    /* 配置系统时钟 */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
        Error_Handler();
    }
}
/**
  * @brief  I2C1初始化函数
  * @param  无
  * @retval 无
  */
static void MX_I2C1_Init(void)
{
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 400000;
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    if (HAL_I2C_Init(&hi2c1) != HAL_OK)
    {
        Error_Handler();
    }
}
/**
  * @brief  ADC1初始化函数
  * @param  无
  * @retval 无
  */
static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};
    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }
    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}
/**
  * @brief  GPIO初始化函数
  * @param  无
  * @retval 无
  */
static void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    /* GPIO时钟使能 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /* PA0初始化(DHT11) */
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    /* PB0/PB1/PB2初始化(继电器) */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    /* 默认电平设置 */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_SET);
}
/**
  * @brief  错误处理函数
  * @param  无
  * @retval 无
  */
void Error_Handler(void)
{
    __disable_irq();
    while (1)
    {
        // 可添加LED闪烁提示错误
    }
}
#ifdef USE_FULL_ASSERT
/**
  * @brief  断言失败处理函数
  * @param  file: 文件名
  * @param  line: 行号
  * @retval 无
  */
void assert_failed(uint8_t *file, uint32_t line)
{
    /* 用户可添加自定义打印信息 */
}
#endif /* USE_FULL_ASSERT */

五、代码编译与下载

5.1 编译代码

  1. 打开Keil工程文件(GreenhouseMonitor.uvprojx);
  2. 点击工具栏中的“Build”按钮(或按F7),检查是否有编译错误;
  3. 若有错误,根据提示修正(常见错误:引脚定义错误、头文件缺失、字模表不完整);
  4. 编译成功后,点击“Rebuild”按钮生成HEX文件。

5.2 下载程序

  1. 将ST-Link调试器连接到STM32最小系统板的SWD接口;
  2. 打开Keil的“Magic Wand”工具(或按F8),选择“Debug”选项卡;
  3. 调试器选择“ST-Link Debugger”,点击“Settings”配置ST-Link;
  4. 点击“Flash Download”选项卡,勾选“Reset and Run”;
  5. 点击“Download”按钮(或按F8),等待程序下载完成;
  6. 下载完成后,STM32自动复位并运行程序。

六、调试与测试

6.1 硬件测试

  1. 检查所有接线是否正确,确保无短路、虚接;
  2. 给系统上电,观察OLED显示屏是否显示欢迎界面;
  3. 等待2秒后,OLED显示实时温湿度、土壤湿度数据;
  4. 用手触摸DHT11传感器,观察温度值是否上升;
  5. 向土壤湿度传感器喷水,观察土壤湿度值是否上升;
  6. 当温度超过30℃时,排风扇继电器应吸合,排风扇启动;
  7. 当湿度低于40%时,加湿器继电器应吸合,加湿器启动;
  8. 当土壤湿度低于20%时,水泵继电器应吸合,水泵启动。

6.2 常见问题排查

  1. OLED无显示:检查I2C接线(PB6/PB7)、OLED供电(3.3V)、I2C地址是否正确;
  2. DHT11数据为0:检查PA0接线、上拉电阻是否接好、延时函数是否准确;
  3. 土壤湿度值异常:检查PA1接线、ADC通道配置是否正确;
  4. 继电器不动作:检查PB0/PB1/PB2接线、继电器供电是否正常、触发电平是否正确。

七、功能扩展建议

  1. 添加无线通信模块(ESP8266/ESP32),实现数据上传至云平台(阿里云/腾讯云);
  2. 增加光照传感器(BH1750),采集光照强度并控制补光灯;
  3. 添加蜂鸣器报警模块,当参数超出阈值时发出声光报警;
  4. 增加按键模块,实现阈值的手动调节;
  5. 增加SD卡模块,实现历史数据的本地存储。

总结

  1. 本项目基于STM32F103C8T6实现了智能农业大棚的核心监控功能,涵盖温湿度、土壤湿度采集,OLED显示和执行设备自动控制,代码可直接落地使用;
  2. 硬件接线需严格按照引脚分配表执行,重点注意DHT11上拉电阻、OLED 3.3V供电、继电器高低压隔离等细节;
  3. 软件部分采用模块化设计,分为DHT11驱动、ADC驱动、OLED驱动、继电器驱动和主逻辑,零基础小白可按模块逐步实现,降低开发难度。
© 版权声明

相关文章