news 2026/4/18 0:04:42

STM32项目分享:粮仓环境监测系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32项目分享:粮仓环境监测系统

目录

一、前言

二、项目简介

1.功能详解

2.主要器件

三、原理图设计

四、PCB硬件设计

PCB图

五、程序设计

六、实验效果 ​

七、包含内容

项目分享


一、前言

项目成品图片:

哔哩哔哩视频链接:

https://www.bilibili.com/video/BV1pymdB2ELT/?spm_id_from=333.1387.search.video_card.click&vd_source=199aed5297a00e80e1faf7e270afe8d7

(资料分享见文末)

二、项目简介

1.功能详解

基于STM32的粮仓环境监测系统

功能如下:

  1. 环境采集:温湿度、火焰、烟雾、是否有人闯入、二氧化碳
  2. 显示功能:环境数据显示在OLED屏幕上
  3. 模式切换:通过按键可以切换手动模式和自动模式
  4. 自动模式:环境温度或湿度高于上限开启风扇降温或除湿;火焰传感器检测到发生火灾时蜂鸣器报警;烟雾传感器检测异常时蜂鸣器报警;光电红外传感器检测有人且防盗模式开启时语音播报“警告,有人闯入”;SGP30传感器检测到二氧化碳浓度超标时打开通风口(步进电机模拟)并开启风扇通风且蜂鸣器报警。
  5. 手动模式:可通过按键切换为手动模式,可以控制LED灯、风扇、通风口和蜂鸣器的开关。
  6. 阈值调节:可设置温度阈值、湿度阈值、二氧化碳阈值、防盗模式(开或关)
  7. 机智云APP:通过WIFI连接手机机智云APP,APP可以接收环境信息数据、模式切换、阈值调节和手动模式下外设控制

2.主要器件

  • STM32F103C8T6最小系统板
  • OLED显示屏(4针IIC协议)
  • DHT11温湿度传感器
  • MQ-2烟雾传感器
  • 二氧化碳传感器
  • ESP8266-01S(WIFI模块)
  • 继电器
  • 步进电机
  • 风扇模块
  • 蜂鸣器
  • LED灯

三、原理图设计

四、PCB硬件设计

PCB图

五、程序设计

#include "stm32f10x.h" #include "led.h" #include "beep.h" #include "usart.h" #include "delay.h" #include "oled.h" #include "key.h" #include "Modules.h" #include "adcx.h" #include "flash.h" #include "usart2.h" #include "usart3.h" #include "IR.h" #include "HW.h" #include "sgp30.h" #include "dht11.h" #include "string.h" #include "relay.h" #include "timer.h" #include "TIM3.h" #include "TIM2.h" #include "gizwits_product.h" #include "gizwits_protocol.h" #include "stepmotor.h" /****************异方辰电子工作室****************** STM32 *文件 : STM32粮仓环境检测 *版本 : V1.0 *日期 : 2025.12.10 *MCU : STM32F103C8T6 *接口 : 见代码 *BILIBILI : 异方辰电子 *小红书 : 异方辰电子 *CSDN : 异方辰电子 *授权IP : 辰哥单片机设计、异方辰、YFC电子、北海单片机设计 **********************BEGIN***********************/ #define KEY_Long1 11 #define KEY_1 1 #define KEY_2 2 #define KEY_3 3 #define KEY_4 4 #define AUTO_MODE 1 // 自动模式 #define MANUAL_MODE 2 // 手动模式 #define SETTINGS_MODE 3 // 设置模式 #define FLASH_START_ADDR 0x0801f000 // Flash阈值存储起始地址(STM32F103C8T6 Flash尾部区域) //#define CONFIG_MODE_WINDOW 3000 #ifndef GIZWITS_MODE_NORMAL #define GIZWITS_MODE_NORMAL 0 // 正常模式 #endif #ifndef GIZWITS_MODE_SOFTAP #define GIZWITS_MODE_SOFTAP 1 // 软AP配网模式 #endif #ifndef GIZWITS_MODE_AIRLINK #define GIZWITS_MODE_AIRLINK 2 // 一键配网模式 #endif // 全局变量定义 extern SensorModules sensorData; // 传感器数据结构体(存储温度、光伏电压/电流/功率/转换率) extern SensorThresholdValue Sensorthreshold; // 传感器阈值结构体(存储温度、电压、电流阈值) extern DriveModules driveData; // 驱动器状态结构体(存储LED、蜂鸣器、继电器开关状态) uint8_t mode = 1; // 系统模式:1=自动模式,2=手动模式,3=设置模式(默认自动模式) u8 dakai; // 串口3通信传递变量 u8 Flag_dakai; // 串口3接收完成标志位 uint8_t is_secondary_menu = 0; // 二级菜单标志位:0=一级菜单,1=二级菜单 uint8_t secondary_pos = 1; // 二级菜单光标位置: uint8_t secondary_type = 0; // 二级菜单类型 //float pa0_voltage = 0.0f; uint8_t count_m = 1; // 手动模式菜单计数 uint8_t count_s = 1; // 设置模式菜单计数 //extern unsigned char p[16]; // 温度显示字符串缓冲区 uint8_t last_mode = 0; // 上一次系统模式(用于模式切换时清屏刷新显示) extern dataPoint_t currentDataPoint; uint8_t send_data[] = "A7:00001";//语音播放曲目1 uint8_t send_data1[] = "A7:00002";//语音播放曲目2 uint8_t motor_flag_last = 0; // 上一次MOTOR_Flag的值(初始和driveData.MOTOR_Flag一致) uint8_t motor_rotate_done = 1; // 转动执行完成标志:1=已完成(初始完成,上电不执行) /** * @brief 主函数(系统初始化、主循环逻辑) * @param 无 * @retval int:返回值(实际无意义,符合C语言标准) */ int main(void) { SystemInit(); delay_init(72); ADCX_Init(); LED_Init(); BEEP_Init(); BEEP_OFF; USART1_Config(); USART2_Config(); USART3_Config(); Key_Init(); HW_Init(); IR_Init(); SGP30_Init(); DHT11_Init(); MOTOR_Init(); OLED_Init(); RELAY_Init(); OLED_Clear(); // 从Flash读取阈值(上电加载掉电保存的阈值) delay_ms(100); // 延时100ms,确保Flash稳定 FLASH_ReadThreshold(); TIM2_Init(72-1,1000-1); // 初始化定时器2(2ms定时中断,用于系统滴答计数) TIM3_Int_Init(1000-1,72-1); // 初始化定时器3(1ms定时中断,用于按键扫描/数据刷新) // 状态管理静态变量(主循环中保持状态) static uint32_t last_sensor_time = 0; // 传感器扫描时间戳(控制扫描频率) static uint32_t last_display_time = 0; // 显示刷新时间戳(控制显示频率) // 阈值合法性校验 if (Sensorthreshold.tempValue < 20 || Sensorthreshold.tempValue > 50 || Sensorthreshold.humiValue < 30 || Sensorthreshold.humiValue > 70 || Sensorthreshold.CO2Value < 500 || Sensorthreshold.CO2Value > 1500 || Sensorthreshold.SmogeValue > 1000 || Sensorthreshold.anti_theft_mode > 1) { // 初始化默认值 Sensorthreshold.tempValue = 30; Sensorthreshold.humiValue = 50; Sensorthreshold.CO2Value = 1500; Sensorthreshold.SmogeValue = 20; Sensorthreshold.anti_theft_mode = 0; // 默认防盗模式关 // 写入默认值到Flash FLASH_Unlock(); FLASH_ProgramHalfWord(FLASH_START_ADDR, Sensorthreshold.tempValue); FLASH_ProgramHalfWord(FLASH_START_ADDR + 2, Sensorthreshold.humiValue); FLASH_ProgramHalfWord(FLASH_START_ADDR + 4, Sensorthreshold.CO2Value); FLASH_ProgramHalfWord(FLASH_START_ADDR + 6, Sensorthreshold.SmogeValue); FLASH_ProgramHalfWord(FLASH_START_ADDR + 8, Sensorthreshold.anti_theft_mode); FLASH_Lock(); } USART3_SendString("AF:30"); //音量调到最大 delay_ms(300); USART3_SendString("A7:00001"); //欢迎使用 userInit(); // 机智云用户初始化(数据点初始化等) gizwitsInit(); // 机智云协议初始化(WiFi配网、数据通信初始化) delay_ms(200); printf("Start \n"); gizwitsHandle((dataPoint_t *)&currentDataPoint); delay_ms(200); // gizwitsSetMode(WIFI_SOFTAP_MODE); ScanGizwitsMode(); delay_ms(200); while (1) // 主循环(无限循环,处理系统所有逻辑) { userHandle(); gizwitsHandle((dataPoint_t *)&currentDataPoint); userHandle(); // ==================== 获取当前系统时间(基于定时器2滴答计数)==================== uint32_t current_time = delay_get_tick(); // ==================== 控制传感器扫描频率(每200ms扫描一次)==================== if(current_time - last_sensor_time > 100) // 100个定时器2滴答 = 100*2ms=200ms { SensorScan(); // 扫描传感器,获取最新温度、光伏电压/电流/功率数据 // Update_Display_Parts(); // 拆分传感器数据为整数+小数部分(用于OLED显示) last_sensor_time = current_time; // 更新传感器扫描时间戳 } // ==================== 立即处理按键(模式切换、参数调整等)==================== uint8_t current_key_num = KeyNum; // 保存当前按键值(避免被后续逻辑修改) // 模式切换按键处理(仅响应模式切换相关按键) if(current_key_num != 0) { switch(mode) { case AUTO_MODE: // 当前为自动模式 if(current_key_num == KEY_1) // 按键1短按:切换到手动模式 { mode = MANUAL_MODE; count_m = 1; // 手动模式菜单页码重置为1(灯光控制) // 切换到手动模式时,关闭LED和蜂鸣器 driveData.LED_Flag = 0; driveData.BEEP_Flag = 0; driveData.RELAY_Flag = 0; // 同步初始状态到机智云 currentDataPoint.valueLED = driveData.LED_Flag; currentDataPoint.valueBEEP= driveData.BEEP_Flag; currentDataPoint.valueRELAY = driveData.RELAY_Flag; KeyNum = 0; // 清空按键标识 } else if(current_key_num == KEY_Long1) // 按键1长按:切换到设置模式 { mode = SETTINGS_MODE; count_s = 1; // 设置模式菜单页码重置为1 KeyNum = 0; // 清空按键标识 } break; case MANUAL_MODE: // 当前为手动模式 if(current_key_num == KEY_1) // 按键1短按:切换到自动模式 { mode = AUTO_MODE; KeyNum = 0; // 清空按键标识 } break; case SETTINGS_MODE: // 当前为设置模式 // 设置模式内部按键在模式处理中单独处理 break; } } // ==================== 模式切换时清屏并刷新菜单 ==================== if(last_mode != mode) { OLED_Clear(); // 清屏,避免不同模式菜单重叠 // 绘制新模式的固定菜单标题 switch(mode) { case AUTO_MODE: OLED_autoPage1(); // 绘制自动模式第一页固定菜单 break; case MANUAL_MODE: OLED_manualPage1(); // 绘制手动模式第一页固定菜单 break; case SETTINGS_MODE: OLED_settingsPage1(); // 绘制设置模式第一页固定菜单 break; } OLED_Refresh(); // 立即刷新OLED显示 last_mode = mode; // 更新上一次模式为当前模式 } // ==================== 各模式核心逻辑处理 ==================== switch(mode) { case AUTO_MODE: // 自动模式 // 显示传感器动态数据 SensorDataDisplay1(); AutoControl(); // 自动控制LED、蜂鸣器、继电器 Control_Manager(); // 执行驱动器控制(硬件操作) break; case MANUAL_MODE: // 手动模式 { static uint8_t manual_page_initialized = 0; // 手动模式页面初始化标志 static uint8_t last_manual_count = 0; // 上一次手动模式菜单页码 static uint8_t last_LED_Flag = 0; // 上一次LED状态 static uint8_t last_BEEP_Flag = 0; // 上一次蜂鸣器状态 static uint8_t force_refresh = 0; // 强制刷新标志(模式切换时使用) // 模式切换到手动模式时,初始化状态 if(last_mode != mode) { manual_page_initialized = 0; last_manual_count = 0; last_LED_Flag = driveData.LED_Flag; last_BEEP_Flag = driveData.BEEP_Flag; force_refresh = 1; // 设置强制刷新标志 count_m = 1; // 光标默认选中灯光控制 // 初始状态:关闭LED和蜂鸣器 driveData.LED_Flag = 0; driveData.BEEP_Flag = 0; driveData.RELAY_Flag = 0; currentDataPoint.valueLED = driveData.LED_Flag; currentDataPoint.valueBEEP= driveData.BEEP_Flag; currentDataPoint.valueRELAY = driveData.RELAY_Flag; } uint8_t current_manual_count = SetManual(); // 获取当前手动模式菜单页码 // 检测设备状态是否变化(变化则需要刷新显示) uint8_t need_refresh = 0; if(driveData.LED_Flag != last_LED_Flag || driveData.BEEP_Flag != last_BEEP_Flag) { need_refresh = 1; last_LED_Flag = driveData.LED_Flag; // 更新LED状态 last_BEEP_Flag = driveData.BEEP_Flag; // 更新蜂鸣器状态 } // 页面未初始化、页码变化、设备状态变化或强制刷新时,重绘页面 if(!manual_page_initialized || current_manual_count != last_manual_count || need_refresh || force_refresh) { OLED_manualPage1(); // 绘制固定菜单标题 OLED_manualOption(current_manual_count); // 显示当前光标位置 ManualSettingsDisplay1(); // 显示设备状态(开/关) manual_page_initialized = 1; // 标记页面已初始化 last_manual_count = current_manual_count; // 更新上一次页码 force_refresh = 0; // 清除强制刷新标志 OLED_Refresh(); // 强制刷新显示 } // 处理手动模式按键(控制设备开关) if(current_key_num != 0) { ManualControl(current_manual_count); // 执行手动控制逻辑 OLED_manualPage1(); // 重绘固定菜单标题 OLED_manualOption(current_manual_count); // 重绘光标 ManualSettingsDisplay1(); // 重绘设备状态 OLED_Refresh(); // 按键操作后立即刷新显示 KeyNum = 0; // 清空按键标识 } // 确保显示内容始终正确(冗余绘制,避免显示异常) OLED_manualPage1(); // 绘制固定菜单标题 OLED_manualOption(current_manual_count); // 显示光标 ManualSettingsDisplay1(); // 显示设备状态 Control_Manager(); // 执行驱动器控制(硬件操作) break; } case SETTINGS_MODE: // 设置模式 { static uint8_t is_threshold_page_inited = 0; // 设置模式页面初始化标志 static uint8_t last_count_s = 1; // 新增:记录上一次的页码,用于检测页面切换 uint8_t curr_count_s = SetSelection(); // 获取当前设置模式菜单页码 // 检测页码是否切换(跨页),切换则清屏并重置初始化标志 if(curr_count_s != last_count_s) { if((curr_count_s <=4 && last_count_s ==5) || (curr_count_s ==5 && last_count_s <=4)) { OLED_Clear(); // 仅跨页清屏 is_threshold_page_inited = 0; } last_count_s = curr_count_s; } // 处理设置模式内部按键(修改阈值、返回自动模式) if(current_key_num != 0) { if (is_secondary_menu == 1) // 处于二级菜单(预留扩展) { // 二级菜单按键处理(暂未实现) if (current_key_num == KEY_2 || current_key_num == KEY_3 || current_key_num == KEY_4) { OLED_Refresh(); // 刷新显示 KeyNum = 0; // 清空按键标识 } else if (current_key_num == KEY_1) // 按键1短按:返回一级菜单 { is_secondary_menu = 0; secondary_pos = 1; OLED_Clear(); // 清屏 if(curr_count_s == 5) { OLED_settingsPage2(); // 绘制第二页 SettingsThresholdDisplay2(); } else { OLED_settingsPage1(); // 绘制第一页 SettingsThresholdDisplay1(); } OLED_settingsOption(curr_count_s); // 重绘光标 OLED_Refresh(); // 刷新显示 KeyNum = 0; // 清空按键标识 } } else // 处于一级菜单 { if (current_key_num == KEY_3 || current_key_num == KEY_4) // 按键3/4:修改阈值 { ThresholdSettings(curr_count_s); // 执行阈值修改逻辑 if(curr_count_s == 5) { SettingsThresholdDisplay2(); // 第二页(防盗模式) } else { SettingsThresholdDisplay1(); // 第一页(温/湿/CO2/烟雾) } OLED_Refresh(); // 刷新显示 KeyNum = 0; // 清空按键标识 } else if (current_key_num == KEY_1) // 按键1短按:返回自动模式 { mode = AUTO_MODE; is_threshold_page_inited = 0; // 重置页面初始化标志 last_count_s = 1; // 重置上一次页码 // 将修改后的阈值写入Flash(掉电保存) FLASH_W(FLASH_START_ADDR, Sensorthreshold.tempValue, Sensorthreshold.humiValue, Sensorthreshold.CO2Value, Sensorthreshold.anti_theft_mode,Sensorthreshold.SmogeValue); KeyNum = 0; // 清空按键标识 } } } // 正常显示逻辑(一级菜单) if (is_secondary_menu == 1) { // 二级菜单显示(暂未实现) } else { // 一级菜单显示 if (curr_count_s >= 1 && curr_count_s <= 5) // 页码在有效范围内 { if (is_threshold_page_inited == 0) // 页面未初始化 { if(curr_count_s == 5) { OLED_settingsPage2(); // 页码5→绘制第二页(防盗模式) } else { OLED_settingsPage1(); // 页码1-4→绘制第一页 } is_threshold_page_inited = 1; // 标记页面已初始化 } } OLED_settingsOption(curr_count_s); // 显示当前光标 // 根据页码显示对应阈值 if(curr_count_s == 5) { SettingsThresholdDisplay2(); // 第二页阈值(防盗模式) } else { SettingsThresholdDisplay1(); // 第一页阈值(温/湿/CO2/烟雾) } } break; } } // ==================== 控制OLED显示刷新频率(每50ms刷新一次)==================== if(current_time - last_display_time > 25) // 25个定时器2滴答 = 25*2ms=50ms { OLED_Refresh(); // 刷新OLED显示 last_display_time = current_time; // 更新显示刷新时间戳 } // 机智云用户逻辑处理(数据同步、配网状态处理等) userHandle(); } }

六、实验效果

七、包含内容

项目分享

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 4:16:59

Python代码示例:快速筛选偶数并求均值

请提供具体的编程语言和代码功能要求&#xff0c;以便生成符合需求的代码示例。例如&#xff1a;编程语言&#xff1a;Python代码要求&#xff1a;实现快速排序算法或编程语言&#xff1a;JavaScript代码要求&#xff1a;从API获取数据并解析JSON提供详细信息后&#xff0c;将生…

作者头像 李华
网站建设 2026/4/14 3:37:10

本章节我们将讨论如何React 表单与事件

React 表单与事件 本章节我们将讨论如何在 React 中使用表单。 HTML 表单元素与 React 中的其他 DOM 元素有所不同,因为表单元素生来就保留一些内部状态。 在 HTML 当中&#xff0c;像 <input>, <textarea>, 和 <select> 这类表单元素会维持自身状态&…

作者头像 李华
网站建设 2026/4/13 23:15:17

Android应用程序 c/c++ 崩溃排查流程三——ndk-stack工具使用

目录 一.背景 二.ndk-stack工具如何使用 一.背景 Android中使用c/c出现crash&#xff0c;或者前一篇的AddressSanitizer工具中&#xff0c;AddressSanitizer工具抓取的日志需要再定位下具体在哪一行&#xff0c;使用addr2line工具固然可以进行进一步排查&#xff0c;但是还有…

作者头像 李华
网站建设 2026/4/17 11:13:10

中小微企业一体化管理系统横向对比:从CRM到生产的全链路能力拆解

在数字化转型浪潮中&#xff0c;“业务孤岛”是中小微企业的核心痛点——CRM的销售数据无法同步到进销存&#xff0c;生产工单与财务核算脱节&#xff0c;薪资计算仍需人工核对销售业绩……一套能覆盖CRM、进销存、薪资、财务、上下游协同、生产工单的一体化系统&#xff0c;成…

作者头像 李华
网站建设 2026/4/15 15:10:00

“ThreadLocal是什么?揭秘它的隐藏机制!(Java面试必看)”

文章目录“ThreadLocal是什么&#xff1f;揭秘它的隐藏机制&#xff01;&#xff08;Java面试必看&#xff09;”一、什么是ThreadLocal&#xff1f;二、ThreadLocal的隐藏机制1. 线程的副本管理2. 变量的生命周期3. 实例的共享与隔离三、ThreadLocal的实际应用1. 用户登录态管…

作者头像 李华