一、前言
项目成品图片:

哔哩哔哩视频链接:
(资料分享见文末)
二、项目简介
1.功能详解
基于STM32厨房安全检测(蓝牙版)
功能如下:
- STM32F103C8T6单片机作为主控芯片
- 检测功能:检测环境温湿度、烟雾浓度、一氧化碳、火焰信号和人体信号
- 显示功能:OLED显示环境参数
- 手动控制:手动模式下可通过按键控制水泵、风扇、LED灯和阀门(舵机模拟)的开关
- 自动模式:自动模式下系统检测到有人时开启LED,温度高于阈值开启风扇和蜂鸣器报警,烟雾和一氧化碳超过阈值开启风扇关闭阀门并开启蜂鸣器报警,发生火灾时关闭阀门开启水泵和蜂鸣器报警
- 模式切换:通过按键可切换自动模式或手动模式
- 阈值调节:按键可调节各参数的阈值
- 蓝牙APP:通过蓝牙连接手机APP,可查看环境信息数据与控制指令下发
2.主要器件
- STM32F103C8T6单片机
- OLED 屏幕
- DHT11温湿度传感器
- MQ2烟雾传感器
- MQ7一氧化碳传感器
- 火焰传感器
- 光电红外传感器
- BT04-A蓝牙模块
- 继电器
- 有源蜂鸣器
- 水泵
- 风扇模块
- 舵机
三、原理图设计

四、PCB硬件设计
PCB图


五、程序设计
cpp
#include "stm32f10x.h" // Device header
#include "servo.h"
#include "oled.h"
#include "dht11.h"
#include "led.h"
#include "key.h"
#include "tim2.h"
#include "tim3.h"
#include "Modules.h"
#include "flash.h"
#include "HW.h"
#include "servo.h"
#include "usart2.h"
#include "usart.h"
#include "beep.h"
#include "fan.h"
#include "bump.h"
/****************异方辰电子工作室******************
STM32
* 项目 : STM32智能厨房安全系统(蓝牙版)
* 版本 : V3.0
* 日期 : 2026.4.18
* MCU : STM32F103C8T6
* 接口 : 见代码
* IP账号 : 异方辰电子/辰哥单片机设计(同BILIBILI|抖音|快手|小红书|CSDN|公众号|视频号等)
* 作者 : 辰哥
* 工作室 : 异方辰电子工作室
* 授权IP : 辰哥单片机设计、异方辰电子、YFC电子、异方辰系列
* 官方网站 : www.yfcdz.cn
**********************BEGIN***********************/
#define KEY_Long1 11
#define FLASH_START_ADDR 0x0800FC00//写入的起始地址
SensorModules sensorData; //声明传感器数据结构体变量
SensorThresholdValue Sensorthreshold; //声明传感器阈值结构体变量
DriveModules driveData;
uint8_t menu = 1; //显示菜单变量
uint8_t mode = 1; //系统模式 1自动 2手动 3设置
//系统静态变量
uint8_t count_a = 1; //自动模式按键数
uint8_t count_m = 1; //手动模式按键数
uint8_t count_s = 1; //设置模式按键数
/**
* @brief 显示菜单内容
* @param 无
* @retval 无
*/
enum
{
AUTO_MODE = 1,
MANUAL_MODE,
SETTINGS_MODE
}MODE_PAGES;
/**
* @brief 显示菜单1的固定内容
* @param 无
* @retval 无
*/
void OLED_autoPage1(void)
{
//显示"温度: C"
OLED_ShowChinese(0, 0, 0,16,1);
OLED_ShowChinese(16, 0, 2,16,1);
OLED_ShowChar(32, 0, ':',16,1);
//显示"湿度: %"
OLED_ShowChinese(64, 0, 1,16,1);
OLED_ShowChinese(80,0, 2,16,1);
OLED_ShowChar(96,0, ':',16,1);
//显示"烟雾浓度: "
OLED_ShowChinese(0, 16, 10,16,1);
OLED_ShowChinese(16,16, 11,16,1);
OLED_ShowChinese(32,16, 12,16,1);
OLED_ShowChinese(48,16, 2,16,1);
OLED_ShowChar(64,16, ':',16,1);
//显示"一氧化碳: "
OLED_ShowChinese(0, 32, 18,16,1);
OLED_ShowChinese(16,32, 19,16,1);
OLED_ShowChinese(32,32, 20,16,1);
OLED_ShowChinese(48,32, 21,16,1);
OLED_ShowChar(64,32, ':',16,1);
//显示"系统模式:"
OLED_ShowChinese(0,48,50,16,1);
OLED_ShowChinese(16,48,51,16,1);
OLED_ShowChinese(32,48,52,16,1);
OLED_ShowChinese(48,48,53,16,1);
OLED_ShowChar(64,48, ':',16,1);
}
/**
* @brief 显示菜单2的固定内容
* @param 无
* @retval 无
*/
void OLED_autoPage2(void)
{
//显示"火焰:"
OLED_ShowChinese(0 ,0,54,16,1);
OLED_ShowChinese(16,0,55,16,1);
OLED_ShowChinese(32,0,56,16,1);
OLED_ShowChinese(48,0,57,16,1);
OLED_ShowChar(64,0,':',16,1);
//显示"人体"
OLED_ShowChinese(0 ,16,58,16,1);
OLED_ShowChinese(16,16,59,16,1);
OLED_ShowChinese(32,16,56,16,1);
OLED_ShowChinese(48,16,57,16,1);
OLED_ShowChar(64,16,':',16,1);
}
/**
* @brief 显示菜单1的传感器数据
* @param 无
* @retval 无
*/
void SensorDataDisplay1(void)
{
char all_data[200]; // 字符串变长,适当扩大缓冲区
// 格式化所有数据,添加火焰和人体状态
sprintf(all_data,
"温度: %d\r\n"
"湿度: %d\r\n"
"烟雾浓度: %d\r\n"
"燃气浓度: %d\r\n"
"火焰: %s\r\n"
"人体: %s\r\n",
sensorData.temp,
sensorData.humi,
sensorData.smog,
sensorData.CO,
sensorData.fire ? "有火" : "无火",
sensorData.HW ? "有人" : "无人"
);
USART2_SendString(all_data);
delay_ms(100);
//显示温度数据
OLED_ShowNum(40,0 , sensorData.temp, 2,16,1);
//显示湿度数据
OLED_ShowNum(104, 0, sensorData.humi, 2,16,1);
//显示烟雾浓度数据
OLED_ShowNum(72, 16, sensorData.smog, 4,16,1);
//显示甲烷浓度数据
OLED_ShowNum(72, 32, sensorData.CO, 4,16,1);
//显示系统状态数据
if (mode == MANUAL_MODE)
{
OLED_ShowChinese(72, 48, 15,16,1);//手动
OLED_ShowChinese(88, 48, 17,16,1);
}
else
{
OLED_ShowChinese(72, 48, 16,16,1);//自动
OLED_ShowChinese(88, 48, 17,16,1);
}
}
/**
* @brief 显示菜单2的传感器数据
* @param 无
* @retval 无
*/
void SensorDataDisplay2(void)
{
char all_data[200]; // 字符串变长,适当扩大缓冲区
// 格式化所有数据,添加火焰和人体状态
sprintf(all_data,
"温度: %d\r\n"
"湿度: %d\r\n"
"烟雾浓度: %d\r\n"
"燃气浓度: %d\r\n"
"火焰: %s\r\n"
"人体: %s\r\n",
sensorData.temp,
sensorData.humi,
sensorData.smog,
sensorData.CO,
sensorData.fire ? "有火" : "无火",
sensorData.HW ? "有人" : "无人"
);
USART2_SendString(all_data);
delay_ms(100);
if (!sensorData.fire)
{
OLED_ShowChinese(72, 0, 61,16,1);
OLED_ShowChinese(88, 0, 54,16,1);
}
else
{
OLED_ShowChinese(72, 0, 60,16,1);//有火
OLED_ShowChinese(88, 0, 54,16,1);
}
if (!sensorData.HW)
{
OLED_ShowChinese(72, 16, 61,16,1);
OLED_ShowChinese(88, 16, 58,16,1);
}
else
{
OLED_ShowChinese(72, 16, 60,16,1);//有人
OLED_ShowChinese(88, 16, 58,16,1);
}
}
/**
* @brief 显示阈值设置界面1的固定内容
* @param 无
* @retval 无
*/
void OLED_settingsPage1(void)
{
//显示"温度阈值:"
OLED_ShowChinese(16, 0, 0,16,1);
OLED_ShowChinese(32, 0, 2,16,1);
OLED_ShowChinese(48, 0, 26,16,1);
OLED_ShowChinese(64, 0, 27,16,1);
OLED_ShowChar(80, 0, ':',16,1);
//显示"烟雾阈值:"
OLED_ShowChinese(16, 16, 10,16,1);
OLED_ShowChinese(32, 16, 11,16,1);
OLED_ShowChinese(48, 16, 26,16,1);
OLED_ShowChinese(64, 16, 27,16,1);
OLED_ShowChar(80, 16, ':',16,1);
//显示"一氧阈值:"
OLED_ShowChinese(16, 32, 18,16,1);
OLED_ShowChinese(32, 32, 19,16,1);
OLED_ShowChinese(48, 32, 26,16,1);
OLED_ShowChinese(64, 32, 27,16,1);
OLED_ShowChar(80, 32, ':',16,1);
}
/**
* @brief 显示阈值界面1的传感器数据
* @param 无
* @retval 无
*/
void settingsThresholdDisplay1(void)
{
//显示温度阈值数据
OLED_ShowNum(96, 0, Sensorthreshold.tempValue,2, 16,1);
//显示烟雾阈值数据
OLED_ShowNum(96, 16, Sensorthreshold.smogValue,4, 16,1);
//显示甲烷阈值数据
OLED_ShowNum(96, 32, Sensorthreshold.COValue,4, 16,1);
}
/**
* @brief 显示阈值界面的选择符号
* @param num 为显示的位置
* @retval 无
*/
void OLED_settingsOption(uint8_t num)
{
switch(num)
{
case 1:
OLED_ShowChar(0, 0,'>',16,1);
OLED_ShowChar(0,16,' ',16,1);
OLED_ShowChar(0,32,' ',16,1);
OLED_ShowChar(0,48,' ',16,1);
break;
case 2:
OLED_ShowChar(0, 0,' ',16,1);
OLED_ShowChar(0,16,'>',16,1);
OLED_ShowChar(0,32,' ',16,1);
OLED_ShowChar(0,48,' ',16,1);
break;
case 3:
OLED_ShowChar(0, 0,' ',16,1);
OLED_ShowChar(0,16,' ',16,1);
OLED_ShowChar(0,32,'>',16,1);
OLED_ShowChar(0,48,' ',16,1);
break;
case 4:
OLED_ShowChar(0, 0,' ',16,1);
OLED_ShowChar(0,16,' ',16,1);
OLED_ShowChar(0,32,' ',16,1);
OLED_ShowChar(0,48,'>',16,1);
break;
default:
break;
}
}
/**
* @brief 记录阈值界面下按KEY2的次数
* @param 无
* @retval 返回次数
*/
uint8_t SetSelection(void)
{
static uint8_t count = 1;
if(KeyNum == KEY_2)
{
KeyNum = 0;
count++;
if (count > 3)
{
count = 1;
}
}
return count;
}
/**
* @brief 对阈值界面的传感器阈值进行修改
* @param num 为当前用户需要更改的传感器阈值位置
* @retval 无
*/
void ThresholdSettings(uint8_t num)
{
switch (num)
{
case 1:
if (KeyNum == 3)
{
KeyNum = 0;
Sensorthreshold.tempValue++;
if (Sensorthreshold.tempValue > 99)
{
Sensorthreshold.tempValue = 0;
}
}
else if (KeyNum == 4)
{
KeyNum = 0;
Sensorthreshold.tempValue--;
if (Sensorthreshold.tempValue > 99)
{
Sensorthreshold.tempValue = 99;
}
}
break;
case 2:
if (KeyNum == 3)
{
KeyNum = 0;
Sensorthreshold.smogValue+=10;
if (Sensorthreshold.smogValue > 500)
{
Sensorthreshold.smogValue = 0;
}
}
else if (KeyNum == 4)
{
KeyNum = 0;
Sensorthreshold.smogValue-=10;
if (Sensorthreshold.smogValue > 500)
{
Sensorthreshold.smogValue = 500;
}
}
break;
case 3:
if (KeyNum == 3)
{
KeyNum = 0;
Sensorthreshold.COValue+=10;
if (Sensorthreshold.COValue > 500)
{
Sensorthreshold.COValue = 0;
}
}
else if (KeyNum == 4)
{
KeyNum = 0;
Sensorthreshold.COValue-=10;
if (Sensorthreshold.COValue > 500)
{
Sensorthreshold.COValue = 500;
}
}
break;
default: break;
}
}
/**
* @brief 系统自动模式下的运行
* @param 无
* @retval 无
*/
void AutoControl(void)
{
//LED灯开启条件
if (sensorData.HW)
{
driveData.LED_Flag=1;
}
else
{
driveData.LED_Flag=0;
}
//风扇开启条件
if(sensorData.smog > Sensorthreshold.smogValue||sensorData.CO > Sensorthreshold.COValue||sensorData.temp > Sensorthreshold.tempValue)
{
driveData.Fan_Flag=1;
}
else
{
driveData.Fan_Flag=0;
}
//蜂鸣器开启条件
if(sensorData.smog > Sensorthreshold.smogValue||sensorData.CO > Sensorthreshold.COValue||sensorData.temp > Sensorthreshold.tempValue||sensorData.fire )
{
Buzzer_ON();
}
else
{
Buzzer_OFF();
}
//阀门关闭条件
if (sensorData.fire || sensorData.smog > Sensorthreshold.smogValue || sensorData.CO > Sensorthreshold.COValue)
{
driveData.servo_Flag = 1; // 关闭阀门
}
else
{
driveData.servo_Flag = 0; // 打开阀门
}
//水泵开启条件
if (sensorData.fire)
{
driveData.Bump_Flag=1;
}
else
{
driveData.Bump_Flag=0;
}
}
/**
* @brief 记录自动模式界面下按KEY2的次数
* @param 无
* @retval 返回次数
*/
uint8_t SetAuto(void)
{
if(KeyNum == KEY_2)
{
KeyNum = 0;
count_a++;
if (count_a > 2)
{
count_a = 1;
}
OLED_Clear();
}
return count_a;
}
/**
* @brief 记录手动模式界面下按KEY2的次数
* @param 无
* @retval 返回次数
*/
uint8_t SetManual(void)
{
if(KeyNum == KEY_2)
{
KeyNum = 0;
count_m++;
if (count_m > 4)
{
count_m = 1;
}
}
return count_m;
}
void OLED_manualOption(uint8_t num)
{
switch(num)
{
case 1:
OLED_ShowChar(0, 0,'>',16,1);
OLED_ShowChar(0,16,' ',16,1);
OLED_ShowChar(0,32,' ',16,1);
OLED_ShowChar(0,48,' ',16,1);
break;
case 2:
OLED_ShowChar(0, 0,' ',16,1);
OLED_ShowChar(0,16,'>',16,1);
OLED_ShowChar(0,32,' ',16,1);
OLED_ShowChar(0,48,' ',16,1);
break;
case 3:
OLED_ShowChar(0, 0,' ',16,1);
OLED_ShowChar(0,16,' ',16,1);
OLED_ShowChar(0,32,'>',16,1);
OLED_ShowChar(0,48,' ',16,1);
break;
case 4:
OLED_ShowChar(0, 0,' ',16,1);
OLED_ShowChar(0,16,' ',16,1);
OLED_ShowChar(0,32,' ',16,1);
OLED_ShowChar(0,48,'>',16,1);
break;
default: break;
}
}
void OLED_manualPage1(void)
{
//显示"灯光"
OLED_ShowChinese(16,0,28,16,1);
OLED_ShowChinese(32,0,29,16,1);
OLED_ShowChar(64,0,':',16,1);
//显示"风扇"
OLED_ShowChinese(16,16,33,16,1);
OLED_ShowChinese(32,16,34,16,1);
OLED_ShowChar(64,16,':',16,1);
//显示"阀门"
OLED_ShowChinese(16,32,64,16,1);
OLED_ShowChinese(32,32,65,16,1);
OLED_ShowChar(64,32,':',16,1);
//显示"水泵"
OLED_ShowChinese(16,48,38,16,1);
OLED_ShowChinese(32,48,39,16,1);
OLED_ShowChar(64,48,':',16,1);
}
/**
* @brief 显示手动模式设置参数界面1
* @param 无
* @retval 无
*/
void ManualSettingsDisplay1(void)
{
if(driveData.LED_Flag)
{
OLED_ShowChinese(96,0,40,16,1); //开
}
else
{
OLED_ShowChinese(96,0,42,16,1); //关
}
if(driveData.Fan_Flag)
{
OLED_ShowChinese(96,16,40,16,1); //开
}
else
{
OLED_ShowChinese(96,16,42,16,1); //关
}
if(driveData.servo_Flag==0)
{
OLED_ShowChinese(96,32,40,16,1); //开
}
else
{
OLED_ShowChinese(96,32,42,16,1); //关
}
if(driveData.Bump_Flag)
{
OLED_ShowChinese(96,48,40,16,1); //开
}
else
{
OLED_ShowChinese(96,48,42,16,1); //关
}
}
/**
* @brief 手动模式控制函数
* @param 无
* @retval 无
*/
void ManualControl(uint8_t num)
{
switch(num)
{
case 1:
//显示外设开关
if(KeyNum == KEY_3)
{
driveData.LED_Flag = 1;
KeyNum = 0;
}
if(KeyNum == KEY_4)
{
driveData.LED_Flag = 0;
KeyNum = 0;
}
break;
case 2:
if(KeyNum == KEY_3)
{
driveData.Fan_Flag = 1;
KeyNum = 0;
}
if(KeyNum == KEY_4)
{
driveData.Fan_Flag = 0;
KeyNum = 0;
}
break;
case 3:
if(KeyNum == KEY_3)
{
//阀门开
driveData.servo_Flag = 0;
KeyNum = 0;
}
if(KeyNum == KEY_4)
{
driveData.servo_Flag = 1;
KeyNum = 0;
}
break;
case 4:
if(KeyNum == KEY_3)
{
driveData.Bump_Flag = 1;
KeyNum = 0;
}
if(KeyNum == KEY_4)
{
driveData.Bump_Flag = 0;
KeyNum = 0;
}
break;
default: break;
}
}
/**
* @brief 控制函数
* @param 无
* @retval 无
*/
void Control_Manager(void)
{
if(driveData.LED_Flag)
{
LED_ON();
}
else
{
LED_OFF();
}
if(driveData.Fan_Flag)
{
FAN_ON;
}
else
{
FAN_OFF;
}
if(driveData.servo_Flag)
{
Servo_SetAngle(0);
}
else
{
Servo_SetAngle(90);
}
if(driveData.Bump_Flag)
{
BUMP_ON;
}
else
{
BUMP_OFF;
}
}
void USART2_ProcessCmd(void) // 串口2接收函数
{
if (USART2_GetReceivedFlag())
{
uint8_t* rx_buf = USART2_GetRxBuffer();
switch(rx_buf[0])
{
case 'A': // 模式切换
if (mode == AUTO_MODE)
{
mode = MANUAL_MODE;
OLED_Clear();
count_m = 1;
driveData.LED_Flag = 0;
driveData.Fan_Flag = 0;
driveData.Bump_Flag = 0;
driveData.servo_Flag = 1;
}
else if (mode == MANUAL_MODE)
{
mode = AUTO_MODE;
OLED_Clear();
}
break;
case 'B':
if (mode == MANUAL_MODE)
{
driveData.LED_Flag = !driveData.LED_Flag;
count_m = 1;
}
break;
case 'C':
if (mode == MANUAL_MODE)
{
driveData.Fan_Flag = !driveData.Fan_Flag;
count_m = 2;
}
break;
case 'D':
if (mode == MANUAL_MODE)
{
driveData.servo_Flag = !driveData.servo_Flag;
count_m = 3;
}
break;
case 'E':
if (mode == MANUAL_MODE)
{
driveData.Bump_Flag = !driveData.Bump_Flag;
count_m = 4;
}
break;
default:
break;
}
USART2_ClearReceivedFlag();
}
}
int main(void)
{
SystemInit();
delay_init(72);
ADCx_Init();
OLED_Init();
DHT11_Init();
LED_Init();
BEEP_Init();
FAN_Init();
BUMP_Init();
HW_Init();
Key_Init();
Servo_Init();
USART2_Init();
TIM2_Init(72-1,1000-1);
KeyNum = 0;
delay_ms(1000);
OLED_Clear();
USART1_Config();
Sensorthreshold.tempValue = FLASH_R(FLASH_START_ADDR);
Sensorthreshold.smogValue = FLASH_R(FLASH_START_ADDR + 2);
Sensorthreshold.COValue = FLASH_R(FLASH_START_ADDR + 4);
if (Sensorthreshold.tempValue > 50) Sensorthreshold.tempValue = 30;
if (Sensorthreshold.smogValue > 500) Sensorthreshold.smogValue = 100;
if (Sensorthreshold.COValue > 500) Sensorthreshold.COValue = 100;
while (1)
{
USART2_ProcessCmd();
SensorScan();
// 同步界面菜单变量
if (mode == AUTO_MODE) menu = AUTO_MODE;
else if (mode == MANUAL_MODE) menu = MANUAL_MODE;
else if (mode == SETTINGS_MODE) menu = SETTINGS_MODE;
switch (menu)
{
case AUTO_MODE:
{
static uint8_t last_auto_page = 0;
uint8_t page = SetAuto();
if (page != last_auto_page) OLED_Clear();
last_auto_page = page;
if (page == 1) {
OLED_autoPage1();
SensorDataDisplay1();
} else {
OLED_autoPage2();
SensorDataDisplay2();
}
AutoControl();
Control_Manager();
if (KeyNum == KEY_1) {
KeyNum = 0;
mode = MANUAL_MODE;
count_m = 1;
OLED_Clear();
}
if (KeyNum == KEY_Long1) {
KeyNum = 0;
count_s = 1;
mode = SETTINGS_MODE;
OLED_Clear();
}
break;
}
case MANUAL_MODE:
{
uint8_t idx = SetManual();
OLED_manualPage1();
ManualSettingsDisplay1();
OLED_manualOption(idx);
ManualControl(idx);
Control_Manager();
if (KeyNum == KEY_1) {
KeyNum = 0;
mode = AUTO_MODE;
driveData.LED_Flag = 0;
driveData.Fan_Flag = 0;
driveData.servo_Flag = 0;
driveData.Bump_Flag = 0;
OLED_Clear();
}
break;
}
case SETTINGS_MODE:
{
uint8_t sel = SetSelection();
OLED_settingsPage1();
settingsThresholdDisplay1();
OLED_settingsOption(sel);
ThresholdSettings(sel);
if (KeyNum == KEY_1) {
KeyNum = 0;
mode = AUTO_MODE;
OLED_Clear();
FLASH_W(FLASH_START_ADDR, Sensorthreshold.tempValue,
Sensorthreshold.smogValue, Sensorthreshold.COValue);
}
break;
}
default: break;
}
OLED_Refresh(); // 统一刷新一次
}
}
六、实验效果 
七、包含内容
