做艺术品拍卖的网站,如何利用网站策划做好网站建设,定制做网站,文旅品牌建设基于梁山派GD32F470的MQ-2烟雾传感器ADCDMA数据采集实战 最近在做一个智能家居环境监测的小项目#xff0c;需要检测烟雾浓度#xff0c;用到了MQ-2传感器。很多刚开始接触嵌入式开发的朋友#xff0c;一看到ADC、DMA这些外设就有点发怵#xff0c;觉得配置起来很复杂。其实…基于梁山派GD32F470的MQ-2烟雾传感器ADCDMA数据采集实战最近在做一个智能家居环境监测的小项目需要检测烟雾浓度用到了MQ-2传感器。很多刚开始接触嵌入式开发的朋友一看到ADC、DMA这些外设就有点发怵觉得配置起来很复杂。其实只要理清思路一步步来并没有想象中那么难。今天我就以梁山派GD32F470开发板为例手把手带你实现MQ-2传感器的数据采集并且用上DMA这个“搬运工”让CPU解放出来干别的活。咱们这个教程的目标很明确让MQ-2传感器的模拟输出AO引脚连接到开发板的ADC引脚通过ADC转换把模拟电压变成数字值再用DMA自动把转换结果存到数组里最后我们读取这个数组计算浓度百分比。整个过程CPU只需要初始化一下之后就可以“撒手不管”非常高效。1. 认识你的“鼻子”MQ-2烟雾传感器在写代码之前咱们得先了解要驱动的对象。MQ-2传感器就像是一个电子鼻子专门“嗅探”可燃气体和烟雾。1.1 传感器是怎么工作的MQ-2的核心是一种叫二氧化锡的半导体材料。简单理解它的电阻会随着周围烟雾浓度的变化而变化。空气中氧气多的时候它的电阻比较大当有可燃气体或烟雾时它的表面导电性变强电阻就变小了。传感器模块会把这种电阻变化转换成电压变化输出给我们。烟雾浓度越大输出的模拟电压就越高。注意MQ-2传感器在使用前需要预热就像老式的电子管收音机要热机一样不预热的话它输出的数据会很不准。通常上电后需要等待几十秒到一分钟让它内部的加热丝稳定工作。1.2 模块引脚与参数咱们常用的模块通常有4个引脚2.54mm排针工作电压是5V。关键要分清两个输出引脚引脚标识类型功能说明VCC电源接5V电源正极GND电源接电源地DO数字输出输出高低电平。当气体浓度超过模块上电位器设定的阈值时输出高电平否则为低电平。AO模拟输出输出模拟电压0-5V。浓度越高电压越高。这是我们采集数据的主要接口。我们的目标是采集AO引脚的模拟电压所以需要用到开发板的ADC模数转换器功能。2. 硬件连接与引脚规划2.1 连线很简单把MQ-2模块和梁山派开发板连起来MQ-2 VCC- 开发板5V引脚MQ-2 GND- 开发板GND引脚MQ-2 AO- 开发板PC1引脚这是我们选定的ADC输入引脚MQ-2 DO- 开发板PF11引脚可选用于阈值报警本文重点在AO2.2 为什么选PC1引脚梁山派的核心是GD32F470芯片不是每个引脚都能做ADC输入。我们需要查阅芯片数据手册Datasheet找到带有ADC功能的引脚。根据资料PC1引脚复用了ADC0的第11输入通道ADC0_IN11正好符合我们的需求。DO引脚我们选择PF11这只是一个普通的GPIO输入引脚接上去是为了完整性在代码里配置成输入模式即可。3. 软件设计思路为什么用ADCDMA采集模拟信号最直接的想法是让ADC转换然后CPU去读结果。但这样CPU就得频繁守着ADC效率太低。这里我们引入DMA直接存储器访问。你可以把DMA想象成一个特别能干的“搬运工”。我们告诉它“去ADC那里每转换好一个数据就把它搬到内存的这个数组里搬满30个为止然后循环从头开始搬。” 设置好之后这个搬运过程完全不需要CPU参与。CPU可以安心去执行其他任务比如处理网络数据、刷新屏幕等只需要在需要的时候去数组里读取已经搬好的数据就行。这种“ADC负责转换DMA负责搬运CPU负责处理”的模式是嵌入式里非常经典的高效数据采集方案。4. 手把手代码实现接下来是重头戏咱们一步步把代码写出来。我会把关键代码都列出来并加上详细注释。4.1 头文件定义 (bsp_mq2.h)头文件主要进行宏定义把引脚、外设通道等固定信息定义好这样主程序看起来更清晰修改也方便。#ifndef _BSP_MQ2_H_ #define _BSP_MQ2_H_ #include gd32f4xx.h // 时钟定义 #define RCU_MQ2_GPIO_AO RCU_GPIOC // AO引脚(PC1)的GPIO时钟 #define RCU_MQ2_GPIO_DO RCU_GPIOF // DO引脚(PF11)的GPIO时钟 #define RCU_MQ2_ADC RCU_ADC0 // ADC0时钟 #define RCU_MQ2_DMA RCU_DMA1 // DMA1时钟 // 外设定义 #define PORT_DMA DMA1 // 使用DMA1 #define CHANNEL_DMA DMA_CH0 // 使用DMA1的通道0 #define PORT_ADC ADC0 // 使用ADC0 #define CHANNEL_ADC ADC_CHANNEL_11 // 使用ADC0的第11通道 (对应PC1) // 引脚定义 #define PORT_MQ2_AO GPIOC // AO引脚端口 #define GPIO_MQ2_AO GPIO_PIN_1 // AO引脚号 (PC1) #define PORT_MQ2_DO GPIOF // DO引脚端口 #define GPIO_MQ2_DO GPIO_PIN_11 // DO引脚号 (PF11) // 采样参数 #define SAMPLES 30 // 每个通道采样30次 #define CHANNEL_NUM 1 // 总共1个ADC通道 (只有MQ2的AO) // 声明DMA用的缓冲区数组在.c文件中定义 extern uint16_t gt_adc_val[SAMPLES][CHANNEL_NUM]; // 函数声明 void ADC_DMA_Init(void); // 初始化ADC和DMA unsigned int Get_Adc_Dma_Value(char CHx); // 获取指定通道的平均ADC值 unsigned int Get_MQ2_Percentage_value(void); // 获取MQ2浓度百分比 #endif4.2 核心驱动实现 (bsp_mq2.c)这是最关键的部分包含了ADC和DMA的完整配置。#include bsp_mq2.h #include systick.h // 用于延时函数 // DMA缓冲区一个二维数组计划存储 SAMPLES 次采样每次 CHANNEL_NUM 个通道的数据。 // 虽然我们只有一个通道但这样写结构更清晰方便以后扩展。 uint16_t gt_adc_val[SAMPLES][CHANNEL_NUM]; /****************************************************************** * 函数名称ADC_DMA_Init * 函数说明初始化ADC和DMA配置PC1为ADC输入PF11为数字输入 * 并设置ADC连续扫描、DMA自动传输。 * 函数形参无 * 函数返回无 ******************************************************************/ void ADC_DMA_Init(void) { // 1. 初始化DMA配置结构体 dma_single_data_parameter_struct dma_single_data_parameter; // 2. 打开所有需要用到的外设时钟开关总闸 rcu_periph_clock_enable(RCU_MQ2_GPIO_AO); // 打开GPIOC时钟 rcu_periph_clock_enable(RCU_MQ2_GPIO_DO); // 打开GPIOF时钟 rcu_periph_clock_enable(RCU_MQ2_ADC); // 打开ADC0时钟 rcu_periph_clock_enable(RCU_MQ2_DMA); // 打开DMA1时钟 // 3. 配置ADC时钟源和分频 adc_clock_config(ADC_ADCCK_PCLK2_DIV4); // ADC时钟 PCLK2 / 4 // 4. 配置GPIO引脚模式 // DO引脚(PF11)配置为上拉输入读取高低电平 gpio_mode_set(PORT_MQ2_DO, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_MQ2_DO); // AO引脚(PC1)配置为模拟输入模式这是ADC引脚必须的模式 gpio_mode_set(PORT_MQ2_AO, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_MQ2_AO); // 5. 配置ADC工作模式 adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT); // 独立模式只有一个ADC adc_special_function_config(PORT_ADC, ADC_CONTINUOUS_MODE, ENABLE); // 使能连续转换模式 adc_special_function_config(PORT_ADC, ADC_SCAN_MODE, ENABLE); // 使能扫描模式多通道时需要 adc_data_alignment_config(PORT_ADC, ADC_DATAALIGN_RIGHT); // 数据右对齐方便阅读 // 6. 配置ADC规则组通道告诉ADC要转换哪个通道按什么顺序 adc_channel_length_config(PORT_ADC, ADC_REGULAR_CHANNEL, CHANNEL_NUM); // 规则组通道数为1 // 配置第0个扫描顺序为通道11(PC1)采样时间15个周期时间越长精度越高速度越慢 adc_regular_channel_config(PORT_ADC, 0, CHANNEL_ADC, ADC_SAMPLETIME_15); // 7. 配置ADC其他参数 adc_resolution_config(PORT_ADC, ADC_RESOLUTION_12B); // 12位分辨率0-4095 adc_external_trigger_config(PORT_ADC, ADC_REGULAR_CHANNEL, EXTERNAL_TRIGGER_DISABLE); // 禁用外部触发用软件触发 // 8. 配置ADC与DMA的联动 adc_dma_request_after_last_enable(PORT_ADC); // 使能规则组通道转换完成后就发DMA请求 adc_dma_mode_enable(PORT_ADC); // 使能ADC的DMA传输 // 9. 使能ADC并校准校准很重要能减少误差 adc_enable(PORT_ADC); delay_1ms(1); // 短暂延时等待ADC稳定 adc_calibration_enable(PORT_ADC); // 开启自校准 // 10. 配置DMA告诉“搬运工”从哪里搬搬到哪里搬多少 dma_deinit(PORT_DMA, CHANNEL_DMA); // 先复位DMA通道清除旧配置 // 配置DMA参数结构体 dma_single_data_parameter.periph_addr (uint32_t)(ADC_RDATA(PORT_ADC)); // 外设地址ADC0的数据寄存器 dma_single_data_parameter.periph_inc DMA_PERIPH_INCREASE_DISABLE; // 外设地址不递增始终读同一个寄存器 dma_single_data_parameter.memory0_addr (uint32_t)(gt_adc_val); // 内存地址我们的数组首地址 dma_single_data_parameter.memory_inc DMA_MEMORY_INCREASE_ENABLE; // 内存地址递增存到数组下一个位置 dma_single_data_parameter.periph_memory_width DMA_PERIPH_WIDTH_16BIT; // 数据宽度16位ADC是12位用16位变量存 dma_single_data_parameter.direction DMA_PERIPH_TO_MEMORY; // 传输方向从外设到内存 dma_single_data_parameter.number SAMPLES * CHANNEL_NUM; // 传输数据量30个样本 * 1个通道 dma_single_data_parameter.priority DMA_PRIORITY_HIGH; // DMA通道优先级高 // 初始化DMA通道 dma_single_data_mode_init(PORT_DMA, CHANNEL_DMA, dma_single_data_parameter); // 11. 选择DMA通道对应的外设告诉DMA1通道0是为ADC0服务的 dma_channel_subperipheral_select(PORT_DMA, CHANNEL_DMA, DMA_SUBPERI0); // 12. 使能DMA循环模式并启动 dma_circulation_enable(PORT_DMA, CHANNEL_DMA); // 循环模式搬完30个数据后回到数组开头继续搬 dma_channel_enable(PORT_DMA, CHANNEL_DMA); // 使能DMA通道 // 13. 软件触发ADC开始转换一旦开始ADC就会按照连续模式一直转换DMA一直搬运 adc_software_trigger_enable(PORT_ADC, ADC_REGULAR_CHANNEL); }初始化函数看起来很长但逻辑是清晰的开时钟 - 配引脚 - 配ADC - 配DMA - 启动。配置完成后ADC就会自动、连续地把PC1引脚上的电压值转换成数字并且DMA会自动把转换结果存到gt_adc_val数组里。4.3 数据读取与处理函数ADC和DMA在后台默默工作我们需要的时候就去缓冲区里取数据。/****************************************************************** * 函数名称Get_Adc_Dma_Value * 函数说明从DMA缓冲区中读取指定通道的ADC值并计算平均值。 * 平均值滤波可以有效地减少单次采样的随机噪声干扰。 * 函数形参CHx - 通道序号我们只有0通道 * 函数返回指定通道的平均ADC值0-4095 ******************************************************************/ unsigned int Get_Adc_Dma_Value(char CHx) { unsigned char i 0; unsigned int AdcValue 0; // 累加 SAMPLES 次采样的数据 for(i0; i SAMPLES; i) { AdcValue gt_adc_val[i][CHx]; // gt_adc_val[i][0] 存储着第i次采样的ADC值 } // 求平均值 AdcValue AdcValue / SAMPLES; return AdcValue; } /****************************************************************** * 函数名称Get_MQ2_Percentage_value * 函数说明获取MQ2传感器的浓度百分比。 * 将12位ADC值0-4095映射到百分比0-100%。 * 函数形参无 * 函数返回浓度百分比0-100 ******************************************************************/ unsigned int Get_MQ2_Percentage_value(void) { int adc_max 4095; // 12位ADC最大值 int adc_new 0; int Percentage_value 0; // 1. 获取平均ADC值 adc_new Get_Adc_Dma_Value(0); // 2. 计算百分比(当前值 / 最大值) * 100 // 注意这里用了强制类型转换为float确保计算是浮点数运算避免整数除法丢失精度。 Percentage_value ((float)adc_new / adc_max) * 100; return Percentage_value; }这里用了两个技巧平均值滤波不是读一次ADC就完事而是把DMA循环搬运的30个数据求平均。这样可以平滑掉电源波动、环境噪声带来的干扰让读数更稳定。百分比转换把原始的ADC值0-4095转换成更直观的百分比0-100。注意这个百分比是电压百分比不是精确的ppm浓度。要得到实际浓度还需要根据传感器数据手册进行标定和公式换算。5. 在主函数中调用最后我们在主函数里把上面的模块用起来。#include gd32f4xx.h #include systick.h #include bsp_usart.h // 串口初始化用于打印数据 #include bsp_mq2.h #include stdio.h int main(void) { // 1. 系统初始化 nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 设置中断优先级分组 systick_config(); // 初始化系统滴答定时器用于delay_ms usart_gpio_config(115200U); // 初始化串口波特率115200用于打印 // 2. 初始化MQ2的ADC和DMA ADC_DMA_Init(); printf(MQ-2 ADCDMA Data Collection Demo Start\r\n); // 3. 主循环 while(1) { // 每隔1秒读取并打印一次ADC原始值和浓度百分比 printf(ADC Value: %d\r\n, Get_Adc_Dma_Value(0)); printf(MQ-2 Percentage: %d%%\r\n, Get_MQ2_Percentage_value()); printf(-----------------\r\n); delay_1ms(1000); // 延时1秒 } }把代码编译下载到梁山派开发板打开串口助手如SecureCRT、Putty等设置好波特率115200就能看到每秒输出一次的数据了。对着传感器吹口气或者用打火机注意安全别烧了释放少量气体观察ADC值和百分比的变化。几个你可能遇到的坑点数据跳动大首先检查电源是否稳定传感器和开发板最好共地。其次确保传感器已充分预热通电等待1分钟以上。最后可以尝试增加SAMPLES的数值比如改成100或者调整ADC_SAMPLETIME_15为更长的采样时间如ADC_SAMPLETIME_239牺牲速度换取稳定性。DMA数据不更新检查DMA缓冲区数组gt_adc_val是否被定义为全局变量。检查DMA的源地址ADC数据寄存器和目标地址数组地址配置是否正确。确保adc_software_trigger_enable被调用启动了ADC转换。百分比计算为0检查Get_Adc_Dma_Value(0)返回的值是否正常0-4095。如果ADC值始终很小检查硬件连接PC1引脚是否确实接到了模块的AO引脚模块VCC是否接了5V。这套ADCDMA的采集框架非常实用不光适用于MQ-2对于其他模拟输出的传感器比如MQ-135、温湿度传感器、光照传感器等也完全适用你只需要修改一下引脚定义和通道配置即可。希望这篇教程能帮你打通GD32的ADC和DMA应用之路。