STM32项目实战:基于STM32F103的智能农业大棚监控系统
文章目录
-
- 一、项目概述
-
- 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 接线注意事项
- DHT11的VCC接5V,GND接GND,DATA接PA0,同时DATA引脚需串联10K上拉电阻到VCC;
- 土壤湿度传感器VCC接5V,GND接GND,AO(模拟输出)接PA1,DO(数字输出)悬空;
- OLED显示屏VCC接3.3V(避免5V烧屏),GND接GND,SCL接PB6,SDA接PB7;
- 继电器模块VCC接5V,GND接GND,IN1/IN2/IN3分别接PB0/PB1/PB2,继电器COM端接电源,NO端接执行设备;
- 所有执行设备(排风扇/加湿器/水泵)需通过继电器隔离,避免直接由STM32引脚驱动。
三、开发环境搭建
3.1 软件工具
- STM32CubeMX(版本6.9.0):用于图形化配置STM32引脚、时钟、外设等;
- Keil MDK-ARM(版本5.38):用于代码编写、编译、下载;
- ST-Link驱动:用于程序下载和调试;
- 串口调试助手(可选):用于查看数据输出。
3.2 STM32CubeMX配置步骤
步骤1:新建工程
- 打开STM32CubeMX,点击“New Project”;
- 在搜索框输入“STM32F103C8T6”,选择对应芯片,点击“Start Project”;
- 弹出“MCU Selection”窗口,确认芯片型号后点击“OK”。
步骤2:配置时钟树
- 点击左侧“Clock Configuration”;
- 将HSE(外部高速时钟)设置为“Crystal/Ceramic Resonator”;
- 将PLLCLK设置为72MHz(HSE=8MHz,PLLMUL=9);
- 将AHB Prescaler设置为1,APB1 Prescaler设置为2,APB2 Prescaler设置为1;
- 点击“OK”保存时钟配置。
步骤3:配置GPIO引脚
- 点击左侧“Pinout & Configuration”;
- 配置PA0为“GPIO_Output”(DHT11数据引脚);
- 配置PA1为“ADC1_IN1”(土壤湿度模拟输入);
- 配置PB0/PB1/PB2为“GPIO_Output”(继电器控制引脚);
- 配置PB6/PB7为“I2C1_SCL/I2C1_SDA”(OLED通信引脚);
- 所有输出引脚默认电平设置为“High”(继电器低电平触发,默认关闭)。
步骤4:配置ADC
- 点击左侧“Analog” -> “ADC1”;
- 启用“ADC1_IN1”通道,转换模式设置为“Continuous Conversion Mode”;
- 采样时间设置为“239.5 Cycles”(提高采样精度);
- 数据对齐方式设置为“Right alignment”。
步骤5:生成代码
- 点击右上角“Project Manager”;
- 设置项目名称(如“GreenhouseMonitor”)、项目路径,选择工具链为“MDK-ARM”;
- 点击“Code Generator”,勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”;
- 点击“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 编译代码
- 打开Keil工程文件(GreenhouseMonitor.uvprojx);
- 点击工具栏中的“Build”按钮(或按F7),检查是否有编译错误;
- 若有错误,根据提示修正(常见错误:引脚定义错误、头文件缺失、字模表不完整);
- 编译成功后,点击“Rebuild”按钮生成HEX文件。
5.2 下载程序
- 将ST-Link调试器连接到STM32最小系统板的SWD接口;
- 打开Keil的“Magic Wand”工具(或按F8),选择“Debug”选项卡;
- 调试器选择“ST-Link Debugger”,点击“Settings”配置ST-Link;
- 点击“Flash Download”选项卡,勾选“Reset and Run”;
- 点击“Download”按钮(或按F8),等待程序下载完成;
- 下载完成后,STM32自动复位并运行程序。
六、调试与测试
6.1 硬件测试
- 检查所有接线是否正确,确保无短路、虚接;
- 给系统上电,观察OLED显示屏是否显示欢迎界面;
- 等待2秒后,OLED显示实时温湿度、土壤湿度数据;
- 用手触摸DHT11传感器,观察温度值是否上升;
- 向土壤湿度传感器喷水,观察土壤湿度值是否上升;
- 当温度超过30℃时,排风扇继电器应吸合,排风扇启动;
- 当湿度低于40%时,加湿器继电器应吸合,加湿器启动;
- 当土壤湿度低于20%时,水泵继电器应吸合,水泵启动。
6.2 常见问题排查
- OLED无显示:检查I2C接线(PB6/PB7)、OLED供电(3.3V)、I2C地址是否正确;
- DHT11数据为0:检查PA0接线、上拉电阻是否接好、延时函数是否准确;
- 土壤湿度值异常:检查PA1接线、ADC通道配置是否正确;
- 继电器不动作:检查PB0/PB1/PB2接线、继电器供电是否正常、触发电平是否正确。
七、功能扩展建议
- 添加无线通信模块(ESP8266/ESP32),实现数据上传至云平台(阿里云/腾讯云);
- 增加光照传感器(BH1750),采集光照强度并控制补光灯;
- 添加蜂鸣器报警模块,当参数超出阈值时发出声光报警;
- 增加按键模块,实现阈值的手动调节;
- 增加SD卡模块,实现历史数据的本地存储。
总结
- 本项目基于STM32F103C8T6实现了智能农业大棚的核心监控功能,涵盖温湿度、土壤湿度采集,OLED显示和执行设备自动控制,代码可直接落地使用;
- 硬件接线需严格按照引脚分配表执行,重点注意DHT11上拉电阻、OLED 3.3V供电、继电器高低压隔离等细节;
- 软件部分采用模块化设计,分为DHT11驱动、ADC驱动、OLED驱动、继电器驱动和主逻辑,零基础小白可按模块逐步实现,降低开发难度。
© 版权声明
文章版权归作者所有,未经允许请勿转载。