基于STM32的家庭/室外环境监测系统(单片机毕设)

目录

前言

一、项目介绍和演示视频

1. 实物图

2. 演示视频

3. 概述

二、硬件框架

1. 硬件模块

2. 原理图以及PCB

三、软件框架

1. 0.96寸OLED屏幕(SSD1306)

1.1 CubeMX配置

1.2 初始化代码

2. MQ-2烟雾传感器

2.1 CubeMX配置

2.2 初始化代码

3. DHT11温湿度模块

3.1 驱动代码

4. HC-05蓝牙模块

4.1 CubeMX配置

4.2 空闲中断回调函数

5. 有源蜂鸣器和TB6612电机驱动模块

5.1 CubeMX配置

5.2 核心代码

四、蓝牙APP界面设计

1. UI界面设计

2. 逻辑界面设计


前言

源代码下载链接:

需要实物的可以私信博主或者在文章最下方添加好友。

一、项目介绍和演示视频

1. 实物图

2. 演示视频

基于STM32的家庭/室外环境监测系统(单片机毕设)

3. 概述

        主控为stm32f103c8t6。使用DHT11温湿度传感器和MQ-2烟雾传感器,读取并实时刷新在0.96寸OLED屏幕上,同时通过蓝牙模块HC-05使用串口通信将数据上传到上位机(自制蓝牙APP)。可手动控制蜂鸣器以及电机作为报警器和风扇;在自动预警模式下,监测到温度高出设定的阈值后打开风扇降温;当监测到烟雾浓度高出设定阈值后将关闭风扇防止火情蔓延,并开启蜂鸣器报警,上位机同步更新报警状态。


二、硬件框架

1. 硬件模块

stm32f103c8t6最小系统板 0.96寸OLED屏幕
MQ-2烟雾传感器(5V) DHT11温湿度传感器
有源蜂鸣器 HC-05蓝牙模块(5V)

TB6612电机驱动模块(5V)

直流电机(5V)
面包板

排母若干


2. 原理图以及PCB

原理图:

PCB:


三、软件框架

1. 0.96寸OLED屏幕(SSD1306)

        见我上传的资源:OLED驱动代码,设置成免费下载了。

1.1 CubeMX配置

        勾选I2C1,设置为快速模式即可。

1.2 初始化代码

//oled初始化
HAL_Delay(20);
//屏幕启动比stm32要慢,上电延时20ms
OLED_Init();

2. MQ-2烟雾传感器

        使用ADC的连续转换模式,可参考这篇博客:HAL库教程

2.1 CubeMX配置

        记得配置Continuous Conversion ModeEnabled,这样就开启了ADC的连续转换模式。

2.2 初始化代码

//烟雾传感器初始化
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);

3. DHT11温湿度模块

        由于该模块是单数据线,需要在代码里不断改变引脚状态,因此不需要在CubeMX里配置,我这里用的是PA2引脚。

3.1 驱动代码

dht11.c:

#include "dht11.h"

uint8_t datas[5];//空气温湿度数据

void delay_us(uint16_t cnt)
{
	uint8_t i;
	while(cnt)
	{
		for (i = 0; i < 10; i++)
		{
			
		}	
		cnt--;
	}
}
 
void DHT_GPIO_Init(uint32_t Mode)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	__HAL_RCC_GPIOA_CLK_ENABLE();
	
	GPIO_InitStruct.Pin = GPIO_PIN_2;
	GPIO_InitStruct.Mode = Mode;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
 
void DHT11_Start(void)
{
	DHT_GPIO_Init(GPIO_MODE_OUTPUT_PP);
	DHT_HIGHT;
	DHT_LOW;
	HAL_Delay(30);
	DHT_HIGHT;
	
	DHT_GPIO_Init(GPIO_MODE_INPUT);
	while(DHT_VALUE);
	while(!DHT_VALUE);
	while(DHT_VALUE);
}
 
 
void Read_Data_From_DHT(void)
{
	int i;//轮
	int j;//每一轮读多少次
	char tmp;
	char flag;
	DHT11_Start();
	DHT_GPIO_Init(GPIO_MODE_INPUT);
	for(i= 0;i < 5;i++)
	{
		for(j=0;j<8;j++)
		{
			while(!DHT_VALUE);//等待卡g点
			delay_us(40);
			if(DHT_VALUE == 1)
			{
				flag = 1;
				while(DHT_VALUE);
			}
			else
			{
				flag = 0;
			}
			tmp = tmp << 1;
			tmp |= flag;
		}
		datas[i] = tmp;
	}
}

dht11.h:

#ifndef __DHT11_H__
#define __DHT11_H__

#include "main.h"

#define DHT_HIGHT HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET)
#define DHT_LOW HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET)
#define DHT_VALUE HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2)

extern uint8_t datas[5];

void Read_Data_From_DHT(void);

#endif

        后续直接调用Read_Data_From_DHT函数读取数据就好了,数据会存放在datas数组里:datas[0]是湿度的整数数据、datas[1]是湿度的小数数据、datas[2]是温度的整数数据、datas[3]是温度的小数数据、datas[5]是校验数据。


4. HC-05蓝牙模块

        使用串口收发数据,借助蓝牙APP,可以把这个模块当作平常用的CH340模块来用。

4.1 CubeMX配置

        使用串口1收发数据,波特率为115200,由于我们需要接收不定长数据,因此还要用到串口空闲中断,不妨使用DMA模式下的空闲中断:

        点开DMA设置,为接收和发送都创建DMA通道(默认即可),并确保打开了串口中断:

4.2 空闲中断回调函数

//串口接收buffer
#define RX_BUF_SIZE 50
uint8_t receiveData[RX_BUF_SIZE] = "";

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    //每次进入回调函数之前判断是哪个串口触发的中断
    if(huart == &huart1)
    {	

		HAL_UARTEx_ReceiveToIdle_DMA(&huart1, (uint8_t *)receiveData, sizeof(receiveData));
		__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
    }
}

5. 有源蜂鸣器和TB6612电机驱动模块

        全部设置为推挽输出即可,不需要对电机进行调速。

5.1 CubeMX配置

        PA3为蜂鸣器引脚,PA4~6分别为TB6612的PWMA、AIN2、AIN1。电机驱动模块主要操作的是AIN2和AIN1引脚,要让电机旋转只需要随便拉低一个引脚即可,蜂鸣器也是低电平触发。


5.2 核心代码

/* 头文件包含 */
#include "main.h"        // HAL库主头文件
#include "adc.h"         // ADC驱动
#include "dma.h"         // DMA驱动
#include "i2c.h"         // I2C驱动(用于OLED)
#include "usart.h"       // 串口驱动
#include "gpio.h"        // GPIO驱动
#include "dht11.h"       // DHT11温湿度传感器驱动
#include "oled.h"        // OLED显示驱动
#include <stdio.h>       // 标准输入输出(用于printf)
#include <string.h>      // 字符串操作

/* 系统状态宏定义 */
#define OFF 0            // 关闭状态
#define ON  1            // 开启状态

/* 蜂鸣器控制宏 */
#define BUZZER_ON HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET)  // PA3低电平触发蜂鸣器
#define BUZZER_OFF HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET)   // PA3高电平关闭蜂鸣器

/* 全局变量声明 */
float smoke = 0.0;       // 烟雾浓度值(百分比)

#define RX_BUF_SIZE 50   // 串口接收缓冲区大小
uint8_t receiveData[RX_BUF_SIZE] = ""; // 串口接收缓冲区

// 设备状态标志
uint8_t Buzzer_State = OFF;    // 蜂鸣器状态
uint8_t Fan_State = OFF;       // 风扇状态
uint8_t Auto_Alarm_State = OFF;// 自动报警模式状态

// 状态显示字符串(OLED用)
char State_String[2][5] = {"OFF", "ON"}; 

/* 自定义printf输出重定向 */
int fputc(int ch, FILE *f)
{      
    HAL_UART_Transmit(&huart1, (const uint8_t *)&ch, 1, HAL_MAX_DELAY);  
    return ch;
}

/* 串口接收完成回调函数 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    char buffer[50] = "";
    memcpy(buffer, receiveData, sizeof(receiveData)); // 复制接收数据到临时缓冲区
    
    if(huart == &huart1)
    {    
        buffer[Size] = '\0'; // 添加字符串终止符
        
        /* 自动报警模式控制 */
        if(strcmp(buffer, "Auto_On\r\n") == 0) {
            Auto_Alarm_State = ON;
            HAL_UART_Transmit_DMA(&huart1, (uint8_t *)buffer, Size);
        }
        else if(strcmp(buffer, "Auto_Off\r\n") == 0) {
            // 关闭所有输出设备
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // 风扇IN1
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET); // 风扇IN2
            BUZZER_OFF;
            
            // 更新状态标志
            Buzzer_State = OFF;
            Fan_State = OFF;
            Auto_Alarm_State = OFF;        
            
            // 返回确认信息
            printf("Buzzer_Off\r\nFan_Off\r\nAuto_Off\r\n");
        }

        /* 手动控制模式处理 */
        if(Auto_Alarm_State == OFF) {
            // 风扇控制
            if(strcmp(buffer, "Fan_On\r\n") == 0) {
                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // IN1=0
                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);   // IN2=1(正转)
                Fan_State = ON;
                HAL_UART_Transmit_DMA(&huart1, (uint8_t *)buffer, Size);
            }
            else if(strcmp(buffer, "Fan_Off\r\n") == 0) {
                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);   // IN1=1
                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);   // IN2=1(刹车)
                Fan_State = OFF;
                HAL_UART_Transmit_DMA(&huart1, (uint8_t *)buffer, Size);
            }
            
            // 蜂鸣器控制
            else if(strcmp(buffer, "Buzzer_On\r\n") == 0) {
                BUZZER_ON;
                Buzzer_State = ON;
                HAL_UART_Transmit_DMA(&huart1, (uint8_t *)buffer, Size);
            }
            else if(strcmp(buffer, "Buzzer_Off\r\n") == 0) {
                BUZZER_OFF;
                Buzzer_State = OFF;
                HAL_UART_Transmit_DMA(&huart1, (uint8_t *)buffer, Size);
            }
        }
        
        // 重新启动DMA接收
        HAL_UARTEx_ReceiveToIdle_DMA(&huart1, receiveData, sizeof(receiveData));
        __HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); // 禁用传输过半中断
    }
}

/* 自动报警处理函数 */
void Auto_Alarm(void)
{
    /* 烟雾浓度报警(阈值60%) */
    if(smoke > 60) {
        BUZZER_ON;
        if(Buzzer_State == OFF) { // 状态变更时上报
            Buzzer_State = ON;
            printf("Buzzer_On\r\n");
        }
    } else {
        BUZZER_OFF;
        if(Buzzer_State == ON) {
            Buzzer_State = OFF;
            printf("Buzzer_Off\r\n");
        }
    }
    
    /* 温度控制逻辑(阈值30℃) */
    if(datas[2] >= 30 && smoke < 60) { // 温度高且无烟雾危险时开启风扇
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 正转
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);
        if(Fan_State == OFF) {
            printf("Fan_On\r\n");
            Fan_State = ON;
        }
    } else { // 关闭风扇
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, GPIO_PIN_SET);        
        if(Fan_State == ON) {
            printf("Fan_Off\r\n");
            Fan_State = OFF;
        }
    }
}

/* 主函数 */
int main(void)
{
    // 硬件初始化
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART1_UART_Init();
    MX_I2C1_Init();
    MX_ADC1_Init();

    /* 外设初始化 */
    HAL_Delay(20); // 等待硬件稳定
    OLED_Init();   // OLED显示屏初始化
    
    // ADC校准和启动
    HAL_ADCEx_Calibration_Start(&hadc1);
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
    
    // 启动串口DMA接收
    HAL_UARTEx_ReceiveToIdle_DMA(&huart1, receiveData, sizeof(receiveData));
    __HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);

    /* 主循环 */
    while (1)
    {
        OLED_NewFrame(); // 准备新显示帧
        
        // 传感器数据读取
        Read_Data_From_DHT(); // 获取温湿度数据
        smoke = (HAL_ADC_GetValue(&hadc1) / 4095.0) * 100.0; // 计算烟雾浓度
        
        // 自动报警模式处理
        if(Auto_Alarm_State == ON) {
            Auto_Alarm();
        }
        
        /* OLED显示内容格式化 */
        char Tem_mes[10], Hum_mes[10], Smo_mes[10], Sta_mes[20], BlueTooth_mes[30];
        sprintf(Tem_mes, "Tem:%d.%d", datas[2], datas[3]);    // 温度显示
        sprintf(Hum_mes, "Hum:%d.%d%%", datas[0], datas[1]);  // 湿度显示
        sprintf(Smo_mes, "Smoke:%.1f%%", smoke);              // 烟雾浓度
        sprintf(Sta_mes, "A:%s B:%s F:%s",                   // 状态显示
                State_String[Auto_Alarm_State], 
                State_String[Buzzer_State], 
                State_String[Fan_State]);
        sprintf(BlueTooth_mes, "NULL;%d.%d;%d.%d;%.1f;NULL\r\n", // 蓝牙数据格式
                datas[2], datas[3], datas[0], datas[1], smoke);
        
        /* OLED显示更新 */
        OLED_PrintASCIIString(0, 0, Tem_mes, &afont16x8, OLED_COLOR_NORMAL);
        OLED_PrintString(65, 0, "℃", &font16x16, OLED_COLOR_NORMAL);
        OLED_PrintASCIIString(0, 17, Hum_mes, &afont16x8, OLED_COLOR_NORMAL);
        OLED_PrintASCIIString(0, 33, Smo_mes, &afont16x8, OLED_COLOR_NORMAL);
        OLED_PrintASCIIString(0, 49, Sta_mes, &afont12x6, OLED_COLOR_NORMAL);
        OLED_ShowFrame(); // 刷新显示
        
        /* 蓝牙数据传输 */
        HAL_UART_Transmit_DMA(&huart1, (uint8_t *)BlueTooth_mes, strlen(BlueTooth_mes));
        
        HAL_Delay(1000); // 主循环周期1秒
    }
}

/* 注意事项:
1. GPIO分配:
   - PA3: 蜂鸣器控制
   - PA5/PA6: TB6612电机驱动控制引脚(IN1/IN2)
   - 确保实际硬件连接与代码一致

2. 传感器数据格式:
   - datas数组来自DHT11驱动,索引:
     0: 湿度整数部分
     1: 湿度小数部分 
     2: 温度整数部分
     3: 温度小数部分

3. 蓝牙数据协议:
   "NULL;温度;湿度;烟雾;NULL\r\n" 格式示例:
   "NULL;25.5;60.0;30.5;NULL\r\n"

4. 改进建议:
   - 增加传感器数据校验
   - 添加看门狗防止死机
   - 使用RTOS进行任务管理
   - 添加EEPROM存储报警阈值
*/

四、蓝牙APP界面设计

1. UI界面设计

控件如下:

2. 逻辑界面设计

蓝牙连接逻辑:

按键发送逻辑:

接收数据逻辑: