这次我们来看一个很典型的嵌入式综合项目:基于 STM32 的酒精检测系统。这类选题在毕设、课设、电赛入门、个人练手项目里出现频率很高,因为它把传感器采集、ADC 转换、数据处理、OLED 显示、蜂鸣器报警、按键交互全部串在一起,刚好覆盖 STM32 开发的核心链路。更重要的是,它是开源的,你不需要从零造轮子,拿到代码和原理图就能改、能烧、能跑。
先说核心结论:这套系统的重点不在算法多复杂,而在于它的工程结构很完整,适合用来建立 STM32 项目开发的整体框架。它的核心逻辑是“酒精传感器输出电压 -> STM32 ADC 采样 -> 软件滤波和浓度换算 -> OLED 显示 -> 超阈值报警”。如果只是做功能演示,一块 STM32F103 核心板、一个 MQ-3 酒精传感器模块、一个 0.96 寸 OLED 显示屏就能搭起来,整体硬件成本不高。如果你手头有正点原子、野火或者其他厂家基于 STM32F103C8T6 的开发板,基本都能直接适配。
这篇文章会带你走一遍完整流程:系统架构和传感器原理、硬件接线与电路设计、开发环境准备、STM32CubeMX 工程配置、代码实现(ADC 采集、滤波、阈值判断、OLED 显示、报警控制)、功能测试方案、常见问题排查,最后给出几个扩展方向。如果你想把这个项目改造成带联网报警的版本,后面也有 ESP8266 接入思路。文章涉及的所有代码都以 STM32 HAL 库为基准,结合 STM32CubeMX 生成初始化工程,这是目前最主流的开发方式,也最容易迁移到不同型号的 STM32 上。
1. 项目核心能力速览
先把这套系统的能力边界和硬件门槛放前面,方便你快速判断它适不适合自己。
| 能力项 | 说明 |
|---|---|
| 项目类型 | 嵌入式传感器采集与报警系统,基于 STM32 实现 |
| 核心传感器 | MQ-3 酒精传感器,检测范围常见为 10ppm - 300ppm,需预热稳定 |
| 主控芯片 | STM32F103C8T6 或同系列 STM32F103,也可迁移到 F407、G431 等型号 |
| 信号采集 | STM32 内置 12 位 ADC,单通道或多通道采样 |
| 显示方案 | 0.96 寸 SSD1306 OLED,I2C 接口 |
| 报警方案 | 有源蜂鸣器 + LED 指示灯,可扩展继电器控制外部设备 |
| 交互方案 | 独立按键调节报警阈值,阈值可保存到片内 Flash |
| 开发环境 | STM32CubeMX + Keil MDK 或 STM32CubeIDE,HAL 库 |
| 调试工具 | ST-Link V2、J-Link 或板载 DAP-Link |
| 操作系统支持 | 裸机前后台系统,不依赖 RTOS,适合入门 |
| 是否支持 API 接口 | 本身是单片机程序,不直接提供 HTTP API,但可通过串口输出数据 |
| 是否支持批量任务 | 支持连续自动检测,属于周期采集,不是批量任务模型 |
| 适合场景 | 毕设/课设、嵌入入门练手、车载酒驾提醒实验、环境酒精浓度监测学习 |
从表里可以看出来,这个项目没有特别高的硬件门槛。软件环境方面,STM32CubeMX 负责生成底层初始化代码,Keil 或 STM32CubeIDE 负责编译烧录,调试器用最普及的 ST-Link V2 就行。相比图像、AI 推理类项目对显存和 GPU 的要求,这几乎是零成本入门,你需要的只是一块十几块钱的 STM32 最小系统板。但要注意,MQ-3 传感器在刚上电时输出并不稳定,预热和标定是必须处理的步骤,这也是后面最容易踩坑的地方。
2. 系统工作原理与整体架构
2.1 MQ-3 传感器的工作方式
MQ-3 属于半导体气敏传感器,内部有一个加热电阻和一个二氧化锡气敏层。当环境中存在酒精蒸气时,气敏层的电导率会发生变化,导致传感器模块的模拟输出电压随酒精浓度升高而升高。MQ-3 模块通常提供两个输出:
- 数字输出 DO:模块板载比较器,通过电位器设置阈值,超过阈值后输出低电平。
- 模拟输出 AO:输出电压约 0 - 5V,需要接 MCU 的 ADC 引脚进行采样。
项目里推荐使用 AO 模拟输出,因为只靠 DO 只能判断“超标”和“不超标”,拿不到连续浓度值。只有用 ADC 读取 AO 电压,才能通过换算得到浓度,并在 OLED 上显示实时数据。MQ-3 对酒精有较高灵敏度,同时对汽油、烟雾等也有一定响应,所以它适合做酒精浓度变化的定性检测,而不是实验室级别的精密测量。
2.2 系统整体工作流程
系统上电之后先进行初始化,包括时钟、GPIO、ADC、I2C、OLED、按键、蜂鸣器。初始化完成后,进入主循环,每个周期执行以下步骤:
- ADC 采集 MQ-3 模块 AO 引脚的电压值。
- 对多次采样做软件滤波,降低传感器噪声和电源波动带来的抖动。
- 按分压比例还原传感器实际输出电压,再根据标定公式换算成酒精浓度。
- 将浓度值、当前阈值、报警状态显示到 OLED 上。
- 将当前浓度与阈值比较,超过阈值后开启蜂鸣器和 LED 报警。
- 检测按键输入,按键 1 增加阈值,按键 2 减少阈值,按键 3 保存阈值到 Flash。
这个流程非常符合嵌入式项目的经典写法:采集层、处理层、输出层分离,逻辑清晰,也方便后续扩展。比如你想增加 WiFi 上报功能,只需要把“串口输出浓度数据”当成一个独立模块加入即可,不需要改动采集和显示逻辑。
2.3 报警状态设计
为了区分不同风险等级,可以设计两档报警逻辑。浓度低于阈值时,系统只做显示和状态指示;浓度超过阈值一档时,LED 慢闪提示,蜂鸣器短响;浓度超过阈值二档时,LED 快闪,蜂鸣器连续鸣叫,同时继电器吸合。这个双阈值逻辑在代码里只是两个 if 判断,却能让系统的实用性明显提升。如果只做一个固定阈值,项目演示效果会很单一。
3. 硬件选型与电路设计
3.1 元器件清单
下表是一套完整可运行的基础物料清单,价格按常见电商参考价估算,实际以你所在地区购买渠道为准:
| 元器件 | 型号/规格 | 数量 | 说明 |
|---|---|---|---|
| 主控板 | STM32F103C8T6 最小系统板 | 1 | 64KB Flash,20KB RAM,带 LED 和按键 |
| 酒精传感器 | MQ-3 模块 | 1 | 带 AO/DO 输出,板载电位器 |
| OLED 显示屏 | 0.96 寸 SSD1306,I2C | 1 | 分辨率为 128x64 |
| 蜂鸣器 | 有源蜂鸣器 3.3V/5V | 1 | 高电平或低电平触发,按模块实际规格 |
| LED 指示灯 | 红色,5mm | 1 | 报警指示 |
| 电阻 | 10K 分压电阻 | 2 | 用于把 MQ-3 AO 5V 输出分压到 3.3V 安全范围 |
| 按键 | 轻触按键 6x6mm | 3 | 调阈值、确认操作 |
| 继电器模块 | 5V 低电平触发 | 1 | 可选,控制风扇或外部设备 |
| 杜邦线 | 母对母,公对母 | 若干 | 接线调试 |
| 面包板 | 400 孔 | 1 | 原型搭建 |
STM32F103C8T6 的 ADC 输入电压范围是 0 - 3.3V,而 MQ-3 模块的 AO 输出在浓度较高时可能达到 4V 以上,所以不能直接接到 PA0 这类 ADC 引脚上,否则可能超出芯片引脚绝对最大额定值,轻则采样不准,重则损坏 MCU。稳妥的做法是加一级电阻分压,把 0 - 5V 映射到 0 - 3.3V 左右。例如用 10K 和 15K 电阻分压,分压比例约为 15/(10+15)=0.6,这样 5V 对应 3V,预留一定余量。也可以直接用两个 10K 电阻构成 0.5 倍分压,5V 对应 2.5V,范围更安全。具体分压电阻值需要根据 MQ-3 模块的最大输出和你希望测量的浓度范围来定,不建议直接网上抄一个电阻值了事。
3.2 接线表
下面是一组常用的接线方式,适用于 STM32F103C8T6 最小系统板:
| 模块 | 引脚 | STM32F103C8T6 引脚 |
|---|---|---|
| MQ-3 模块 VCC | 5V | 5V(模块需要 5V 供电) |
| MQ-3 模块 GND | GND | GND |
| MQ-3 模块 AO | 经过分压电路 | PA0(ADC1_IN0) |
| OLED SCL | I2C 时钟 | PB6 |
| OLED SDA | I2C 数据 | PB7 |
| OLED VCC | 3.3V | 3.3V |
| OLED GND | GND | GND |
| 蜂鸣器 IN | 控制引脚 | PB1 |
| LED 正极 | 限流电阻 | PB0 |
| 按键 1 | 增加阈值 | PC13 |
| 按键 2 | 减少阈值 | PC14 |
| 按键 3 | 确认/保存 | PC15 |
这里需要特别说明:MQ-3 的加热丝需要较大电流,所以供电最好从 USB 5V 引入,不要直接从 STM32 的 3.3V LDO 上取电。如果板载 LDO 输出能力不足,传感器工作时可能会导致 MCU 复位或 ADC 参考电压波动。更稳定的做法是给整个系统外接 5V 电源,STM32 再通过自己的稳压电路得到 3.3V。ADC 参考电压就是 3.3V,如果这一路电压不稳,采样结果就会出现不规则跳动。
3.3 分压电路设计
由于 MQ-3 模块的 AO 最大输出电压可能接近 5V,而 STM32 ADC 只能承受 3.3V,所以必须在 AO 和 PA0 之间加分压电阻。下面是一个最简单的方案:
MQ-3 AO --- R1(10K) --- PA0 | R2(10K) | GND分压系数 = R2 / (R1 + R2) = 0.5。代码中需要把这个系数“还原”回去,也就是实际传感器电压 = ADC 测量电压 / 0.5。如果 R1 和 R2 都选 10K,那么最大可测模拟电压约为 6.6V,对 5V 输出有足够余量。需要强调的是,分压电阻值要选用精度在 5% 以内的普通电阻即可,但两个电阻的阻值要尽量一致,否则换算会出现较大偏差。更精确的做法是先用万用表实测 R1 和 R2 的实际阻值,然后把实际分压系数硬编码到程序里。
4. 开发环境准备
4.1 软件工具链
做 STM32 开发,目前最通用的一套组合是 STM32CubeMX + Keil MDK,或者 STM32CubeMX + STM32CubeIDE。前者适合习惯 Keil 编译器的工程师,后者由 ST 官方出品,免费且跨平台,适合 Windows/Linux/macOS 用户。无论选哪个,STM32CubeMX 都建议先安装,因为它可以根据图形化配置自动生成工程代码,大幅减少手动初始化外设的工作量。
安装过程中需要注意:STM32CubeMX 依赖 Java 运行环境,安装时选对操作系统版本,避免反复提示“找不到 Java”。Keil MDK 安装完成后,还需要在 Pack Installer 里安装 STM32F1 系列器件支持包,否则编译器无法识别 STM32F103C8T6。如果你用的是 STM32CubeIDE,器件支持包会在创建工程时自动下载,环境搭建相对省心一些。
4.2 硬件调试与烧录工具
烧录和调试建议使用 ST-Link V2 调试器,它支持 SWD 接口,只需要接 SWDIO、SWCLK、GND 三条线,再加一个 3.3V 给目标板供电。接线时 SWDIO 对应 PA13,SWCLK 对应 PA14。如果调试器没识别到芯片,优先检查接线是否交叉错位、目标板是否供电、驱动是否安装成功。Windows 下如果设备管理器里显示“STM32 ST-LINK Utility”或“STMicroelectronics STLink dongle”有感叹号,一般是驱动问题,需要重新安装 ST-Link 驱动。
4.3 工程文件组织建议
在实际写代码之前,可以先规划一下源码目录,避免所有 .c 文件堆在一起。一个清晰的结构类似下面这样:
Alcohol_Detector/ ├── Core/ │ ├── Inc/ │ └── Src/ ├── Drivers/ │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ ├── Middlewares/ ├── App/ │ ├── oled.c │ ├── oled.h │ ├── mq3.c │ ├── mq3.h │ ├── filter.c │ └── filter.h ├── MDK-ARM/ └── alcohol_detector.ioc业务代码单独放在 App 目录下,可以避免和 CubeMX 自动生成的代码混在一起。当 CubeMX 重新生成代码时,如果业务代码放在 Core 目录外的自定义目录,就不容易被覆盖。这是一个很重要的工程习惯。
5. 基于 STM32CubeMX 的工程配置
5.1 创建工程
打开 STM32CubeMX,选择 MCU 型号时输入 STM32F103C8。如果使用的是其他型号,比如 STM32F407VET6,引脚会不一样,但配置思路相同。进入界面后,按下面的步骤配置调试接口、时钟、ADC、I2C 和 GPIO。调试接口建议选择 Serial Wire,只用 SWD 两条线,如果选择 JTAG 模式,部分引脚会被占用,后续使用其他 GPIO 可能冲突。
5.2 时钟配置
在 Clock Configuration 页面,把 HSE 设为 Crystal/Ceramic Resonator,然后把 PLL Source 选中 HSE,系统时钟频率设置为 72MHz。如果最小系统板上没有外部晶振,需要把 HSE 设为 Bypass Clock Source,并且系统时钟频率要按板载晶振和 PLL 参数调整。STM32F103C8T6 内部 HSI 精度能跑,但 ADC 采样会更容易受到温度影响,所以能用外部晶振就用外部晶振。
5.3 ADC 配置
在 Categories 中选择 ADC1,启用 IN0 通道,对应 PA0 引脚。ADC 参数建议配置为:
- 采样分辨率:12 bit
- 扫描模式:Disabled
- 连续转换模式:Disabled
- 转换次数:1
- 采样周期:55.5 周期或更大
采样周期越大,采样越稳定,但转换速度也会变慢。酒精检测系统对采样速率要求不高,所以选择较高采样周期更合适。转换模式可以选择单次转换,然后在 while 循环里手动触发;也可以开启连续转换模式,配合 DMA 使用。入门阶段建议先用轮询方式读取,代码简单,也容易调试。
5.4 I2C 配置
OLED 使用 I2C1,配置为 I2C 模式,默认速度 100KHz 即可。OLED 的 I2C 地址通常是 0x78(7 位地址为 0x3C),具体要看屏幕模块的规格。I2C 总线需要上拉电阻,大部分 OLED 模块板载已经包含,不需要额外添加。如果你的 OLED 模块没有上拉电阻,就要在 SCL 和 SDA 上分别接一个 4.7K 上拉到 3.3V。
5.5 GPIO 配置
蜂鸣器、LED、按键引脚配置为 GPIO_Output 或 GPIO_Input。按键引脚建议配置为上拉输入模式,按下时读到低电平,松开时读到高电平。如果按键接的是外部下拉电路,也可以配置为下拉输入。STM32F103C8T6 的 PC13、PC14、PC15 是特殊的 RTC 引脚,作为普通按键输入没有太大问题,但要注意这个引脚在低功耗模式下有额外限制。
生成代码时选择 IDE 为 MDK-ARM,Toolchain/IDE 下拉框里选择 Keil MDK V5,生成工程后找到 MDK-ARM 目录下的 .uvprojx 文件打开即可开始写代码。
6. 程序设计与代码实现
6.1 MQ-3 数据读取与软件滤波
MQ-3 传感器输出存在明显的随机噪声,尤其是在刚上电预热阶段,读数会有较大波动。如果直接把单次 ADC 结果用于阈值判断,报警触发会非常不稳定。推荐使用“中位值平均滤波法”:连续采集 N 个样本,去掉最大值和最小值,剩下的样本取平均。这种滤波方式既能滤掉脉冲干扰,又能平滑噪声。
下面是一个典型的滤波器实现:
/* filter.h */ #ifndef __FILTER_H #define __FILTER_H #include <stdint.h> #define FILTER_N 11 uint16_t MedianAverageFilter(uint16_t *data, uint8_t len); #endif/* filter.c */ #include "filter.h" uint16_t MedianAverageFilter(uint16_t *data, uint8_t len) { uint16_t sum = 0; uint16_t minValue = 0xFFFF; uint16_t maxValue = 0; uint8_t i; for (i = 0; i < len; i++) { if (data[i] < minValue) { minValue = data[i]; } if (data[i] > maxValue) { maxValue = data[i]; } sum += data[i]; } sum -= minValue + maxValue; return sum / (len - 2); }主循环里每次采样得到一组数据后,先调用这个滤波函数,再基于滤波结果计算电压和浓度。采样时可以在循环里加一点延时,保证两次采样之间有一定间隔。
6.2 电压换算与浓度计算
ADC 原始值转换为电压的公式为:
V_mcu = adc_value * 3.3f / 4095其中 V_mcu 是 PA0 引脚上经过分压后的电压。还原 MQ-3 模块实际输出:
V_sensor = V_mcu / DIVIDER_RATIODIVIDER_RATIO 就是之前分压电路的分压系数。如果电路是 10K 对 10K,DIVIDER_RATIO 就是 0.5f。在代码里,建议把这个参数定义成一个宏,方便后续调整。
浓度计算是这类项目最需要谨慎的一步。MQ-3 的数据手册里通常有酒精浓度与传感器电阻比值的曲线,但这条曲线并不完全线性,而且不同批次传感器差异很大。严格来说,工程上需要做两点或多点标定,用已知浓度的酒精气体去校准曲线。作为学习和演示用途,可以先用简化逼近公式。假设采用线性近似:
alcohol_ppm = (V_sensor - V_ref) * K这里的 V_ref 是空气中的基准电压,K 是比例系数。V_ref 需要在洁净空气中预热后实测,K 需要通过特定浓度标定。不要把任何网上抄的固定系数当成绝对准确的值,代码注释里应该写明这是简化模型,只用于验证功能。下面给出一个计算函数的示例:
/* mq3.h */ #ifndef __MQ3_H #define __MQ3_H #include <stdint.h> #define ADC_REF_VOLTAGE 3.3f #define ADC_MAX_VALUE 4095 #define DIVIDER_RATIO 0.5f #define MQ3_AIR_BASE_VOLTAGE 0.35f #define MQ3_SLOPE_COEFF 180.0f uint16_t MQ3_ReadAverage(void); float MQ3_CalcVoltage(uint16_t adcValue); float MQ3_CalcPPM(float sensorVoltage); #endif/* mq3.c */ #include "mq3.h" #include "adc.h" #include "filter.h" extern ADC_HandleTypeDef hadc1; uint16_t MQ3_ReadAverage(void) { uint16_t samples[FILTER_N]; uint8_t i; for (i = 0; i < FILTER_N; i++) { HAL_ADC_Start(&hadc1); if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) { samples[i] = HAL_ADC_GetValue(&hadc1); } HAL_Delay(5); } return MedianAverageFilter(samples, FILTER_N); } float MQ3_CalcVoltage(uint16_t adcValue) { return (float)adcValue * ADC_REF_VOLTAGE / ADC_MAX_VALUE; } float MQ3_CalcPPM(float sensorVoltage) { return (sensorVoltage - MQ3_AIR_BASE_VOLTAGE) * MQ3_SLOPE_COEFF; }注意:MQ3_AIR_BASE_VOLTAGE 和 MQ3_SLOPE_COEFF 都是需要根据实际模块标定的参数,这里只是示例值。如果你发现浓度显示离谱,先检查这两个参数。
6.3 OLED 显示功能
OLED 显示建议直接移植 SSD1306 的软件包,常见的是基于 HAL 库的 I2C 驱动。驱动库一般会提供OLED_Init()、OLED_Clear()、OLED_ShowString()、OLED_ShowNum()等接口。移植时主要有三个地方要改:I2C 句柄、I2C 地址、屏幕宽高。如果屏幕型号是 0.96 寸 128x64,使用 GDDRAM 映射,地址通常为 0x3C,代码里对应左移一位后的写地址 0x78。
OLED 上建议分四行显示:第一行显示“ALC: xxx ppm”,第二行显示“THR: xxx ppm”,第三行显示状态“NORMAL”或“ALARM”,第四行显示传感器电压辅助调试。这种排版在 128x64 分辨率下非常合适,信息密度高且不拥挤。
6.4 阈值设置按键
阈值设置是这个项目交互体验的关键。如果阈值固定写死在代码里,每次改阈值都要重新编译烧录,体验很差。增加三个按键后,用户可以在运行中调节阈值:
- 按键 1(KEY_UP):阈值加 10ppm,长按可以连续加。
- 按键 2(KEY_DOWN):阈值减 10ppm,同样支持长按连续减。
- 按键 3(KEY_SAVE):把当前阈值写入 Flash,下次开机自动加载。
写 Flash 可以用 STM32 内置的 Flash 模拟 EEPROM,也可以直接用固定的 Flash 页保存。STM32F103C8T6 的 Flash 容量是 64KB,如果代码占用的空间不大,可以把最后 1 页(通常是 1KB 大小)作为参数存储区。需要注意的是,使用 HAL 库操作 Flash 时要先解锁,写入完成后重新上锁,并且擦写之前要确保当前程序不是从存储在要擦除页面的代码中运行。更稳妥的做法是参考 ST 官方的 EEPROM 模拟例程,或者直接外接一个 AT24C02 I2C EEPROM,编程难度会低很多。对新手来说,AT24C02 是更容易跑通的方案。
6.5 报警控制逻辑
蜂鸣器和 LED 的控制逻辑简单直接:
if (alcoholPpm >= thresholdHigh) { HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); statusText = "ALARM"; } else if (alcoholPpm >= thresholdLow) { HAL_GPIO_TogglePin(BUZZER_GPIO_Port, BUZZER_Pin); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); statusText = "WARNING"; } else { HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); statusText = "NORMAL"; }有源蜂鸣器只需要给高电平就会发声,所以“短响”可以通过在循环里定时翻转电平来实现。低阈值和高阈值这两个值可以用宏定义,也可以存到 Flash 里。建议在代码里预留这两个阈值变量,方便后续改成其他控制逻辑,比如超过高阈值时自动打开继电器排风扇。
6.6 主函数流程
主函数不需要复杂的状态机,一个 while 循环就能完成全部逻辑。为了提高可读性,可以把每个功能封装成子函数:
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_I2C1_Init(); OLED_Init(); OLED_Clear(); OLED_ShowString(0, 0, "Alcohol Detector"); while (1) { uint16_t adcValue = MQ3_ReadAverage(); float voltage = MQ3_CalcVoltage(adcValue); float ppm = MQ3_CalcPPM(voltage / DIVIDER_RATIO); uint8_t keyValue = Key_Scan(); HandleKeyPress(keyValue); ClampThreshold(); DisplayAlcoholeInfo(ppm, voltage); ControlAlarm(ppm); HAL_Delay(200); } }ADC 计算里的 DIVIDER_RATIO 要注意使用位置:如果MQ3_CalcVoltage已经返回 PA0 引脚电压,那么MQ3_CalcPPM内部要先做sensorVoltage / DIVIDER_RATIO还原传感器实际输出电压。这里是最容易搞混的地方,写代码时建议加注释。
7. 功能测试与效果验证
7.1 预热与稳定性测试
MCU 烧录完成后,第一件事不是马上去测酒精,而是让系统通电预热 3 到 5 分钟,观察 OLED 上的浓度值是否逐渐趋于稳定。如果显示的 ppm 值在地板附近漂移,先检查分压电路接线和供电是否稳定。如果一直跳动严重,可以增加滤波采样数量,比如把 FILTER_N 从 11 改成 21,观察效果。预热后的稳定读数就是环境基准电压,应该代入 MQ3_AIR_BASE_VOLTAGE 中,否则后续浓度计算会出现很大的负值或偏大值。这里建议记录预热后的电压值,再手动修改代码里的MQ3_AIR_BASE_VOLTAGE,重新编译烧写。
7.2 酒精浓度响应测试
测试时建议准备一个密闭的透明塑料袋或塑料容器,把蘸了少量酒精的棉球放进去,等待酒精蒸气挥发,然后将 MQ-3 模块探头伸入容器,观察 OLED 读数是否快速上升。重点验证三点:读数是否随浓度升高而升高、报警是否在达到阈值时触发、撤走酒精源后读数是否逐渐回落。如果读数上升不明显,可能原因包括 MQ-3 模块电位器调节不当、AO 输出未正确接入 ADC、分压电路阻值错误。如果读数已经超过阈值但报警没触发,检查报警引脚的 GPIO 配置和蜂鸣器驱动电平是高电平还是低电平触发。
7.3 阈值调节与保存测试
连续按“增加阈值”按键,观察 OLED 上的 THR 值是否按预期步进。如果按键存在双击或跳变问题,需要在按键扫描函数里加入消抖逻辑,常见的做法是延时消抖,也可以使用定时器扫描加状态机消抖。调节到目标阈值后,按“保存阈值”按键,然后重新上电,确认 THR 值能恢复到刚才保存的值,而不是回到默认值。如果重启后阈值丢失,说明 Flash 读写部分有问题,需要检查地址、擦除和写入时序。使用 AT24C02 的话,则要确认 I2C 写周期等待时间是否足够。
7.4 长时间稳定性测试
酒精检测系统比较容易被忽略的问题是长时间运行漂移。建议把系统连续运行 4 到 8 小时,每隔一段时间记录一次 OLED 的显示读数。如果读数在无酒精环境下不断爬升,很可能是 MQ-3 传感器长时间通电后的零点漂移,也有可能是传感器本身吸附了环境中残留的有机物。可以在程序里增加一个“一键校准”功能,按下按键后把当前读数作为新的基准电压并保存到 Flash,这样每次使用前可以快速校准。
7.5 效果验收标准
| 测试项目 | 通过标准 |
|---|---|
| 开机启动 | 3 秒内 OLED 正常显示,无花屏 |
| 预热稳定性 | 5 分钟后显示值漂移不超过整体的 10% |
| 酒精响应 | 放入酒精蒸气后读数明显上升,响应时间在几秒到十几秒量级 |
| 阈值报警 | 浓度超过阈值后蜂鸣器响,LED 亮,无误报 |
| 阈值保存 | 断电重上电后阈值保持 |
| 按键操作 | 按键响应正常,无明显跳变 |
8. 串口输出与联网扩展
8.1 串口输出浓度数据
如果你想在电脑上实时查看酒精浓度曲线,最直接的办法是使用 STM32 的 USART1,将浓度数据通过串口发送到上位机。CubeMX 里配置 USART1 为异步模式,波特率 115200,然后在主循环中调用printf输出格式化数据。重定向 printf 到串口,需要重写 fputc 函数:
#include <stdio.h> extern UART_HandleTypeDef huart1; int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 100); return ch; }输出格式可以定义成简单易解析的 CSV:
ADC: 1024, V: 0.82V, PPM: 55.4上位机用串口助手查看,或者用 Python 的 pyserial 读取后实时绘图。这个功能对排查传感器漂移和标定参数非常有用。如果你后续要做更完整的上位机系统,可以把数据帧设计成带校验的格式,比如帧头 + 数据长度 + 数据 + CRC。
8.2 ESP8266 无线上报
在扩展功能里,ESP8266 是最常见的 WiFi 方案。STM32 通过 USART2 或 USART3 与 ESP8266 通信,ESP8266 以透传模式把浓度数据提交到 MQTT 服务器或 HTTP 接口。需要注意 STM32F103C8T6 的串口资源有限,如果同时使用 USART1 调试、USART2 接 ESP8266、I2C 接 OLED、ADC 接传感器,资源是够用的,但引脚分配要提前规划好,避免冲突。另外,ESP8266 模组对供电电流要求较高,启动瞬间可能拉低 3.3V 电压,导致 STM32 复位。建议给 ESP8266 单独供电,不要和 STM32 共用同一路 3.3V LDO。
8.3 远程报警与数据记录
接上网关后,可以把酒精检测系统的报警事件上报到微信小程序、钉钉机器人或者自己的后端服务。这种方式适合车载防酒驾提醒、实验室环境监测、班级宿舍场景演示。如果做数据记录,建议在 STM32 端只保留最近若干条报警记录,更多的历史数据由服务端负责存储和分析。MCU 端持续记录大量数据既浪费 Flash,也没有必要。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 编译报错,找不到 STM32F1xx 系列器件 | Keil Pack 未安装 | 检查 Pack Installer 中 STM32F1 支持包 | 安装 STM32F1xx_DFP 支持包 |
| ST-Link 连接失败 | 接线错误、驱动异常、目标板未供电 | 检查 SWDIO/SWCLK/GND 接线和设备管理器 | 重新安装驱动;给目标板单独供电 |
| 烧录后程序不运行 | BOOT0 引脚电平错误、时钟配置错误 | 检查 BOOT0 是否接地,检查 HSE 晶振 | BOOT0 接 GND;改用内部 HSI 或修正晶振配置 |
| OLED 白屏 | I2C 地址错误、接线错误、库不匹配 | 用 I2C 扫描程序检测地址 | 确认 0x3C/0x78 地址;更换 OLED 库 |
| ADC 显示值跳变 | 供电不稳、滤波不足、分压接触不良 | 用万用表测 PA0 电压 | 增加滤波次数;检查杜邦线连接;改用独立电源 |
| 浓度值刚上电就很高 | MQ-3 未预热,基准电压错误 | 等待预热后记录基准电压 | 更新 MQ3_AIR_BASE_VOLTAGE |
| 蜂鸣器不响 | 引脚配置错误、蜂鸣器是有源/无源类型不匹配 | 检查蜂鸣器类型和驱动电平 | 有源蜂鸣器给高电平响;无源蜂鸣器需要 PWM 驱动 |
| 按键按下没反应 | 按键消抖不充分、上拉/下拉配置错误 | 用调试器读 GPIO 电平 | 增加消抖;调整上下拉配置 |
| 阈值断电丢失 | Flash 擦写失败或未保存 | 增加保存日志,检查地址是否越界 | 改用 AT24C02 外部 EEPROM |
| MQ-3 读数缓慢下降不恢复 | 传感器吸附残留酒精或环境不流通 | 把传感器放在通风处 | 等待恢复;必要时用加热电阻加速清理 |
10. 最佳实践与合规提醒
这套系统目前最合适的使用场景是嵌入式学习、课程设计、毕业设计演示、车载防酒驾提醒实验,以及基础的环境酒精浓度监测学习。它不是一个精密计量设备,也不应该替代正规酒精检测仪。MQ-3 传感器对湿度、温度、汽油蒸汽和其他挥发性有机物都有响应,在实际应用中很容易出现误报。如果要把这个方案用于生产环境,必须使用经过标定的更高精度传感器,并完成国家或行业相关计量标准的认证。
从工程化角度,有几点建议可以直接用在你的项目里。
第一,第一次上电不要急着接高浓度酒精测试,先让传感器预热,观察稳定状态下的基准电压。整个系统的“零漂”问题几乎都出在这一步。
第二,分压电路一定要加,而且要先计算再接线,不要直接把 MQ-3 AO 接到 STM32 的 PA0 上。STM32 的 GPIO 引脚不是 5V 容忍时,5V 电压可能损坏芯片。
第三,采样滤波不能省。就算是演示项目,滤波也能让你少很多排查噪声的时间。如果滤波后还觉得抖动,可以在主循环里把显示刷新周期从 200ms 改成 500ms,视觉上会更稳定。
第四,代码注释要记录你的标定参数。比如你用的分压电阻实际阻值是多少,环境基准电压实测是多少,斜率系数试到多少合适。不然过两天这个工程你自己都看不懂为什么当时要填那个系数。
第五,涉及数据上报、远程报警、网络服务时,要注意隐私和数据安全。本项目如果部署在公共场所或车辆上,采集的环境浓度数据属于运行数据,建议只做本地展示,不轻易上传。如果确实需要联网,要确保服务端访问受控,避免接口被人恶意刷写。
第六,如果演示对象是非嵌入式背景的人,可以先用串口把浓度曲线画出来,再对着曲线讲解“传感器响应 -> ADC 采样 -> 滤波 -> 阈值判断”这条链路,会比只看一个 OLED 屏幕效果好很多。这也是很多开源嵌入式项目在展示时最有效的方式。
11. 总结与下一步
这个基于 STM32 的酒精检测系统,最值得尝试的点在于它覆盖了嵌入式开发的完整闭环:从传感器选型、信号调理、ADC 采集,到滤波算法、交互显示、报警控制,再到 Flash 参数存储和串口输出。即使你之前只写过 LED 闪烁和按键控制,把这个项目完整跑一遍,基本就能建立起 STM32 裸机开发的整体思维。
建议第一次跑项目时,先完成最简单的功能链路:MQ-3 预热后读取 ADC 电压,在 OLED 上显示电压值;然后在此基础上加入浓度换算;最后再加阈值调节、Flash 保存和报警逻辑。不要一开始就把所有功能堆在一起,否则一个问题出现时很难定位是传感器问题还是代码问题。最容易踩的坑就是 MQ-3 模块没有预热、分压比例没有算对、AO 输出直接接了 3.3V 引脚,这三类问题可以排在前三名。
下一步可以往三个方向扩展:一是加 ESP8266 联网上报,把浓度和报警事件推到服务端,做远程监控;二是用更高精度的酒精传感器或集成环境传感器模块,提高检测稳定性和交叉干扰抑制能力;三是把 UI 做得更完整,比如加入动态曲线、历史记录、校准菜单、等多种显示模式。如果做完这些,这个项目的复杂度就已经超出一般课设水平,可以作为嵌入式能力展示放进简历里。建议收藏备用,动手跑一遍再评价它的工程价值。