1. 系统概述与开发环境搭建烟雾报警系统是智能家居和工业安全领域的基础设施而基于STM32的方案因其性价比高、开发资源丰富成为首选。这次我们要实现的系统核心是STM32F103单片机配合MQ-2传感器仿真中用滑动变阻器模拟、LCD1602显示屏和蜂鸣器报警电路。整个开发流程涉及Proteus仿真和Keil编程对初学者特别友好。开发环境准备其实比你想象的简单。我用的Proteus 8.9有个隐藏技巧——安装后一定要勾选STM32库支持否则找不到F103型号。Keil MDK5的配置更要注意安装完STM32CubeMX后记得通过包安装器添加F1系列支持包。有次我忘了这步调试时各种报错折腾了半天才发现问题。硬件连线示意图STM32F103C8T6核心板ADC1通道1接滑动变阻器模拟MQ-2PA0-PA2接三个按键设置/加/减PB0-PB7接LCD1602数据线PA8接蜂鸣器驱动三极管基极提示Proteus里搜索POT-HG可以找到更精确的滑动变阻器模型比默认的电位器仿真效果更好。2. Proteus仿真搭建技巧仿真电路搭建是验证设计的关键步骤。这里有个实用技巧在Proteus中放置STM32芯片后先右键设置晶振频率为8MHz与代码中HSI配置一致。我见过有人直接用默认值结果串口通信全乱套。ADC采集部分的仿真有特殊处理放置滑动变阻器并设置为10KΩ连接变阻器中间引脚到PA1ADC1通道1添加电压探针方便观察输入变化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(cmd4); // 先高4位 LCD_Write4Bits(cmd0x0F); // 后低4位 HAL_Delay(1); // 实测需要至少37us延时 }显示字符串时采用DMAGPIO可以大幅降低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 i0; i100; 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延时。