当前位置: 首页 > news >正文

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

✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

目录

一、硬件系统设计

1.1 硬件清单

1.2 接线方案

1.3 具体接线图

1.4 连接实物图

二、代码架构讲解

2.1 数据采集算法

2.2 数据可视化算法

2.3 循环队列结构

2.4 数据访问模式

2.5 完整代码

三、项目结果演示

3.1 操作流程

3.2 视频演示

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

4.1 光学检测原理

4.2 输出特性曲线

4.3 工作原理

五、常见问题解答

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

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

Q3: 如何校准传感器?


1)项目概述

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

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

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

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

一、硬件系统设计

1.1 硬件清单

组件规格数量
主控板STM32F407VET61
粉尘传感器GP2Y1014AU1
TFT显示屏ST7789 240x2401
电阻150Ω1
电容220uF1

1.2 接线方案

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

STM32F407VET6引脚连接组件引脚功能
5VGP2Y1014AU VCC电源正极
GNDGP2Y1014AU GND电源地
A0GP2Y1014AU AOUT模拟输出
3GP2Y1014AU ILEDLED驱动
53TFT_CS片选
7TFT_DC数据/命令
6TFT_RST复位
3.3VTFT VCC电源
GNDTFT GND

1.3 具体接线图

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

1.4 连接实物图

二、代码架构讲解

2.1 数据采集算法

void loop(void) {/* === 阶段1: 传感器驱动时序控制 === */digitalWrite(iled, HIGH);     // 开启红外LEDdelayMicroseconds(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 数据可视化算法

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 循环队列结构

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) {// 数组初始化为0for(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 数据访问模式

// 获取历史数据的时间序列
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 完整代码

#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   6Adafruit_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 closedSerial.begin(9600);       //send and receive at 9600 baudSerial.println("Dust Monitor System Starting...");// Initialize displaytft.init(240, 240);tft.setRotation(1);tft.fillScreen(ST77XX_BLACK);// 显示启动界面showStartupScreen();startTime = millis();// 绘制静态界面drawStaticUI();Serial.println("System initialized");
}void drawStaticUI() {// Clear screentft.fillScreen(ST77XX_BLACK);// Draw title bartft.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 bartft.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 valueuint16_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 valuetft.setTextColor(ST77XX_WHITE);tft.setTextSize(2);tft.setCursor(40, 150);tft.print("Voltage: ");tft.print(voltage, 1);tft.print(" mV");// Display quality level with backgroundint 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 frameworktft.fillRect(0, 30, 240, 180, ST77XX_BLACK);// Graph title with time infotft.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 labelstft.drawRect(30, 70, 190, 110, ST77XX_GRAY);// Y-axis scale and labelsfor(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 labelstft.setCursor(30, 185);tft.print("-4m");tft.setCursor(100, 185);tft.print("-2m");tft.setCursor(170, 185);tft.print("Now");// 绘制阈值线drawThresholdLines();// Draw curvedrawGraphLine();
}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;} elsedensity = 0;// Add to historical data with timestamphistory.push(density, millis());// Check if screen needs to switchif(millis() - lastScreenSwitch > screenSwitchInterval) {currentScreen = (currentScreen + 1) % 2;lastScreenSwitch = millis();// Update bottom status bartft.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 screenif(currentScreen == 0) {drawDataScreen(density, voltage);} else {drawGraphScreen();}} else {// Partial refresh current screenif(currentScreen == 0) {drawDataScreen(density, voltage);} else {// Graph screen only updates graph parttft.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

http://www.dtcms.com/a/422967.html

相关文章:

  • 网站建设怎么创业网站正建设中
  • 网站建站哪个好宁波建设局网站首页
  • Day31_【 NLP _1.文本预处理 _(3)文本数据分析】
  • 金融/财务图表的强大可视化引擎——Highcharts Stock
  • 如何将照片从Mac传输到安卓设备
  • 第四部分:VTK常用类详解(第112章 vtkGlyph2D 2D符号化类)
  • 如何将三星手机的照片传输到Mac——6种可行的方法
  • 《系统与软件工程功能规模测量IFPUG方法》(GB/T42449-2023)标准解读
  • ChatExcel将发布数据分析Mini AI 工作站
  • 通过AWS IAM Policy Simulator进行权限验证和模拟测试
  • AWS Glue ETL 自动化数据清洗:从概念到企业级实战
  • 北京网站优化方法烟台网站网站建设
  • RabbitMQ 和 Kafka 对比
  • 大模型之扩散模型的学习一
  • 做企业网站 空间怎么买简述商务网站建设步骤
  • 做火锅加盟哪个网站好主营商城网站建设
  • 网站维护怎么做wordpress主题无法预览
  • [吾爱大神原创] wx小程序自动解包工具界面版1.0.0
  • Datagrip连接Oracle23的一些异常记录
  • springboot+vue心理健康服务小程序(源码+文档+调试+基础修改+答疑)
  • flink api-datastream api-source算子
  • 基于数据挖掘的在线游戏行为分析预测系统
  • 无极领域付费网站做外贸要访问国外的网站怎么办
  • 本地项目上传到Git仓库
  • 首批CCF教学案例大赛资源上线:涵盖控制仿真、算法与机器人等9大方向
  • Java外功精要(2)——Spring IoCDI
  • Git简单理解
  • 机器人的“神经网络”:以太网技术如何重塑机器人内部通信?【技术类】
  • k8s-pod的资源限制
  • 【附源码】基于Vue的网上约课系统的设计与实现