基于立创GD32E230C8T6开发板的GP2Y1014AU粉尘传感器ADC驱动与浓度计算实战
基于立创GD32E230C8T6开发板的GP2Y1014AU粉尘传感器ADC驱动与浓度计算实战
最近在做一个室内空气质量监测的小项目,需要检测空气中的粉尘浓度,正好手头有立创的GD32E230C8T6开发板和GP2Y1014AU粉尘传感器。网上资料虽然多,但直接能用在国产GD32芯片上的完整教程不多,调试过程踩了不少坑。今天我就把整个从硬件连接到软件驱动,再到浓度计算的完整过程整理出来,手把手教你怎么在立创开发板上实现粉尘浓度检测。
这篇文章适合正在学习GD32开发,或者想用国产MCU做环境监测项目的朋友。我会尽量用大白话把原理讲清楚,代码也会详细注释,保证你跟着做就能出结果。
1. 认识你的“眼睛”:GP2Y1014AU粉尘传感器
在写代码之前,咱们得先了解手里的传感器是怎么工作的。GP2Y1014AU这个小模块,别看它个头不大,原理挺巧妙的。
你拿到传感器后,会看到中间有个小洞,空气就是从这里流通的。在洞的两侧,一边装了一个红外发光二极管(可以理解为一个小型红外线手电筒),另一边装了一个光电晶体管(相当于一个红外线“眼睛”)。
它的工作原理是这样的:
- 发射:红外发光二极管持续发射一束红外光。
- 阻碍与反射:当空气中飘浮着粉尘颗粒(比如PM2.5)时,这些颗粒会挡住并散射红外光。
- 接收:一部分被散射的红外光会飞到对面的光电晶体管上。
- 转换:光电晶体管接收到光信号后,会把它转换成电信号。空气中的粉尘越多,散射的光就越多,光电晶体管输出的电压就越高。
所以,这个传感器最终给我们的,是一个模拟电压信号(AO引脚输出)。我们的任务就是用GD32E230的ADC(模数转换器)去读取这个电压,再通过一个公式把它换算成我们关心的浓度值(比如毫克每立方米,mg/m³)。
注意:传感器需要5-7V供电,而我们的开发板GPIO是3.3V电平。所以你需要一个外部的5V电源(比如USB口或稳压模块)给传感器单独供电。传感器的输出信号(AO引脚)电压在0-3.3V范围内,可以直接接到开发板的ADC引脚。
传感器关键参数速查表:
| 参数 | 数值 | 说明 |
|---|---|---|
| 工作电压 | 5-7V | 必须外接5V电源,不能从开发板3.3V取电 |
| 工作电流 | 最大20mA | 功耗很低 |
| 检测粒径 | 0.8微米 | 可以检测到PM2.5级别的颗粒物 |
| 灵敏度 | 0.5V/(0.1mg/m³) | 浓度每变化0.1mg/m³,输出电压变化约0.5V |
| 清洁空气电压 | 0.9V(典型值) | 在干净空气中,输出大约0.9V |
2. 硬件连接:把传感器和开发板“牵上线”
硬件连接很简单,就三根线,但接错了可能烧东西,咱们仔细看一下。
所需材料:
- 立创GD32E230C8T6开发板 一块
- GP2Y1014AU粉尘传感器 一个
- 5V电源(如USB充电头+MicroUSB线,或稳压模块) 一个
- 杜邦线 若干
接线步骤:
传感器供电(VCC & GND):
- 找到传感器的
VCC和GND引脚。 - 将
VCC连接到你的5V电源正极。 - 将
GND连接到5V电源的负极,同时,这个GND还必须用一根杜邦线连接到开发板的GND引脚。这一步至关重要!只有共地,ADC才能正确读取电压。
- 找到传感器的
信号线连接(AO & LED):
- 传感器的
AO(模拟输出)引脚,连接到开发板的PA1引脚。这是我们ADC采集的入口。 - 传感器的
LED引脚,连接到开发板的PB1引脚。这个引脚很关键,它用来控制传感器内部的红外LED。传感器要求我们以特定的时序来点亮和熄灭这个LED,然后在这个时间窗口内读取AO的电压,这样才能得到有效的测量值。
- 传感器的
开发板供电:开发板通过其自身的MicroUSB口供电(3.3V系统)。
连接好之后,你的接线应该是这样的:
- 传感器端:VCC→5V, GND→5V地 & 开发板GND, AO→开发板PA1, LED→开发板PB1。
- 开发板端:独立USB供电。
提示:如果手头有万用表,可以在传感器通电后测量一下AO对GND的电压。在静止空气中,它应该稳定在0.9V左右。吹一口气(增加粉尘),电压会瞬间升高然后回落。有这个现象,说明传感器本身是好的。
3. 软件驱动:配置GD32E230的ADC
硬件搞定,接下来就是重头戏——写代码。咱们的目标是让GD32的ADC能准确读取PA1(也就是AO)的电压。我把它分解成几个步骤。
3.1 建立工程与文件
首先,在你的GD32工程里(比如用Keil或VSCode+GCC),创建两个文件:
bsp_dust.c:传感器驱动源文件。bsp_dust.h:传感器驱动头文件。
头文件bsp_dust.h里主要是引脚定义和函数声明,先把框架搭好:
#ifndef _BSP_DUST_H_ #define _BSP_DUST_H_ #include "gd32e23x.h" // 控制传感器LED的引脚定义 (连接传感器LED引脚) #define RCU_LED RCU_GPIOB #define PORT_LED GPIOB #define GPIO_LED GPIO_PIN_1 // 读取传感器AO信号的引脚定义 (连接传感器AO引脚) #define RCU_OUT RCU_GPIOA #define PORT_OUT GPIOA #define GPIO_OUT GPIO_PIN_1 // ADC相关定义 (PA1对应ADC通道1) #define RCU_OUT_ADC RCU_ADC #define PORT_OUT_ADC ADC #define CHANNEL_OUT_ADC ADC_CHANNEL_1 // 采样通道数,我们只用1个通道(PA1) #define CHANNEL_NUM 1 // 函数声明 void Dust_GPIO_Init(void); // 初始化函数 float Read_dust_concentration(void); // 读取浓度函数 #endif3.2 初始化ADC与GPIO
接下来在bsp_dust.c里实现初始化函数Dust_GPIO_Init。这个函数要做的事情比较多,我一步步解释。
#include "bsp_dust.h" #include "systick.h" // 用于延时函数 #include "bsp_usart.h" // 用于printf调试(可选) #include "stdio.h" void Dust_GPIO_Init(void) { /* 第1步:打开相关时钟 */ rcu_periph_clock_enable(RCU_OUT); // 使能GPIOA时钟 (PA1) rcu_periph_clock_enable(RCU_LED); // 使能GPIOB时钟 (PB1) rcu_periph_clock_enable(RCU_OUT_ADC); // 使能ADC时钟 /* 第2步:配置ADC时钟源 */ // ADC模块工作频率不能太高。系统主频72MHz,这里选择4分频,ADC时钟=72/4=18MHz rcu_adc_clock_config(RCU_ADCCK_APB2_DIV4); /* 第3步:配置GPIO引脚模式 */ // 配置PB1为推挽输出,用来控制传感器LED gpio_mode_set(PORT_LED, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_LED); gpio_output_options_set(PORT_LED, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_LED); gpio_bit_write(PORT_LED, GPIO_LED, SET); // 初始化为高电平,LED灭 // 配置PA1为模拟输入模式,这是ADC引脚的标准配置 gpio_mode_set(PORT_OUT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_OUT); /* 第4步:配置ADC工作模式 */ adc_special_function_config(ADC_CONTINUOUS_MODE, ENABLE); // 使能连续转换模式 adc_special_function_config(ADC_SCAN_MODE, ENABLE); // 使能扫描模式(多通道时才需要,单通道也建议开启) adc_data_alignment_config(ADC_DATAALIGN_RIGHT); // 数据右对齐(方便阅读) /* 第5步:配置ADC通道 */ adc_channel_length_config(ADC_REGULAR_CHANNEL, CHANNEL_NUM); // 规则组通道数设为1 // 将通道1(PA1)配置为规则组第0个转换序列,采样时间13.5个周期 adc_regular_channel_config(0, CHANNEL_OUT_ADC, ADC_SAMPLETIME_13POINT5); /* 第6步:其他ADC参数 */ adc_resolution_config(ADC_RESOLUTION_12B); // 12位分辨率,结果范围0-4095 // 禁用外部触发,使用软件触发转换 adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE); adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE); /* 第7步:使能ADC并校准 */ adc_enable(); // 使能ADC模块 delay_1ms(1); // 短暂延时等待ADC稳定 adc_calibration_enable(); // 执行自校准,消除内部误差,这一步很重要! /* 第8步:启动转换 */ adc_software_trigger_enable(ADC_REGULAR_CHANNEL); // 软件触发开始连续转换 }初始化完成后,ADC就会自动在后台连续将PA1的电压值转换成数字量,我们随时可以去读取。
3.3 编写数据滤波函数
直接读取一次ADC值往往跳动很大,因为信号里有噪声。为了得到稳定的数据,我们需要做软件滤波。这里我采用一个简单的滑动平均滤波,取最近10次采样的平均值。
// 滑动平均滤波器,参数m是新的ADC采样值,返回值是滤波后的值 int Filter(int m) { static int flag_first = 0; // 首次运行标志 static int _buff[10]; // 存储最近10次值的数组 static int sum; // 数组元素总和 const int _buff_max = 10; // 窗口大小 int i; // 如果是第一次调用,用当前值m初始化整个数组 if (flag_first == 0) { flag_first = 1; for (i = 0, sum = 0; i < _buff_max; i++) { _buff[i] = m; sum += _buff[i]; } return m; } else { // 1. 从总和中减去最旧的数据(_buff[0]) sum -= _buff[0]; // 2. 将数组所有元素向前移动一位 for (i = 0; i < (_buff_max - 1); i++) { _buff[i] = _buff[i + 1]; } // 3. 将新数据m放入数组末尾 _buff[_buff_max - 1] = m; // 4. 将新数据加入总和 sum += _buff[_buff_max - 1]; // 5. 返回平均值 i = sum / 10.0; return i; } }这个滤波器的效果就是“平滑”数据,让显示出来的浓度值不会跳来跳去。你可以通过修改_buff_max来调整平滑程度,值越大越平滑但反应越慢。
4. 核心逻辑:读取并计算粉尘浓度
GP2Y1014AU传感器对测量时序有严格要求,不能一直亮着LED读数据。必须按照它数据手册里的时序来:先点亮LED,等待一段特定时间后读取电压,然后熄灭LED,再等待一个周期结束。我们的Read_dust_concentration函数就是干这个的。
float Read_dust_concentration(void) { unsigned int value = 0; float f_value = 0, density = 0; /* 严格按照传感器时序操作 */ gpio_bit_write(PORT_LED, GPIO_LED, RESET); // 1. 拉低PB1,点亮传感器LED delay_1us(280); // 2. 等待280微秒 (关键延时!) value = adc_regular_data_read(); // 3. 读取此刻的ADC值 delay_1us(40); // 4. 再等待40微秒 gpio_bit_write(PORT_LED, GPIO_LED, SET); // 5. 拉高PB1,熄灭传感器LED delay_1us(9680); // 6. 等待9680微秒,完成一个测量周期 // 对原始ADC值进行滤波 value = Filter(value); // 将ADC值转换为电压值 (GD32E230的ADC参考电压Vref+通常是3.3V) // ADC值范围0-4095对应电压0-3.3V f_value = (value / 4095.0) * 3.3; // 将电压值转换为粉尘浓度 (mg/m³) // 公式: 浓度 = (电压 - 清洁空气电压) / 灵敏度 // 根据资料:清洁空气典型电压Vc = 0.9V, 灵敏度K = 0.5V / 0.1mg/m³ // 推导出:浓度 = (V - 0.9) / 0.5 * 0.1 = 0.2 * (V - 0.9) // 注意:原始代码中使用了 0.17*value-0.1,这是将ADC值直接换算的简化公式。 // 为了更清晰,我们使用基于电压的公式: if(f_value > 0.9) { density = 0.2 * (f_value - 0.9); } else { density = 0.0; // 电压低于或等于清洁空气电压,认为浓度为零 } return density; // 返回浓度值,单位 mg/m³ }重要提示:
delay_1us(280)和delay_1us(40)这两个延时非常关键,必须尽可能精确。你需要确保你的systick或定时器提供的delay_1us函数是准确的。如果不准,测量值会有偏差。delay_1us(9680)是保证两次测量间隔至少10ms,让传感器内部准备好下一次测量。
5. 上机测试与现象观察
最后,我们在主函数里调用这些功能,并把浓度值打印出来看看。
#include "gd32e23x.h" #include "systick.h" #include "bsp_usart.h" // 用于printf #include "stdio.h" #include "bsp_dust.h" int main(void) { systick_config(); // 初始化系统滴答定时器,提供延时函数 usart_gpio_config(115200U); // 初始化串口,用于打印数据到电脑 printf("GP2Y1014AU Dust Sensor Demo Start!\r\n"); Dust_GPIO_Init(); // 初始化粉尘传感器相关硬件 while(1) { // 读取并打印粉尘浓度 float dust = Read_dust_concentration(); printf("Dust Concentration: %.3f mg/m³\r\n", dust); delay_1ms(1000); // 每秒读取一次 } }将代码编译下载到开发板,打开串口助手(波特率115200),你应该能看到类似这样的输出:
Dust Concentration: 0.023 mg/m³ Dust Concentration: 0.021 mg/m³ ...测试现象:
- 在静止、干净的空气中,数值应该比较低且稳定(接近0或零点零几)。
- 对着传感器的进气孔轻轻吹一口气(注意别把口水吹进去),数值会有一个明显的脉冲式上升,然后缓慢下降。这是因为你吹出的气体中含有水分、皮屑等颗粒物,被传感器检测到了。
- 点一支香或产生一些烟雾靠近传感器,数值会持续升高。
如果现象符合,恭喜你,驱动移植成功!这个传感器比较适合检测相对浓度变化,用于判断空气质量变好还是变差。如果需要绝对精确的测量,可能需要进行校准,并考虑温湿度补偿。但用于大多数DIY项目或定性监测,已经完全够用了。实际项目中,我一般会把滤波窗口再调大一点,让显示更稳定,同时把打印间隔改为2-3秒一次,避免串口数据刷得太快。
