一个基于 STM32 微控制器的环境监测与控制系统的设计与实现。该系统能够实时采集温湿度数据,通过 OLED 屏幕显示环境参数,并提供用户交互界面进行阈值设置。系统还具备 PWM 电机控制、状态指示和异常报警功能,适合应用于智能家居、温室大棚等环境监测场景。
代码下载地址:编译前需要将main.c中中文转位gb2312编码存储,否则会报编码错误https://wwp.lanzoul.com/iGKFS2zhoxif
系统功能概述
该环境监测与控制系统主要实现以下功能:
- 温湿度数据采集与显示(使用 DHT11 传感器)
- 128x64 OLED 屏幕信息显示
- 阈值设置与 Flash 存储功能
- 四档 PWM 电机速度控制
- 状态指示(LED 灯光)与异常报警(蜂鸣器)
- 火焰检测功能
- 按键短按与长按处理
系统工作流程如下:STM32 微控制器通过 DHT11 传感器采集温湿度数据,与预设阈值比较后控制相应的执行器(如风扇、加湿器等),并通过 OLED 屏幕显示实时数据和系统状态。用户可以通过按键设置温度和湿度阈值,系统将阈值保存在 Flash 中以便掉电保存。
硬件设计与选型
核心控制器
系统采用 STM32F103 系列微控制器作为核心,该系列芯片具有以下特点:
- 32 位 Cortex-M3 内核,最高 72MHz 工作频率
- 丰富的外设资源:ADC、I2C、TIM、GPIO 等
- 内置 Flash 和 SRAM,满足程序存储和数据处理需求
- 低功耗设计,适合电池供电或节能场景
传感器与执行器
- DHT11 温湿度传感器:低成本、单总线接口,可同时测量温度和湿度
- OLED 显示屏:128x64 分辨率,I2C 接口,显示效果清晰
- 按键模块:4 个独立按键,用于人机交互
- LED 指示灯:4 个 LED,用于状态指示
- 蜂鸣器:用于异常情况报警
- 电机控制:通过 PWM 信号控制电机速度
- 火焰传感器:用于检测环境中的火焰信号
硬件接口设计
系统硬件接口设计如下:
- DHT11 传感器:连接到 GPIOA_PIN_1(单总线接口)
- OLED 显示屏:通过 I2C1 接口连接(SCL: PB6, SDA: PB7)
- 按键输入:K1-K4 连接到 GPIOB_PIN_0-3(上拉输入)
- LED 输出:LED1-LED4 连接到 GPIOA_PIN_4-7 和 GPIOB_PIN_0-1
- 蜂鸣器:连接到 GPIOA_PIN_3
- 电机控制:通过 TIM3 的 PWM 输出(CH2-CH4)控制
- 火焰传感器:连接到 GPIOA_PIN_6(ADC 输入)
软件架构分析
系统软件采用模块化设计,主要包括以下模块:
- 硬件驱动模块:包含 GPIO、I2C、TIM、ADC 等底层驱动
- 传感器模块:DHT11 温湿度传感器驱动
- 显示模块:OLED 显示屏驱动与界面显示
- 按键模块:按键检测与长按处理
- 存储模块:Flash 数据存储与读取
- 控制模块:PWM 电机控制、LED 与蜂鸣器控制
- 主控制模块:系统初始化与主循环控制
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "OLED.h"
#include "dht11.h"
#include "config.h"
#include "stm32f1xx_hal_flash.h"
#include "flash.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
I2C_HandleTypeDef hi2c1;
TIM_HandleTypeDef htim3;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
static void MX_TIM3_Init(void);
static void MX_ADC1_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
DHT11_TypeDef myDHT11;
bool dht11_flag = false;
bool key_flag = false;
bool auto_Flag = false; // 自动模式
bool set_ui = false; //设置界面
bool cursor = false; //光标位置标志位(false为第一行,true为第二行)
// 存储需要保存到Flash的数据
uint8_t TT=30;//温度阈值(单位:℃)
uint8_t HT=70;//湿度阈值(单位:%RH)
uint16_t motorIndex = 0; // motorSpeedIndex
uint16_t motorSpeed[] = {0, 200, 400, 999}; // Speed
//显示DHT11数据并更新OLED屏幕 128*64
void dht11_show(void)
{
OLED_Clear();//清屏OLED
char str[16];
if(set_ui == false){
OLED_ShowString(1, 1, "工位: 11", OLED_8X16);
if (DHT11_ReadData(&myDHT11) == 0)
{
//温度处理 + LED1控制
if(myDHT11.Temperature>TT){
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);//LED1
motorIndex=3;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
OLED_ShowString(112, 16, "H", OLED_8X16);//温度过高显示"H"
}else{
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);//LED1
motorIndex=0;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET);
OLED_ShowString(112, 16, "L", OLED_8X16);//温度正常显示"L"
}
sprintf(str, "温度:%d.0℃", myDHT11.Temperature);
OLED_ShowString(1, 16, str, OLED_8X16);
//湿度处理
if(myDHT11.Humidity>HT){
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);//LED2
OLED_ShowString(112, 32, "H", OLED_8X16);//湿度过高显示"H"
}else{
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
OLED_ShowString(112, 32, "L", OLED_8X16);//湿度正常显示"L"
}
sprintf(str, "湿度:%dRH%%", myDHT11.Humidity);
OLED_ShowString(1, 32, str, OLED_8X16);
}
//状态显示
//电机档位
//sprintf(str, "D:%d", motorIndex);
//OLED_ShowString(100, 16, str, OLED_8X16);
//TIM3->CCR2=motorSpeed[motorIndex];
//光照强度检测
// HAL_ADC_Start(&hadc1);
// HAL_ADC_PollForConversion(&hadc1, 50);
// uint16_t ADC_Value=0;
// ADC_Value = HAL_ADC_GetValue(&hadc1); //读取AD值
// sprintf(str, "ADC%d", ADC_Value);
// OLED_ShowString(1, 48, str, OLED_8X16);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6)==GPIO_PIN_RESET){
OLED_ShowString(1, 48, "火焰:有", OLED_8X16);
//蜂鸣器翻转
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_3);
//HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);
//LED3
//HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET);
HAL_GPIO_TogglePin(LED3_GPIO_Port,LED3_Pin);
}else{
OLED_ShowString(1, 48, "火焰:无", OLED_8X16);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);
//蜂鸣器关
HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET);
}
//自动模式处理
}else{
// 设置界面显示
OLED_ShowString(1, 1, "参数设置", OLED_8X16);
sprintf(str, "温度阈值:%d℃", TT);
OLED_ShowString(1, 16, str, OLED_8X16);
sprintf(str, "湿度阈值:%dRH%s", HT, "%");
OLED_ShowString(1, 32, str, OLED_8X16);
//光标显示
if(cursor==false){
OLED_Invert(72,16,32,16);//第一行高亮
}else{
OLED_Invert(72,32,48,16);//第二行高亮
}
}
OLED_Update();
}
// 按键
bool KeyCheck(GPIO_TypeDef *gpio_port, uint16_t gpio_pin)
{
if (HAL_GPIO_ReadPin(gpio_port, gpio_pin) == GPIO_PIN_RESET)
{
HAL_Delay(20);
if (HAL_GPIO_ReadPin(gpio_port, gpio_pin) == GPIO_PIN_RESET)
{
while (HAL_GPIO_ReadPin(gpio_port, gpio_pin) == GPIO_PIN_RESET);
return true;
}
}
return false;
}
// 检查按键是否被长按
bool KeyCheck_LongPress(GPIO_TypeDef *gpio_port, uint16_t gpio_pin, uint32_t *press_time)
{
if (HAL_GPIO_ReadPin(gpio_port, gpio_pin) == GPIO_PIN_RESET)
{
HAL_Delay(20); //消抖延时
if (HAL_GPIO_ReadPin(gpio_port, gpio_pin) == GPIO_PIN_RESET)
{
if(*press_time == 0) {
*press_time = HAL_GetTick(); // 记录按下开始时间
}
// 检查是否达到长按时间,这里设定为500ms
else if(HAL_GetTick() - *press_time > 500) {
return true; // 长按确认
}
return false; // 短按状态
}
}
*press_time = 0; // 重置按下时间
return false;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_TIM3_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
//LED
//HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
//HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
//HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET);
//HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET);
// 加载Flash数据
LoadThresholdsFromFlash();
TT= thresholdData.TemperatureThreshold;
HT= thresholdData.HumidityThreshold;
if(TT==0 || HT==0){
TT=30;
HT=70;
}
SaveThresholdsToFlash();
//ADC
// DHT11
DHT11_Init(&myDHT11, GPIOA, GPIO_PIN_1);
// OLED
OLED_Init();
// PWM CH2 CH3 CH4
HAL_TIM_PWM_Init(&htim3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
TIM3->CCR2 = 500;
TIM3->CCR3 = 600;
TIM3->CCR4 = 999;
//HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
//TIM3->CCR3 = 0;
//HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
//蜂鸣器
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
static uint32_t k3_press_time = 0, k4_press_time = 0; // 记录按键按下时间
if (dht11_flag == true)
{
dht11_flag = false;
//dht11_show();
}
if (key_flag == true)
{
key_flag = false;
if (KeyCheck(K1_GPIO_Port, K1_Pin) == 1)
{
// 电机档位+开机
//if(++motorIndex==(sizeof(motorSpeed)/sizeof(motorSpeed[0]))) motorIndex=0;
// 电机档位+UI
set_ui = !set_ui;
thresholdData.TemperatureThreshold=TT;
thresholdData.HumidityThreshold=HT;
SaveThresholdsToFlash();
}
if (KeyCheck(K2_GPIO_Port, K2_Pin) == 1)
{
if(set_ui==true){
cursor=!cursor;
}
}
if (KeyCheck(K3_GPIO_Port, K3_Pin) == 1)
{
// 增减
if(cursor==false){
if(TT<99 && TT>0){
TT++;
}
}else{
if(HT<99 && HT>0){
HT++;
}
}
}
if (HAL_GPIO_ReadPin(K3_GPIO_Port, K3_Pin) == GPIO_PIN_RESET) {
bool is_long = KeyCheck_LongPress(K3_GPIO_Port, K3_Pin, &k3_press_time);
int step = is_long ? 1 : 1; // 长按+5,短按+1
if(set_ui == true) {
if(cursor == false && TT < 99) TT += step;
else if(HT < 99) HT += step;
}
HAL_Delay(100); // 防止重复触发
}
if (KeyCheck(K4_GPIO_Port, K4_Pin) == 1)
{
// 减少
if(cursor==false){
if(TT<99 && TT>0){
TT--;
}
}else{
if(HT<99 && HT>0){
HT--;
}
}
}
// K4长按
if (HAL_GPIO_ReadPin(K4_GPIO_Port, K4_Pin) == GPIO_PIN_RESET) {
bool is_long = KeyCheck_LongPress(K4_GPIO_Port, K4_Pin, &k4_press_time);
int step = is_long ? 1 : 1; // 长按-5,短按-1
if(set_ui == true) {
if(cursor == false && TT > 0) TT -= step;
else if(HT > 0) HT -= step;
}
HAL_Delay(100); // 防止重复触发
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
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();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
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();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
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();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief I2C1 Initialization Function
* @param None
* @retval None
*/
static void MX_I2C1_Init(void)
{
/* USER CODE BEGIN I2C1_Init 0 */
/* USER CODE END I2C1_Init 0 */
/* USER CODE BEGIN I2C1_Init 1 */
/* USER CODE END I2C1_Init 1 */
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();
}
/* USER CODE BEGIN I2C1_Init 2 */
/* USER CODE END I2C1_Init 2 */
}
/**
* @brief TIM3 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 72-1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 1000-1;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
HAL_TIM_MspPostInit(&htim3);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(dht11_GPIO_Port, dht11_Pin, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, BUZZ_Pin|LED1_Pin|LED2_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, LED3_Pin|LED4_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : dht11_Pin */
GPIO_InitStruct.Pin = dht11_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(dht11_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : BUZZ_Pin LED1_Pin LED2_Pin */
GPIO_InitStruct.Pin = BUZZ_Pin|LED1_Pin|LED2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PA6 */
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : LED3_Pin LED4_Pin */
GPIO_InitStruct.Pin = LED3_Pin|LED4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pins : K1_Pin K2_Pin K3_Pin K4_Pin */
GPIO_InitStruct.Pin = K1_Pin|K2_Pin|K3_Pin|K4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
Flash 存储操作需要注意以下几点:
- Flash 写入前需要先擦除
- 避免频繁擦写,延长 Flash 寿命
- 数据存储格式要合理,确保读写一致
- 增加数据有效性检查,防止无效数据
技术难点与解决方案
DHT11 时序控制
DHT11 采用严格的单总线时序,对时序要求非常高。在 STM32 上实现时,由于 Cortex-M3 内核的中断响应和任务调度可能影响时序精度,导致数据读取失败。
解决方案:
- 优化 DHT11 驱动代码,减少非必要操作
- 在数据读取期间禁用中断,确保时序准确
- 增加重试机制,当读取失败时重新尝试
- 精确控制延时,使用 CPU 周期计数代替 HAL_Delay
OLED 显示缓冲区管理
128x64 的 OLED 显示屏需要较大的显示缓冲区,而 STM32F103 的 SRAM 资源有限,直接分配全屏缓冲区可能导致内存不足。
解决方案:
- 采用分页显示方式,每次只更新需要变化的部分
- 动态分配显示缓冲区,只在需要时申请内存
- 优化显示数据结构,减少内存占用
- 实现高效的显示更新算法,减少重绘区域
按键消抖与长按检测
机械按键存在抖动问题,长按检测需要准确的时间测量,处理不当会导致按键响应不灵敏或误触发。
解决方案:
- 采用软件消抖,通过延时和多次采样确认按键状态
- 使用 HAL_GetTick () 获取精确的时间戳,实现准确的长按检测
- 设计合理的按键状态机,处理按下、抖动、长按和释放等状态
- 增加防重复触发机制,避免短时间内多次响应
总结与改进方向
系统特点
- 功能完整:实现了环境监测、数据显示、用户交互和设备控制等功能
- 人机交互友好:支持按键短按和长按操作,OLED 显示清晰直观
- 数据持久化:使用 Flash 存储阈值数据,实现掉电保存
- 可扩展性强:模块化设计便于功能扩展和维护
改进方向
- 增加通信接口:添加 WiFi 或蓝牙模块,实现远程监控和控制
- 优化低功耗:增加睡眠模式,降低系统功耗,适合电池供电场景
- 增强数据处理:添加数据滤波和趋势分析功能,提高数据准确性
- 扩展传感器:增加光照、气压等传感器,实现更全面的环境监测
- 完善报警功能:添加多种报警方式,如短信报警、APP 推送等
应用场景
该系统可广泛应用于以下场景:
- 智能家居环境监测与控制
- 温室大棚温湿度监控
- 仓储物流环境监测
- 实验室环境监控
- 小型气象站