基于STM32的烟雾报警系统Proteus仿真与HAL库编程详解(仿真+源码+分析)
2026/7/15 5:55:39 网站建设 项目流程

1. 系统概述与开发环境搭建

烟雾报警系统是智能家居和工业安全领域的基础设施,而基于STM32的方案因其性价比高、开发资源丰富成为首选。这次我们要实现的系统核心是STM32F103单片机,配合MQ-2传感器(仿真中用滑动变阻器模拟)、LCD1602显示屏和蜂鸣器报警电路。整个开发流程涉及Proteus仿真和Keil编程,对初学者特别友好。

开发环境准备其实比你想象的简单。我用的Proteus 8.9有个隐藏技巧——安装后一定要勾选STM32库支持,否则找不到F103型号。Keil MDK5的配置更要注意,安装完STM32CubeMX后,记得通过包安装器添加F1系列支持包。有次我忘了这步,调试时各种报错,折腾了半天才发现问题。

硬件连线示意图:

  • STM32F103C8T6核心板
  • ADC1通道1接滑动变阻器(模拟MQ-2)
  • PA0-PA2接三个按键(设置/加/减)
  • PB0-PB7接LCD1602数据线
  • PA8接蜂鸣器驱动三极管基极

提示:Proteus里搜索"POT-HG"可以找到更精确的滑动变阻器模型,比默认的电位器仿真效果更好。

2. Proteus仿真搭建技巧

仿真电路搭建是验证设计的关键步骤。这里有个实用技巧:在Proteus中放置STM32芯片后,先右键设置晶振频率为8MHz(与代码中HSI配置一致)。我见过有人直接用默认值,结果串口通信全乱套。

ADC采集部分的仿真有特殊处理:

  1. 放置滑动变阻器并设置为10KΩ
  2. 连接变阻器中间引脚到PA1(ADC1通道1)
  3. 添加电压探针方便观察输入变化

LCD1602的驱动电路要注意:

  • 对比度调节端必须接1KΩ电阻到地
  • 使能信号E的上升沿时间要大于450ns
  • 数据线建议加上4.7KΩ上拉电阻

蜂鸣器驱动是新手容易踩坑的地方。仿真中我用的是BUZZER元件,但实际项目要用电磁式蜂鸣器。关键点在于:

// 驱动电路原理 BEEP_Pin --[1KΩ]--> NPN三极管基极 三极管集电极接蜂鸣器负极 蜂鸣器正极接5V电源

3. HAL库编程详解

3.1 ADC采集配置

CubeMX配置ADC时有个小技巧:把采样时间设为239.5周期(对应ADC_SAMPLETIME_239CYCLES_5),这样能获得更稳定的读数。我在项目中发现,默认的1.5周期采样容易受干扰。

ADC初始化代码:

void MX_ADC1_Init(void) { hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = 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; HAL_ADC_Init(&hadc1); sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); }

实际采集时采用轮询方式更可靠:

float Get_Smoke_Value(void) { uint32_t raw_value; float voltage; HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); raw_value = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); voltage = (float)raw_value * 3.3f / 4095.0f; return voltage * 100.0f; // 转换为ppm模拟值 }

3.2 LCD1602驱动优化

标准4位模式驱动LCD1602时,我优化了写命令函数:

void LCD_WriteCmd(uint8_t cmd) { HAL_GPIO_WritePin(LCD_RS_GPIO_Port, LCD_RS_Pin, GPIO_PIN_RESET); LCD_Write4Bits(cmd>>4); // 先高4位 LCD_Write4Bits(cmd&0x0F); // 后低4位 HAL_Delay(1); // 实测需要至少37us延时 }

显示字符串时采用DMA+GPIO可以大幅降低CPU占用:

void LCD_ShowString(uint8_t x, uint8_t y, char *str) { LCD_SetCursor(x, y); while(*str) { LCD_WriteData(*str++); // 可用HAL_DMA_Start替代延时 for(volatile int i=0; i<100; i++); } }

3.3 按键中断处理

CubeMX配置外部中断时,建议将按键对应的GPIO设置为下拉输入模式,中断触发边沿选择上升沿。我遇到过按键抖动导致多次触发的问题,后来在回调函数中加入20ms防抖处理:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { static uint32_t last_tick = 0; if(HAL_GetTick() - last_tick < 20) return; if(GPIO_Pin == KEY_SET_Pin) { set_mode = !set_mode; LCD_ShowString(0,1, set_mode ? "SET:ON " : "SET:OFF"); } // ...其他按键处理 last_tick = HAL_GetTick(); }

4. 系统调试与优化

4.1 报警阈值设置

阈值调整逻辑我做了人性化改进:在设置模式下,短按加减键调整步长为10ppm,长按则步长变为50ppm。实现原理是利用定时器中断检测按键持续时间:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static uint8_t key2_cnt = 0, key3_cnt = 0; if(HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, KEY_UP_Pin)) { if(++key2_cnt > 5) { // 长按0.5秒 threshold += 50; key2_cnt = 0; } } else if(key2_cnt > 0) { threshold += 10; // 短按 key2_cnt = 0; } // 同理处理KEY_DOWN }

4.2 报警逻辑优化

原始设计是检测到超标立即报警,我增加了持续检测机制——只有浓度连续3次超过阈值才触发报警,避免误报:

uint8_t alarm_check(float current_val) { static uint8_t count = 0; if(current_val > threshold) { if(++count >= 3) { count = 3; return 1; } } else { count = 0; } return 0; }

4.3 串口调试技巧

在usart.c中添加printf重定向,可以方便地查看系统状态:

#include <stdio.h> int __io_putchar(int ch) { HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 10); return ch; } // 使用时直接调用 printf("当前浓度:%.1fppm 阈值:%dppm\n", smoke_value, threshold);

5. 完整代码架构解析

整个工程采用模块化设计,主要文件结构如下:

/Drivers /STM32F1xx_HAL_Driver // HAL库文件 /CMSIS // 内核支持文件 /Inc main.h lcd1602.h adc.h ... /Src main.c // 主循环和初始化 lcd1602.c // LCD驱动 adc.c // 采集处理 ...

主程序流程采用事件驱动架构:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_TIM3_Init(); MX_USART1_UART_Init(); LCD_Init(); HAL_TIM_Base_Start_IT(&htim3); // 启动定时器 while(1) { float smoke = Get_Smoke_Value(); Display_Values(smoke); if(alarm_check(smoke)) { HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET); } HAL_Delay(300); } }

在调试这个系统时,我发现ADC读数偶尔会跳变,后来在电源引脚加了0.1μF去耦电容后问题解决。另一个常见问题是LCD显示乱码,通常是初始化时序不对,解决方法是在LCD_Init()函数后加500ms延时。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询