零知IDE——STM32F407VET6与GP2Y1014AU的粉尘监测系统实现

目录

一、硬件系统设计

[1.1 硬件清单](#1.1 硬件清单)

[1.2 接线方案](#1.2 接线方案)

[1.3 具体接线图](#1.3 具体接线图)

[1.4 连接实物图](#1.4 连接实物图)

二、代码架构讲解

[2.1 数据采集算法](#2.1 数据采集算法)

[2.2 数据可视化算法](#2.2 数据可视化算法)

[2.3 循环队列结构](#2.3 循环队列结构)

[2.4 数据访问模式](#2.4 数据访问模式)

[2.5 完整代码](#2.5 完整代码)

三、项目结果演示

[3.1 操作流程](#3.1 操作流程)

[3.2 视频演示](#3.2 视频演示)

四、GP2Y1014AU粉尘传感器工作原理

[4.1 光学检测原理](#4.1 光学检测原理)

[4.2 输出特性曲线](#4.2 输出特性曲线)

[4.3 工作原理](#4.3 工作原理)

五、常见问题解答

[Q1: 传感器读数总是为0或负值怎么办?](#Q1: 传感器读数总是为0或负值怎么办?)

[Q2: 数据波动很大如何优化?](#Q2: 数据波动很大如何优化?)

[Q3: 如何校准传感器?](#Q3: 如何校准传感器?)


1)项目概述

本项目基于STM32F407VET6微控制器的零知增强板和GP2Y1014AU粉尘传感器,开发了一套完整的空气质量监测系统。系统能够实时检测空气中的PM2.5浓度,通过TFT显示屏直观显示当前空气质量状况和历史数据趋势图,并通过串口输出监测数据。适用于室内空气质量监测、环境监测站等场景,为用户提供准确的粉尘浓度数据和空气质量评估。

(2)项目难点及解决方案

问题描述:GP2Y1014AU输出信号微弱且易受干扰

**解决方案:**采用精确的时序控制,确保在LED开启后280us进行采样,此时输出信号最稳定

一、硬件系统设计

1.1 硬件清单

组件 规格 数量
主控板 STM32F407VET6 1
粉尘传感器 GP2Y1014AU 1
TFT显示屏 ST7789 240x240 1
电阻 150Ω 1
电容 220uF 1

1.2 接线方案

根据代码中定义的引脚,硬件连接如下:

STM32F407VET6引脚 连接组件 引脚功能
5V GP2Y1014AU VCC 电源正极
GND GP2Y1014AU GND 电源地
A0 GP2Y1014AU AOUT 模拟输出
3 GP2Y1014AU ILED LED驱动
53 TFT_CS 片选
7 TFT_DC 数据/命令
6 TFT_RST 复位
3.3V TFT VCC 电源
GND TFT GND

1.3 具体接线图

重要提示:GP2Y1014AU需要外接150Ω限流电阻和220uF滤波电容,以确保红外LED工作稳定

1.4 连接实物图

二、代码架构讲解

2.1 数据采集算法

cpp 复制代码
void loop(void) {
    /* === 阶段1: 传感器驱动时序控制 === */
    digitalWrite(iled, HIGH);     // 开启红外LED
    delayMicroseconds(280);       // 关键延时1 - 等待输出稳定
    adcvalue = analogRead(vout);  // ADC采样
    delayMicroseconds(40);        // 关键延时2 - 维持采样窗口
    digitalWrite(iled, LOW);      // 关闭LED以降低功耗

    /* === 阶段2: 信号处理与转换 === */
    // ADC值转电压值 (12位ADC, 0-4095对应0-5000mV)
    voltage = (SYS_VOLTAGE / 1024.0) * adcvalue;

    /* === 阶段3: 浓度计算算法 === */
    if (voltage >= NO_DUST_VOLTAGE) {
        voltage -= NO_DUST_VOLTAGE;  // 扣除基准电压
        density = voltage * COV_RATIO; // 线性转换
    } else {
        density = 0;  // 低于基准电压视为无尘
    }
    
    /* === 阶段4: 数据存储与显示 === */
    history.push(density, millis());  // 存入循环队列
    
    delay(1000); // 1秒采样周期
}

红外LED开启后,需要等待280us延时至光电晶体管输出稳定、在40us最佳检测窗口期内完成ADC转换

2.2 数据可视化算法

cpp 复制代码
void drawGraphLine() {
    int maxDensity = 200; // Y轴最大值
    int prevX = -1, prevY = -1; // 前一个点坐标
    int dataCount = history.getCount();
    
    for(int i = 0; i < dataCount; i++) {
        float value = history.getValue(i);
        if(value < 0.1) continue; // 数据有效性检查
        
        // 坐标映射算法
        int x = 30 + (i * 190 / min(dataCount, history.getSize()));
        int y = 180 - constrain((int)(value * 110 / maxDensity), 0, 110);
        
        // 线段绘制
        if(prevX >= 0) {
            uint16_t color = getColorByDensity(value);
            tft.drawLine(prevX, prevY, x, y, color);
            tft.fillCircle(x, y, 1, color); // 数据点标记
        }
        
        prevX = x;
        prevY = y;
    }
}

// 根据浓度值获取对应颜色
uint16_t getColorByDensity(float density) {
    if(density < WARNING_THRESHOLD) return ST77XX_GREEN;
    else if(density < DANGER_THRESHOLD) return ST77XX_YELLOW;
    else if(density < CRITICAL_THRESHOLD) return ST77XX_ORANGE;
    else return ST77XX_RED;
}

绘制溶度值可视化波形,根据溶度值设置对应曲线的颜色

2.3 循环队列结构

cpp 复制代码
class CircularBuffer {
private:
    static const int SIZE = 240;  // 队列容量:4分钟数据(240秒)
    float buffer[SIZE];           // 浓度数据存储数组
    unsigned long timestamps[SIZE]; // 时间戳存储数组
    int head;     // 队首指针 - 指向下一个写入位置
    int count;    // 当前元素数量
    
public:
    // 构造函数:初始化队列
    CircularBuffer() : head(0), count(0) {
        // 数组初始化为0
        for(int i = 0; i < SIZE; i++) {
            buffer[i] = 0;
            timestamps[i] = 0;
        }
    }

    // 数据插入算法 - O(1)时间复杂度
    void push(float value, unsigned long timestamp) {
        // 1. 数据写入队首位置
        buffer[head] = value;
        timestamps[head] = timestamp;
    
        // 2. 队首指针循环前进
        head = (head + 1) % SIZE;
    
        // 3. 更新元素计数(队列未满时)
        if(count < SIZE) count++;
    }

    // 数据访问算法 - O(1)时间复杂度  
    float getValue(int index) {
        // 边界检查
        if(index >= count) return 0;
    
        // 计算环形索引:从最新数据向前推算
        // head-1: 最新数据位置
        // + SIZE: 避免负数
        // % SIZE: 环形映射
        int pos = (head - 1 - index + SIZE) % SIZE;
        return buffer[pos];
    }
}    

使用固定大小数组模拟队列,当队列满时自动覆盖最旧数据

2.4 数据访问模式

cpp 复制代码
// 获取历史数据的时间序列
void displayHistoricalData() {
    int dataCount = history.getCount();
    
    Serial.println("=== 历史数据 ===");
    for(int i = 0; i < dataCount; i++) {
        // i=0: 最新数据, i=1: 前一个数据, 以此类推
        float value = history.getValue(i);
        unsigned long time = history.getTimestamp(i);
        
        Serial.print("T-");
        Serial.print(i);
        Serial.print("s: ");
        Serial.print(value);
        Serial.println(" ug/m3");
    }
}

将循环队列中的数据通过串口打印输出

2.5 完整代码

cpp 复制代码
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>

// ST7789 display pin definitions
#define TFT_CS   53
#define TFT_DC    7
#define TFT_RST   6

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

// Custom colors
#define ST77XX_NAVY 0x0010 
#define ST77XX_DARKGREEN  0x0320
#define ST77XX_MAROON 0x8000
#define ST77XX_GRAY 0x8410
#define ST77XX_ORANGE 0xFC00
#define ST77XX_CYAN 0x07FF
#define ST77XX_PURPLE 0x8010
#define ST77XX_OLIVE 0x7BE0

// Dust sensor parameters
#define COV_RATIO 0.17       // (ug/m3) / mv
#define NO_DUST_VOLTAGE 200  // mv
#define SYS_VOLTAGE 5000     // ADC参考电压

// 报警阈值
#define WARNING_THRESHOLD 35.0
#define DANGER_THRESHOLD 75.0
#define CRITICAL_THRESHOLD 150.0

/*
I/O define
*/
const int iled = 3;  //drive the led of sensor
const int vout = A0;  //analog input

/*
variable
*/
float density, voltage;
int adcvalue;

// Variables
unsigned long lastScreenSwitch = 0;
unsigned long startTime = 0;
int currentScreen = 0; // 0: Data display, 1: Graph display
const int screenSwitchInterval = 10000; // 10 seconds switch

// 使用循环队列数据结构存储历史数据
class CircularBuffer {
private:
    static const int SIZE = 240; // 4分钟数据(240秒)
    float buffer[SIZE];
    unsigned long timestamps[SIZE];
    int head;
    int count;
    
public:
    CircularBuffer() : head(0), count(0) {
        for(int i = 0; i < SIZE; i++) {
            buffer[i] = 0;
            timestamps[i] = 0;
        }
    }
    
    void push(float value, unsigned long timestamp) {
        buffer[head] = value;
        timestamps[head] = timestamp;
        head = (head + 1) % SIZE;
        if(count < SIZE) count++;
    }
    
    float getValue(int index) {
        if(index >= count) return 0;
        int pos = (head - 1 - index + SIZE) % SIZE;
        return buffer[pos];
    }
    
    unsigned long getTimestamp(int index) {
        if(index >= count) return 0;
        int pos = (head - 1 - index + SIZE) % SIZE;
        return timestamps[pos];
    }
    
    int getCount() { return count; }
    int getSize() { return SIZE; }
};

CircularBuffer history;

void showStartupScreen() {
    tft.fillScreen(ST77XX_BLACK);
    
    // 绘制渐变背景
    for(int i = 0; i < 240; i++) {
        tft.drawFastVLine(i, 0, 240, tft.color565(i/3, i/4, i/2));
    }
    
    // 标题
    tft.setTextColor(ST77XX_WHITE);
    tft.setTextSize(3);
    tft.setCursor(40, 50);
    tft.print("DUST");
    tft.setTextColor(ST77XX_CYAN);
    tft.setTextSize(2);
    tft.setCursor(120, 50);
    tft.print("MONITOR");
    
    // 版本信息
    tft.setTextColor(ST77XX_WHITE);
    tft.setTextSize(1);
    tft.setCursor(60, 90);
    tft.print("v2.0 - Professional Edition");
    
    // 加载动画
    for(int i = 0; i < 200; i += 5) {
        tft.fillRect(20, 150, i, 10, ST77XX_GREEN);
        delay(30);
    }
    
    // 初始化信息
    tft.setTextColor(ST77XX_YELLOW);
    tft.setTextSize(1);
    tft.setCursor(50, 180);
    tft.print("Initializing Sensor...");
    
    delay(1000);
    
    tft.fillRect(50, 180, 140, 10, ST77XX_BLACK);
    tft.setCursor(70, 180);
    tft.print("Ready to Monitor!");
    
    delay(1000);
}

void setup(void) {
    pinMode(iled, OUTPUT);
    digitalWrite(iled, LOW);  //iled default closed
    
    Serial.begin(9600);       //send and receive at 9600 baud
    Serial.println("Dust Monitor System Starting...");
    
    // Initialize display
    tft.init(240, 240);
    tft.setRotation(1);
    tft.fillScreen(ST77XX_BLACK);
    
    // 显示启动界面
    showStartupScreen();
    
    startTime = millis();
    
    // 绘制静态界面
    drawStaticUI();
    
    Serial.println("System initialized");
}

void drawStaticUI() {
    // Clear screen
    tft.fillScreen(ST77XX_BLACK);
    
    // Draw title bar
    tft.fillRect(0, 0, 240, 30, ST77XX_NAVY);
    tft.setTextColor(ST77XX_WHITE);
    tft.setTextSize(2);
    tft.setCursor(60, 8);
    tft.print("Dust Monitor");
    
    // Draw bottom status bar
    tft.fillRect(0, 210, 240, 30, ST77XX_DARKGREEN);
}

void drawDataScreen(float density, float voltage) {
    // Partial refresh data area (30-210 pixel height)
    tft.fillRect(0, 30, 240, 180, ST77XX_BLACK);
    
    // 显示传感器状态
    tft.setTextColor(ST77XX_WHITE);
    tft.setTextSize(1);
    tft.setCursor(10, 35);
    tft.print("Sensor Status: ");
    tft.setTextColor(ST77XX_GREEN);
    tft.print("ACTIVE");
    
    // 显示基准电压
    tft.setTextColor(ST77XX_WHITE);
    tft.setCursor(10, 50);
    tft.print("Base Voltage: ");
    tft.print(NO_DUST_VOLTAGE);
    tft.print(" mV");
    
    // Display current concentration value (large font)
    tft.setTextColor(ST77XX_CYAN);
    tft.setTextSize(3);
    tft.setCursor(40, 80);
    tft.print("Dust:");
    
    // Change color based on concentration value
    uint16_t dustColor;
    if(density < WARNING_THRESHOLD) {
        dustColor = ST77XX_GREEN;
    } else if(density < DANGER_THRESHOLD) {
        dustColor = ST77XX_YELLOW;
    } else if(density < CRITICAL_THRESHOLD) {
        dustColor = ST77XX_ORANGE;
    } else {
        dustColor = ST77XX_RED;
    }
    
    tft.setTextColor(dustColor);
    tft.setTextSize(4);
    tft.setCursor(60, 110);
    if(density < 0.1) {
        tft.print("0.0");
    } else {
        tft.print(density, 1);
    }
    
    tft.setTextSize(2);
    tft.setCursor(180, 120);
    tft.print("ug/m3");
    
    // Display voltage value
    tft.setTextColor(ST77XX_WHITE);
    tft.setTextSize(2);
    tft.setCursor(40, 150);
    tft.print("Voltage: ");
    tft.print(voltage, 1);
    tft.print(" mV");
    
    // Display quality level with background
    int levelWidth = 120;
    tft.fillRect(40, 185, levelWidth, 20, ST77XX_DARKGREEN);
    tft.setCursor(45, 190);
    tft.print("Level: ");
    
    String levelText;
    if(density < WARNING_THRESHOLD) {
        tft.setTextColor(ST77XX_GREEN);
        levelText = "EXCELLENT";
    } else if(density < DANGER_THRESHOLD) {
        tft.setTextColor(ST77XX_YELLOW);
        levelText = "GOOD";
    } else if(density < CRITICAL_THRESHOLD) {
        tft.setTextColor(ST77XX_ORANGE);
        levelText = "LIGHT POLLUTED";
    } else if(density < 200) {
        tft.setTextColor(ST77XX_RED);
        levelText = "MODERATE";
    } else {
        tft.setTextColor(ST77XX_MAROON);
        levelText = "HEAVY";
    }
    tft.print(levelText);
    
    // 报警指示器
    if(density > WARNING_THRESHOLD) {
        tft.fillCircle(220, 110, 8, dustColor);
        tft.setTextColor(ST77XX_WHITE);
        tft.setTextSize(1);
        tft.setCursor(215, 108);
        tft.print("!");
    }
}

void drawGraphScreen() {
    // Draw graph framework
    tft.fillRect(0, 30, 240, 180, ST77XX_BLACK);
    
    // Graph title with time info
    tft.setTextColor(ST77XX_WHITE);
    tft.setTextSize(2);
    tft.setCursor(70, 35);
    tft.print("TREND GRAPH");
    
    // 显示时间范围
    tft.setTextSize(1);
    tft.setCursor(80, 55);
    tft.print("Last 4 Minutes");
    
    // Draw axes with labels
    tft.drawRect(30, 70, 190, 110, ST77XX_GRAY);
    
    // Y-axis scale and labels
    for(int i = 0; i <= 200; i += 50) {
        int y = 180 - (i * 110 / 200);
        tft.drawLine(28, y, 32, y, ST77XX_GRAY);
        tft.setTextSize(1);
        tft.setCursor(5, y-4);
        tft.print(i);
    }
    
    // X-axis time labels
    tft.setCursor(30, 185);
    tft.print("-4m");
    tft.setCursor(100, 185);
    tft.print("-2m");
    tft.setCursor(170, 185);
    tft.print("Now");
    
    // 绘制阈值线
    drawThresholdLines();
    
    // Draw curve
    drawGraphLine();
}

void drawThresholdLines() {
    // 警告阈值线
    int warnY = 180 - (WARNING_THRESHOLD * 110 / 200);
    tft.drawLine(30, warnY, 220, warnY, ST77XX_GREEN);
    tft.setTextSize(1);
    tft.setCursor(222, warnY-4);
    tft.print("Good");
    
    // 危险阈值线
    int dangerY = 180 - (DANGER_THRESHOLD * 110 / 200);
    tft.drawLine(30, dangerY, 220, dangerY, ST77XX_ORANGE);
    tft.setCursor(222, dangerY-4);
    tft.print("Warn");
    
    // 严重阈值线
    int criticalY = 180 - (CRITICAL_THRESHOLD * 110 / 200);
    tft.drawLine(30, criticalY, 220, criticalY, ST77XX_RED);
    tft.setCursor(222, criticalY-4);
    tft.print("Alert");
}

void drawGraphLine() {
    int maxDensity = 200;
    int prevX = -1, prevY = -1;
    int dataCount = history.getCount();
    
    for(int i = 0; i < dataCount; i++) {
        float value = history.getValue(i);
        if(value < 0.1) continue; // 忽略接近0的值
        
        // 计算X坐标(时间轴)
        int x = 30 + (i * 190 / min(dataCount, history.getSize()));
        
        // 计算Y坐标(浓度轴)
        int y = 180 - constrain((int)(value * 110 / maxDensity), 0, 110);
        
        if(prevX >= 0) {
            // 根据浓度值改变线条颜色
            uint16_t color;
            if(value < WARNING_THRESHOLD) color = ST77XX_GREEN;
            else if(value < DANGER_THRESHOLD) color = ST77XX_YELLOW;
            else if(value < CRITICAL_THRESHOLD) color = ST77XX_ORANGE;
            else color = ST77XX_RED;
            
            tft.drawLine(prevX, prevY, x, y, color);
            
            // 在数据点处绘制小点
            tft.fillCircle(x, y, 1, color);
        }
        
        prevX = x;
        prevY = y;
    }
}

void loop(void) {
    /*
    get adcvalue
    */
    digitalWrite(iled, HIGH);
    delayMicroseconds(280);
    adcvalue = analogRead(vout);
    delayMicroseconds(40);
    digitalWrite(iled, LOW);

    /*
    covert voltage (mv)
    */
    voltage = (SYS_VOLTAGE / 1024.0) * adcvalue;

    /*
    voltage to density
    */
    if (voltage >= NO_DUST_VOLTAGE) {
        voltage -= NO_DUST_VOLTAGE;
        density = voltage * COV_RATIO;
    } else
        density = 0;

    // Add to historical data with timestamp
    history.push(density, millis());
    
    // Check if screen needs to switch
    if(millis() - lastScreenSwitch > screenSwitchInterval) {
        currentScreen = (currentScreen + 1) % 2;
        lastScreenSwitch = millis();
        
        // Update bottom status bar
        tft.fillRect(0, 210, 240, 30, ST77XX_DARKGREEN);
        tft.setTextColor(ST77XX_WHITE);
        tft.setTextSize(1);
        tft.setCursor(10, 218);
        tft.print(currentScreen == 0 ? "Data Screen" : "Graph Screen");
        tft.setCursor(170, 218);
        tft.print("Switch in 10s");
        
        // Redraw current screen
        if(currentScreen == 0) {
            drawDataScreen(density, voltage);
        } else {
            drawGraphScreen();
        }
    } else {
        // Partial refresh current screen
        if(currentScreen == 0) {
            drawDataScreen(density, voltage);
        } else {
            // Graph screen only updates graph part
            tft.fillRect(30, 70, 190, 110, ST77XX_BLACK);
            tft.drawRect(30, 70, 190, 110, ST77XX_GRAY);
            drawThresholdLines();
            drawGraphLine();
        }
    }
    
    // 串口输出
    Serial.print("The current dust concentration is: ");
    Serial.print(density);
    Serial.print(" ug/m3\n");

    delay(1000); // 1秒采样间隔
}

循环队列算法工作原理

①初始状态(空队列)

②插入数据A

③插入数据B、C、D

④插入数据E(队列将满)

⑤插入数据F(覆盖最旧数据A)

**优势:**固定大小内存,避免动态分配开销、插入和访问都是O(1)操作

三、项目结果演示

3.1 操作流程

①系统启动

连接电源后,系统显示启动界面,包含加载动画

②数据监测

系统自动进入数据监测模式,显示当前粉尘浓度、实时电压值及空气质量等级

③趋势图界面

每10秒自动切换数据屏和趋势图屏,趋势波形图为绿色,表示当前空气质量优

④数据输出

系统通过零知IDE串口实时输出监测数据:

Dust Monitor System Starting...

System initialized

The current dust concentration is: 25.3 ug/m3

3.2 视频演示

GP2Y1014AU粉尘监测系统

系统启动并显示加载界面,随后进入主监测界面,显示当前空气质量的数据,实时检测气体溶度

四、GP2Y1014AU粉尘传感器工作原理

4.1 光学检测原理

GP2Y1014AU基于光散射原理工作。传感器内部有一个红外发光二极管和一个光电晶体管,成对角布置:

红外LED发射光束(发射阶段)、空气中粉尘颗粒对光线产生散射(散射阶段)、光电晶体管检测散射光强度(检测阶段)、输出与粉尘浓度成正比的电压信号(输出阶段)

4.2 输出特性曲线

GP2Y1014AU0F 传感器输出电压与灰尘浓度关系在 0 到 0.5mg/m3 范围内成线性关系,如下图所示:

在 0 ~ 0.5mg/m3 范围内取部分电压与浓度的对应值,得到如下转换公式,其中 v 为电压(单位 V),d 为浓度(单位 mg/m3)

v = 5.88∗d+0.6

转换为通过 v 计算 d,得到如下公式:

d = (v−0.6)∗0.17

4.3 工作原理

①置 ILED 引脚高电平,开启传感器内部红外二极管;

ILED 端输入脉冲波形要求

②等待 0.28ms(确保输出波形稳定),外部控制器对 AOUT 引脚电压采样,持续 0.04ms;

ILED 输入脉冲与 AOUT 的采样时序

③采样后置 ILED 引脚低电平,关闭红外二极管;

④依据电压与浓度的对应关系,计算当前空气中的灰尘浓度

ILED引脚驱动MOS管通断

五、常见问题解答

Q1: 传感器读数总是为0或负值怎么办?

A:这通常是由于电压转换公式不正确或ADC配置问题导致的:

检查ADC参考电压设置、验证NO_DUST_VOLTAGE值是否适合您的传感器、确保时序控制精确,特别是280us延时

Q2: 数据波动很大如何优化?

A:可以采取以下措施:

增加软件滤波算法(如移动平均)、确保传感器通气孔不被遮挡

Q3: 如何校准传感器?

A:校准步骤:

在清洁空气中运行传感器,记录基准电压、根据实际值调整NO_DUST_VOLTAGE参数

项目资源

GP2Y1014AU数据手册:GP2Y1010AU0F datasheet

相关推荐
深盾科技3 小时前
Windows 驱动开发基础
windows·驱动开发·stm32
10001hours3 小时前
(基于江协科技)51单片机入门:1.LED
科技·嵌入式硬件·51单片机
qxqxa4 小时前
SPI主控的CS引发的读不到设备寄存器
单片机·嵌入式硬件
平凡灵感码头5 小时前
STM32 程序内存分布详解
stm32·单片机·嵌入式硬件
QUST-Learn3D5 小时前
C++单头文件实现windows进程间通信(基于命名管道)
c++·windows·单片机
btzhy6 小时前
STM32单片机:基本定时器应用:精确定时(STM32L4xx)
stm32·单片机·嵌入式硬件·基本定时器应用:精确定时
云山工作室12 小时前
基于单片机智能水产养殖系统设计(论文+源码)
单片机·嵌入式硬件·毕业设计·毕设
xiaomin201714 小时前
【STM32 HAL库】高级定时器TIM8输出PWM
stm32·单片机·嵌入式硬件
沐欣工作室_lvyiyi15 小时前
基于单片机的小型农业气象监测系统(论文+源码)
单片机·嵌入式硬件·物联网·毕业设计·气象监测