基于GD32VW553的MQ-3酒精传感器驱动移植与数据读取实战
基于GD32VW553的MQ-3酒精传感器驱动移植与数据读取实战
最近在做一个环境监测的小项目,需要检测空气中的酒精浓度,正好手头有MQ-3传感器和GD32VW553开发板。MQ-3这个模块很常见,价格也便宜,但很多新手朋友在把它接到自己的开发板上时,总会遇到各种问题——引脚怎么接?ADC怎么配?数据怎么读?今天我就以GD32VW553这块RISC-V开发板为例,手把手带你走一遍完整的驱动移植流程,从硬件连接到代码编写,再到数据读取和转换,保证你看完就能用起来。
1. 认识MQ-3酒精传感器
MQ-3传感器核心是一层二氧化锡(SnO₂)气敏材料。这东西在干净空气里电阻很大(电导率低),可一旦周围有酒精蒸汽,它的电阻就会变小,而且酒精浓度越高,电阻变得越小。模块内部已经把这电阻变化转换成了电压信号,我们直接读取就行。
模块上有两个输出口:
- AO(Analog Output):模拟电压输出,电压值会随酒精浓度变化,我们需要用ADC(模数转换器)来读取。
- DO(Digital Output):数字开关量输出,模块内部用一个比较器(LM393)把AO电压和一个可调阈值比较,高于阈值就输出高电平,低于就输出低电平。这个阈值可以通过模块上的电位器调节,相当于一个灵敏度旋钮。
简单来说,AO给你的是“具体有多浓”的连续信息,DO给你的是“有没有超标”的开关信息。
模块的基本参数如下:
| 参数 | 值 |
|---|---|
| 工作电压 | 3.3V ~ 5V |
| 工作电流 | 约150mA |
| 输出接口 | AO(模拟量)、DO(数字量) |
| 读取方式 | ADC(读AO)、GPIO输入(读DO) |
| 接口类型 | 4 Pin 2.54mm排针 |
注意:MQ-3对酒精很敏感,但也会受汽油、烟雾等干扰,所以它适合做定性或半定量检测,比如酒驾预检、酒精泄露报警,不适合做精确的实验室级浓度分析。
2. 硬件连接与引脚规划
咱们先搞定接线。MQ-3模块就四根线,GD32VW553开发板上的引脚资源很丰富,这里我选择用PB0和PB2,原因很简单:PB0支持ADC功能,PB2是个普通GPIO,用起来方便。
具体的连接关系看下面这个表:
| 传感器模块引脚 | 开发板引脚 | 说明 |
|---|---|---|
| VCC | 5V0 | 接5V电源,模块内部有稳压,3.3V也能工作,但5V驱动能力更强。 |
| GND | GND | 共地,必须接! |
| DO | PB2 | 数字输出,接GPIO输入引脚。 |
| AO | PB0 | 模拟输出,必须接带ADC功能的引脚。 |
这里有个细节:PB0在GD32VW553上对应的ADC通道是 ADC_CHANNEL_8,这个信息等会儿写代码时会用到,先记一下。
接线时,建议用杜邦线直接连接,确保接触牢固。接好后,可以先给开发板上电,看看MQ-3模块上的电源指示灯是否亮起。
3. 新建驱动文件与工程配置
硬件连好了,接下来在代码里给它安个“家”。一个好的习惯是为每个外设模块创建独立的驱动文件,这样代码结构清晰,以后移植到别的项目也方便。
3.1 创建驱动文件
在你的工程目录下(比如 User 或 BSP 文件夹里),新建两个文件:
bsp_mq3.cbsp_mq3.h
bsp 是“板级支持包”的缩写,这种命名方式很常见。
3.2 添加头文件路径
为了让编译器能找到我们新建的 bsp_mq3.h 文件,需要把它的所在目录添加到工程的包含路径(Include Paths)里。以Keil MDK为例:
- 点击魔术棒按钮(Options for Target)。
- 选择
C/C++选项卡。 - 在
Include Paths一栏,点击添加按钮,然后找到并选中存放bsp_mq3.h的文件夹。
这一步很关键,不然编译时会报错“找不到头文件”。
4. 编写驱动代码(bsp_mq3.h)
头文件主要用来做宏定义和函数声明,相当于一个“使用说明书”。咱们打开 bsp_mq3.h,开始编写。
#ifndef BSP_CODE_BSP_MQ3_H_
#define BSP_CODE_BSP_MQ3_H_
#include "gd32vw55x.h"
#include "systick.h"
// 为了方便,统一延时函数接口
#ifndef delay_ms
#define delay_ms(x) delay_1ms(x)
#endif
#ifndef delay_us
#define delay_us(x) delay_1us(x)
#endif
// 一个宏搞定所有相关时钟的使能,后面初始化函数里直接调用就行
#define MODULE_BSP_RCU_ENABLE() \
rcu_periph_clock_enable(BSP_ADC_GPIO_RCU); \
rcu_periph_clock_enable(BSP_DO_GPIO_RCU); \
rcu_periph_clock_enable(BSP_ADC_RCU);
/* 引脚与ADC通道定义 */
// PB0 - ADC功能
#define BSP_ADC_GPIO_RCU RCU_GPIOB
#define BSP_ADC_GPIO_PORT GPIOB
#define BSP_ADC_GPIO_PIN GPIO_PIN_0
#define BSP_ADC_RCU RCU_ADC
#define BSP_ADC ADC
#define BSP_ADC_CHANNEL ADC_CHANNEL_8 // 重点!PB0对应通道8
// PB2 - 数字输入功能
#define BSP_DO_GPIO_RCU RCU_GPIOB
#define BSP_DO_GPIO_PORT GPIOB
#define BSP_DO_GPIO_PIN GPIO_PIN_2
// 读取DO引脚电平的快捷方式
#define MQ_DO gpio_input_bit_get(BSP_DO_GPIO_PORT, BSP_DO_GPIO_PIN)
// ADC采样次数,多次采样取平均可以滤除一些毛刺
#define SAMPLES 5
/* 函数声明 */
void Module_BSP_Init(void); // 初始化函数
int Get_Adc_MQ3_Value(void); // 获取ADC原始值
int Get_MQ3_Percentage_value(void); // 获取浓度百分比
char Get_MQ3_DO(void); // 获取DO引脚状态
#endif /* BSP_CODE_BSP_MQ3_H_ */
头文件里把引脚、通道、时钟这些硬件相关的信息都用宏定义好了,以后万一要换引脚,只改这里就行,非常方便。ADC_CHANNEL_8 这个定义就是根据GD32VW553的数据手册来的,PB0固定对应通道8。
5. 编写驱动代码(bsp_mq3.c)
这是重头戏,包含了所有具体的操作逻辑。咱们分函数来看。
5.1 初始化函数 Module_BSP_Init
这个函数负责配置GPIO和ADC,让硬件准备好工作。
#include "bsp_mq3.h"
#include "stdio.h"
void Module_BSP_Init(void)
{
/* 第一步:打开时钟开关 */
MODULE_BSP_RCU_ENABLE(); // 这个宏一次使能了GPIOB和ADC的时钟
/* 第二步:配置DO引脚(PB2)为输入模式 */
// GPIO_MODE_INPUT是输入模式,GPIO_PUPD_NONE表示不上拉也不下拉
gpio_mode_set(BSP_DO_GPIO_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, BSP_DO_GPIO_PIN);
/* 第三步:配置ADC时钟和引脚 */
// ADC时钟设为系统时钟(PCLK2)的4分频,保证ADC工作在合适频率
adc_clock_config(ADC_ADCCK_PCLK2_DIV4);
// 将PB0配置为模拟输入模式,这是ADC采集所必需的
gpio_mode_set(BSP_ADC_GPIO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, BSP_ADC_GPIO_PIN);
/* 第四步:配置ADC工作参数 */
adc_special_function_config(ADC_SCAN_MODE, ENABLE); // 使能扫描模式(虽然我们只用一个通道)
adc_data_alignment_config(ADC_DATAALIGN_RIGHT); // 数据右对齐,符合我们的阅读习惯
adc_resolution_config(ADC_RESOLUTION_12B); // 12位分辨率,ADC值范围0-4095
// 规则组通道数设为1(因为我们只用了通道8)
adc_channel_length_config(ADC_ROUTINE_CHANNEL, 1);
// 禁用外部触发,使用软件触发启动转换
adc_external_trigger_config(ADC_ROUTINE_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
adc_software_trigger_enable(ADC_ROUTINE_CHANNEL); // 使能软件触发
/* 最后,使能ADC模块 */
adc_enable();
}
初始化顺序很重要:先开时钟,再配引脚,最后设置ADC参数并开启。GPIO_MODE_ANALOG 这个模式专门用于ADC引脚,它会把内部的上拉、下拉电阻都断开,让引脚直接连接到ADC内部。
5.2 核心ADC读取函数 ADC_GET
这是一个静态函数(只在当前文件内有效),负责执行一次完整的ADC转换。
static int ADC_GET(void)
{
int gAdcResult = 0;
int timeOut = 1000; // 超时计数,防止卡死
// 1. 指定要采样的通道(通道8)和采样时间
adc_routine_channel_config(0, BSP_ADC_CHANNEL, ADC_SAMPLETIME_14POINT5);
// 2. 软件触发,启动一次转换
adc_software_trigger_enable(ADC_ROUTINE_CHANNEL);
// 3. 等待转换完成标志位
while(!adc_flag_get(ADC_FLAG_EOC) && timeOut--)
{
delay_us(1); // 稍作等待
}
// 4. 清除标志位,为下次转换做准备
adc_flag_clear(ADC_FLAG_EOC);
// 如果超时了,说明转换可能出了问题
if(!timeOut)
{
printf("ADC_GET Failed!!!\r\n");
return 0;
}
// 5. 读取转换结果
gAdcResult = adc_routine_data_read();
return gAdcResult;
}
这里用 ADC_SAMPLETIME_14POINT5 表示采样周期是14.5个ADC时钟周期,采样时间稍长一点,结果更稳定,适合MQ-3这种变化相对慢的信号。
5.3 获取平均ADC值 Get_Adc_MQ3_Value
单次ADC读数可能受干扰跳动,咱们采样5次取个平均值。
int Get_Adc_MQ3_Value(void)
{
uint32_t Data = 0;
int i;
for(i = 0; i < SAMPLES; i++)
{
Data += ADC_GET(); // 累加5次采样值
delay_ms(5); // 每次采样间隔5ms,让传感器有稳定时间
}
Data = Data / SAMPLES; // 计算平均值
return Data;
}
delay_ms(5) 这个间隔不是随便写的。MQ-3传感器的响应需要一点时间,连续快速采样读到的可能是同一个值,间隔几毫秒能让读数更真实地反映环境变化。
5.4 计算浓度百分比 Get_MQ3_Percentage_value
ADC读出来是0-4095的数字,咱们把它转换成0-100%的百分比,更直观。
int Get_MQ3_Percentage_value(void)
{
int adc_max = 4095; // 12位ADC的最大值
int adc_new = 0;
int Percentage_value = 0;
adc_new = Get_Adc_MQ3_Value(); // 获取平均ADC值
// 核心转换公式:(当前值 / 最大值) * 100%
Percentage_value = ((float)adc_new / (float)adc_max) * 100.f;
return Percentage_value;
}
注意,这里用了强制类型转换 (float),是为了避免整数除法丢失小数精度。这个百分比是相对于ADC量程的,可以近似理解为“传感器输出电压占参考电压的比例”,数值越大,表示检测到的酒精浓度信号越强。
5.5 读取数字输出 Get_MQ3_DO
这个最简单,就是读取一个GPIO引脚的电平。
char Get_MQ3_DO(void)
{
if( MQ_DO == 0 ) // MQ_DO就是前面定义的宏,直接读取PB2电平
{
return 0; // 低电平,表示未检测到超过阈值的酒精
}
else
{
return 1; // 高电平,表示检测到酒精浓度超过设定阈值
}
}
DO输出是0还是1,完全取决于模块上那个蓝色电位器的位置。顺时针拧,阈值提高,变得更“迟钝”;逆时针拧,阈值降低,变得更“灵敏”。你可以根据实际需要调整。
6. 在主函数中调用与测试
驱动写好了,最后就是在主程序里用起来。打开你的 main.c 文件。
#include "gd32vw55x.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "gd32vw553h_eval.h"
#include "bsp_mq3.h" // 包含我们自己的驱动头文件
int main(void)
{
// ... 系统时钟、串口、LED等初始化代码(你的工程里应该已有)...
// 这里假设你已经配置好了系统滴答定时器(systick)和串口(printf重定向)
printf("\r\n=== MQ-3 Alcohol Sensor Test ===\r\n");
// 初始化MQ-3传感器(配置GPIO和ADC)
Module_BSP_Init();
printf("MQ3 Sensor Initialization Complete\r\n");
// 主循环,每隔500ms读取并打印一次数据
while(1)
{
printf("\n"); // 换行,让每次输出更清晰
printf("MQ3 ADC Value = %d\r\n", Get_Adc_MQ3_Value());
printf("MQ3 Percentage = [%d%%]\r\n", Get_MQ3_Percentage_value());
printf("MQ3 DO Status = %d\r\n", Get_MQ3_DO());
delay_ms(500); // 延时500ms
}
}
编译、下载程序到GD32VW553开发板。打开串口调试助手(比如XCOM、SecureCRT),设置好波特率(和你工程里配置的一致,比如115200),就能看到源源不断的数据输出了。
正常现象应该是这样的:
- 在清洁空气中,ADC值和百分比会比较低(比如几十到几百,对应百分之几)。
- 向传感器附近喷洒少量酒精(或用棉签沾酒精靠近),ADC值和百分比会迅速上升。
- DO状态平时为0,当浓度超过你设定的阈值时,会跳变为1。
7. 调试与常见问题
如果你发现数据不对,别急,按下面几步排查:
-
没有数据输出?
- 检查串口接线(TX、RX是否接反)、波特率是否匹配。
- 确认
printf已正确重定向到串口。
-
ADC值始终为0或4095?
- 为0:检查PB0(AO)接线是否松动或接错。用万用表测量AO引脚对地电压,在空气中应该有一个基础电压(比如1V左右),靠近酒精时会升高。如果电压为0,可能是传感器或供电问题。
- 为4095(满量程):检查ADC参考电压。GD32VW553的ADC参考电压通常是VDDA(接3.3V)。如果AO引脚电压接近或超过3.3V,就会读到4095。MQ-3在5V供电下,AO输出电压可能超过3.3V,这时需要在AO和PB0之间串联一个分压电阻(比如1kΩ和2kΩ),将电压降到3.3V以内。
-
DO状态不变化?
- 调整模块上的蓝色电位器。逆时针旋转(通常)是降低阈值,让模块更灵敏,更容易输出高电平。
-
数据跳动厉害?
- 尝试增加
SAMPLES的数值(比如从5改到10),或增加Get_Adc_MQ3_Value函数中delay_ms(5)的延时。 - 检查电源是否稳定。MQ-3工作电流有150mA,如果电源带载能力不足,电压会被拉低,导致读数不稳。确保开发板的5V输出足够。
- 尝试增加
这个驱动框架我已经在几个小项目里用过了,挺稳定的。关键在于理解ADC的配置流程和传感器数据的处理方式。掌握了MQ-3,其他类似的模拟输出传感器(比如MQ-2、MQ-135)的驱动也就大同小异了,无非是换换引脚和校准方式。希望这篇教程能帮你顺利搞定GD32VW553上的第一个传感器驱动。
更多推荐



所有评论(0)