基于TI MSPM0G3507的GP2Y1014AU粉尘传感器PM2.5检测实战

最近在做一个室内空气质量监测的小项目,需要检测PM2.5浓度。我选用了夏普的GP2Y1014AU粉尘传感器,搭配TI的MSPM0G3507开发板。这个组合性价比高,很适合初学者上手。今天我就把整个从硬件连接到软件驱动、再到数据处理的完整过程分享给大家,手把手教你实现一个可用的PM2.5检测功能。

1. 认识我们的“眼睛”:GP2Y1014AU粉尘传感器

在动手接线写代码之前,咱们先得搞清楚这个传感器是怎么工作的。GP2Y1014AU是一个光学粉尘传感器,它的原理挺有意思的。

你可以把它想象成一个微型的光学探测室。传感器中间有个洞,空气可以自由流过。在内部的一个角落里,装着一个红外发光二极管(IR LED),而在它对面的角落里,装着一个光电晶体管(也就是光敏三极管)。

工作的时候,红外LED会发出一束红外光。如果空气中干干净净,没什么灰尘,这束光就照不到对面的光电晶体管上。但是,一旦空气中有悬浮的颗粒物(比如PM2.5),红外光照射到这些颗粒上就会发生“漫反射”——就像手电筒照在烟雾上一样,光会向四面八方散射。这部分散射的光就有可能被角落里的光电晶体管接收到。

光电晶体管接收到光信号后,会转换成电信号,从传感器的模拟输出引脚(AO)输出一个电压。关键点来了:在一定范围内,空气中的灰尘浓度越高,散射的光就越强,输出的电压也就越高。它们之间近似成线性关系。所以,我们只需要用单片机的ADC(模数转换器)去读取这个电压值,再通过一个公式换算,就能得到PM2.5的浓度了。

这是它的核心规格,接线和编程时都用得上:

参数数值/说明
工作电压5-7V (注意,逻辑部分和LED驱动需要5V)
消耗电流最大20mA
最小检出粒子0.8微米 (可以检测PM2.5甚至PM1.0)
灵敏度0.5V / (0.1mg/m³)
清洁空气电压典型值0.9V (这是判断传感器是否正常的一个参考)
模块引脚红线(VCC)、黑线(VO/AO)、白线(LED-GND)、黄线(GND)、绿线(GND)、蓝线(VCC-LED)

注意:传感器有两条VCC和两条GND,这是为了分开给传感器内部的红外LED和信号处理电路供电,减少干扰。接线时务必区分清楚。

2. 硬件连接:把传感器“插”到开发板上

知道了原理,接下来就是动手连接。我使用的是TI MSPM0G3507开发板,它自带ADC外设,正好用来读取传感器的模拟电压。

传感器的线序比较多,别接错了。根据原始资料,连接关系如下:

传感器引脚(线色)连接到 MSPM0G3507 开发板
蓝线 (LED驱动电压)3.3V 引脚
绿线 (信号地)GND 引脚
白线 (LED控制)PA26 (普通GPIO,用于控制红外LED开关)
黄线 (电源地)GND 引脚
黑线 (模拟输出 VO/AO)PA27 (配置为ADC输入通道)
红线 (模块电源)5V 引脚

接线要点解析:

  1. 电源:红线接5V,这是传感器主电路的工作电压。蓝线接3.3V,这是专门给内部红外LED供电的,采用较低的电压驱动可能有助于延长LED寿命或适应单片机GPIO电平。
  2. 地线:绿线和黄线都是地线,接到开发板的GND即可。
  3. 关键信号线:
    • 白线 (LED):接PA26。这条线不是提供电源,而是用来控制红外LED何时点亮。传感器要求LED以特定的脉冲方式工作,我们需要用单片机GPIO来精确控制它的亮灭时序。
    • 黑线 (VO/AO):接PA27。这是传感器的“声音”,它输出的电压信号就通过这根线传给我们。我们需要把PA27配置成ADC的输入通道,才能读取这个电压值。

3. 软件配置:让开发板准备好“听”和“说”

硬件连好了,接下来要在软件里告诉单片机,PA27用来读ADC,PA26用来输出控制信号。我们用TI提供的SysConfig图形化工具来配置,非常直观。

  1. 在你的CCS工程中找到并双击 empty.syscfg 文件,打开配置界面。
  2. 点击 ADD 按钮,添加一个GPIO配置项。
  3. 配置GPIO:
    • 找到 PA26,将其模式设置为 GPIO Output(GPIO输出)。它就是用来控制传感器LED的。
    • PA27我们暂时不用在这里配置为GPIO,因为它将被ADC模块直接管理。
  4. 再次点击 ADD,这次选择添加一个 ADC12 配置项(MSPM0G3507的ADC是12位的)。
  5. 配置ADC:
    • 在ADC的通道配置里,选择使用 通道0。
    • 将通道0的输入引脚指定为 PA27。这样,SysConfig就会自动帮我们把PA27配置为ADC模拟输入功能,无需再手动设置GPIO模式。
  6. 按下 Ctrl + S 保存配置。

保存后,点击编译工程。这里可能会弹出一些关于引脚冲突的警告,通常是因为默认工程有一些预配置,我们暂时可以不用管它,直接继续。 SysConfig工具会根据你的图形化设置,自动生成底层的配置代码,并更新 ti_msp_dl_config.h 和 ti_msp_dl_config.c 这些文件。我们的工程已经包含了 board.h,而它又引用了上述配置头文件,所以后续编程中,我们直接 #include “board.h” 就能使用所有配置好的功能了,非常方便。

4. 驱动代码编写:核心逻辑的实现

配置好硬件抽象层,我们来写具体的应用代码。我习惯把不同外设的驱动代码分开放。我们在项目里创建一个 BSP (Board Support Package) 文件夹,里面再建 inc (头文件) 和 src (源文件) 两个子文件夹。

新建两个文件:bsp_dust.c 和 bsp_dust.h,别忘了在CCS的工程属性里把 BSP/inc 路径添加到编译器的头文件包含路径中。

4.1 头文件定义 (bsp_dust.h)

头文件主要是声明函数和定义一些常量。

#ifndef __BSP_DUST_H__
#define __BSP_DUST_H__

#include "board.h" // 包含所有硬件配置

// ADC采样次数,多次采样取平均可以减小噪声干扰
#define SAMPLES         30

// 函数声明:读取粉尘浓度值,单位是 mg/m³
float Read_dust_concentration(void);

#endif

4.2 源文件实现 (bsp_dust.c)

这里是重头戏,我们一步步来拆解。

第一步:基础ADC读取函数 首先,我们需要一个最底层的函数,它能触发一次ADC转换并返回结果。TI的DriverLib库让这个操作变得很简单。

#include "bsp_dust.h"
#include "stdio.h"

/**
  * @brief  读取一次ADC原始数据
  * @param  无
  * @retval ADC转换后的原始数字量 (0-4095 对应 0V-参考电压)
  * @note   这是一个静态函数,仅在文件内部使用
  */
static uint32_t ADC_GET(void)
{
    uint32_t gAdcResult = 0;

    // 1. 使能ADC转换器
    DL_ADC12_enableConversions(ADC12_0_INST);
    // 2. 软件触发,开始一次转换
    DL_ADC12_startConversion(ADC12_0_INST);

    // 3. 等待转换完成。当状态变为“空闲”时,表示转换结束
    while (DL_ADC12_getStatus(ADC12_0_INST) != DL_ADC12_STATUS_CONVERSION_IDLE);

    // 4. 转换完成,停止转换并关闭ADC以节省功耗
    DL_ADC12_stopConversion(ADC12_0_INST);
    DL_ADC12_disableConversions(ADC12_0_INST);

    // 5. 从ADC结果存储器(我们用的是通道0对应的MEM0)中读取数据
    gAdcResult = DL_ADC12_getMemResult(ADC12_0_INST, ADC12_0_ADCMEM_CH0);

    return gAdcResult;
}

第二步:平均值滤波 直接读一次ADC值噪声很大,不稳定。常见的做法是连续采样多次,然后取平均值。我在这里写了一个 Get_ADC_Value 函数。

/**
  * @brief  获取经过平均值滤波后的ADC值
  * @param  无
  * @retval 滤波后的ADC原始值
  */
unsigned int Get_ADC_Value(void)
{
    uint32_t Data = 0;

    // 循环采样 SAMPLES 次(前面定义为30次)
    for(int i = 0; i < SAMPLES; i++)
    {
        Data += ADC_GET(); // 累加每次采样的值
        delay_ms(2);       // 每次采样间隔2ms,避免过于密集
    }

    Data = Data / SAMPLES; // 计算平均值
    return Data;
}

第三步:滑动窗口滤波 平均值滤波能平滑数据,但反应会有点“迟钝”。为了兼顾平滑性和实时性,我通常还会再加一个“滑动平均滤波”。它维护一个固定长度的数据队列,每次新数据进来,就挤掉一个最老的数据,然后计算当前队列里所有数据的平均值。

/**
  * @brief  滑动平均滤波器
  * @param  m: 新采集到的ADC原始值
  * @retval 滤波后的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. 总和减去最早的数据
        sum -= _buff[0];
        // 2. 所有数据向前移动一位
        for (i = 0; i < (_buff_max - 1); i++)
        {
            _buff[i] = _buff[i + 1];
        }
        // 3. 将新数据m放入缓冲区末尾
        _buff[9] = m;
        // 4. 总和加上新数据
        sum += _buff[9];

        // 5. 返回当前平均值
        i = sum / 10.0;
        return i;
    }
}

第四步:核心驱动函数——读取浓度 这是最关键的函数,它严格按照GP2Y1014AU传感器的工作时序来操作,并最终计算出浓度值。

/**
  * @brief  读取并计算粉尘浓度
  * @param  无
  * @retval 粉尘浓度值,单位 mg/m³
  */
float Read_dust_concentration(void)
{
    unsigned int value=0;
    float f_value = 0;

    // === 关键时序开始 ===
    // 1. 点亮红外LED(输出低电平,因为LED阴极接GPIO)
    DL_GPIO_clearPins(GPIO_PORT, GPIO_LED1_PIN); // 假设GPIO_LED1_PIN已定义为PA26
    delay_us(280); // 保持LED点亮280微秒

    // 2. 在LED点亮期间,进行ADC采样(采样的是点亮后稳定期的信号)
    value = Get_ADC_Value(); // 这里会进行多次采样和平均
    delay_us(40); // 再等待40微秒

    // 3. 关闭红外LED(输出高电平)
    DL_GPIO_setPins(GPIO_PORT, GPIO_LED1_PIN);

    // 4. 等待至少9680微秒,直到下一个周期开始
    delay_us(9680);
    // === 关键时序结束 ===
    // 整个周期是 280+40+9680 = 10000us (10ms),即采样频率为100Hz

    // 5. 对采样的原始ADC值进行滑动平均滤波,进一步稳定数据
    value = Filter(value);

    // 6. 将ADC原始值转换为浓度值 (mg/m³)
    // 这个公式 (0.17*value - 0.1) 是经验公式,来源于传感器特性及电路设计
    // value 是ADC读数,f_value 是计算出的浓度
    f_value = 0.17 * value - 0.1;

    // 注意:如果计算结果为负数,通常意味着浓度极低或传感器异常,可以将其视为0
    if(f_value < 0) {
        f_value = 0;
    }

    return f_value;
}

重要提示:0.17 * value - 0.1 这个转换公式是线性的经验公式。它的系数(0.17和-0.1)会因你的具体电路(特别是ADC参考电压、分压电阻等)而有所不同。最准确的方法是通过标定获得:在已知浓度的环境下(如洁净空气和特定浓度的粉尘环境)读取ADC值,然后拟合出属于你自己电路的公式。

5. 主程序调用与验证

最后,我们在主函数里调用这个驱动,并打印出结果看看。

#include "ti_msp_dl_config.h"
#include "board.h"
#include "bsp_dust.h"

int main(void)
{
    // 初始化系统时钟、外设等
    SYSCFG_DL_init();

    // 初始化串口,用于打印数据(假设lc_printf已实现)
    lc_printf("\nGP2Y1014AU PM2.5 Sensor Demo Start.....\r\n");

    while (1)
    {
        // 读取并打印粉尘浓度值
        float dust = Read_dust_concentration();
        lc_printf("Dust Concentration = [ %.2f mg/m³ ]\r\n", dust);

        // 每隔500ms读取一次
        delay_ms(500);
    }
}

把代码编译下载到开发板,打开串口调试助手,你应该能看到类似这样的输出:

GP2Y1014AU PM2.5 Sensor Demo Start.....
Dust Concentration = [ 0.03 mg/m³ ]
Dust Concentration = [ 0.05 mg/m³ ]
...

调试小技巧:

  • 数值为负或异常大:检查转换公式的系数,很可能需要根据你的实际电压进行调整。用万用表测量一下传感器AO引脚在洁净空气中的电压,换算成ADC值代入公式校准。
  • 数值跳动大:尝试增加 SAMPLES 的次数,或者增大滑动平均滤波器的窗口大小(_buff_max)。
  • 没有变化:检查LED控制线(白线)的时序是否正确,用示波器或逻辑分析仪查看PA26引脚是否有精确的10ms周期、低电平280us的脉冲。同时检查ADC引脚是否配置正确。

好了,到这里,一个基于MSPM0G3507的PM2.5检测功能就完整实现了。整个过程涵盖了硬件选型、原理理解、引脚配置、驱动编写和数据处理,是嵌入式传感器应用的一个典型例子。你可以把这个模块集成到你的环境监测项目中,结合Wi-Fi或LoRa模块,就能实现数据的远程上报了。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐