基于STM32的环境质量监测课程设计详解

  • 课程设计要求
  • STM32为主控平台:本系统以STM32微控制器为核心,能够实时检测环境温度、湿度和PM2.5粉尘浓度。

    定时与人工设置显示功能:系统支持定时自动显示监测数据,并且允许用户通过人工设置显示内容。

    超标报警功能:当监测的环境指标超出预设的安全阈值时,系统能够自动触发报警机制。整个系统分为三大部分:检测、显示和报警。检测部分采用SHT20温湿度传感器实时监测环境的温度和湿度,同时使用PM2.5传感器检测空气中的粉尘颗粒浓度。显示部分则通过STM32将传感器采集到的数据进行处理后,通过LCD1602液晶屏显示当前的温度、湿度和PM2.5浓度。此外,系统还通过按键实现人机交互,用户可设置PM2.5浓度的上限,超过该上限时,单片机会控制三极管驱动蜂鸣器发出报警音。

    数据存储功能:系统能够记录并存储一定时间范围内的监测数据,便于后续查阅与分析。

  • 系统架构
  • 2.1硬件系统组成架构

    本设计使用单片机作为控制核心,采用单个温湿度传感器(DHT11)对温度和湿度进行监测,GP2Y10传感器则负责检测空气中的PM2.5浓度,以液晶显示屏(OLED)显示检测温度,当PM2.5浓度超过设定阈值时,系统会通过蜂鸣器发出声音报警,系统总体控制框图如图2.1所示:

    该系统软件功能有环境温湿度获取,环境PM2.5浓度获取,环境质量(温湿度及PM2.5浓度)展示,PM2.5浓度报警。下面图2是软件系统框图:

  • 硬件系统设计
  • 3.1 STM32F103C8T6最小系统板

    STM32F103C8T6 最小系统板主要包括以下几个关键部分:

    微控制器  型号:STM32F103C8T6  

    电源供应 输入电压:3.3V  去耦电容:0.1μF 电容  10μF 电容

    复位电路 复位引脚:NRST 上拉电阻:10kΩ 复位按钮:连接到 NRST 引脚

    晶振电路 主晶振:8MHz 实时时钟晶振:32.768kHz

    调试接口 SWD接口:SWDIO引脚 SWCLK引脚 上拉电阻:每个10kΩ

    3.2 DHT11温湿度传感器

    DHT11 是一种常用的温湿度传感器,它集成了温度和湿度的测量功能,广泛应用于各种环境监测系统中。DHT11 的工作原理基于电阻式湿度传感原理和数字温度传感技术。以下是 DHT11 温湿度传感器的工作原理:

    1.湿度测量原理(电阻式)

    DHT11 内部的湿度传感器部分采用的是电阻式测量原理。其核心是一个吸湿性的电阻材料(通常是聚合物),该材料的电阻随着空气中湿度的变化而变化:

    湿度增加时,吸湿材料会吸收水分,导致其电阻值减小。

    湿度降低时,吸湿材料中的水分减少,电阻值增大。

    DHT11 内置一个传感器电路,实时监测电阻值的变化,并通过内部的模数转换(ADC)将电阻值转化为数字信号。这个数字信号被用来表示环境湿度的值,并传输给主控单元(如 STM32 等微控制器)。

    2.温度测量原理

    DHT11 的温度测量是通过内置的数字温度传感器来实现的,通常这种传感器是基于热敏电阻(NTC 或 PTC)原理。NTC热敏电阻的电阻值随温度变化而变化,在 DHT11 中,传感器通过内部的处理电路将温度的变化转换为对应的数字信号。

    温度升高时,NTC 电阻的电阻值减小。

    温度降低时,NTC 电阻的电阻值增大。

    DHT11 将温度变化转化为数字信号,经过处理后,输出对应的温度值。

    3.3 GP2Y10烟雾传感器

    GP2Y10烟雾传感器的工作原理:

    GP2Y10是一款光学传感器,使用了红外LED和光电二极管来探测烟雾颗粒。其基本原理可以分为以下几个步骤:

    红外光源(LED)发射:GP2Y10 内部有一个红外LED,它会发射红外光。这种光在传感器内部被照射到空气中。

    光散射:当空气中有颗粒物(如烟雾、灰尘)存在时,这些颗粒物会散射红外光。散射的光的强度与颗粒物的浓度成正比。

    烟雾中的微小颗粒物比大气中的其他颗粒物更加有效地散射红外光,因此当空气中烟雾浓度较高时,散射光会增强。

    光电二极管接收散射光:在传感器内部,配有一个 光电二极管,用于接收被颗粒物散射回来的红外光。光电二极管将接收到的光转换为电信号。

    电信号处理:传感器的内部电路会处理光电二极管产生的电信号,输出一个与空气中颗粒物浓度成正比的电压信号。这个信号可以被外部微控制器(如 STM32)读取,进而根据电压值推测出空气中的烟雾浓度。

    输出信号:GP2Y10 提供的是一个模拟信号,通常是一个与烟雾浓度相关的 模拟电压输出。该信号可以用来判断烟雾的浓度,或者进一步进行报警、显示等操作。

    3.4 OLED显示屏

    OLED(有机发光二极管)显示屏通过电流激励有机材料自发光来显示图像。其基本结构包括基板、透明阳极、发光层、阴极等。电流通过阴极注入电子,阳极注入空穴,电子和空穴在发光层中结合形成激子,并释放能量以光的形式发射出来。不同颜色的发光材料(红、绿、蓝)生成不同波长的光,结合成全色显示。由于每个像素都是自发光的,OLED 显示屏具有高对比度和宽视角,能实现真正的黑色和鲜艳的色彩。相比LCD,OLED 不需要背光源,薄且轻,功耗低。主要应用于智能手机、电视等领域,但也存在蓝光寿命较短和屏幕烧录问题。

    3.5有源蜂鸣器

    有源蜂鸣器是一种能够自发产生声音的电子元件,通常由压电陶瓷、振荡电路和电源组成。其工作原理基于压电效应,即材料在电场作用下会发生形变,从而产生机械振动并发出声音。具体来说,当电压施加到蜂鸣器的压电陶瓷元件时,压电材料会根据电场的变化发生形变。这种形变产生的振动会使蜂鸣器的外壳(通常是金属材料)产生振动,进而发出声音。蜂鸣器内部通常有一个内置的振荡电路,这个电路在接收到电源后,会自动产生一定频率的信号,控制蜂鸣器的振动频率,产生一定的音调。与无源蜂鸣器不同,有源蜂鸣器不需要外部的信号驱动。它自带振荡电路,连接电源后即可产生声音。常见的工作频率为2kHz至4kHz,适用于各种警报、提示音等场合。当电源电压施加到蜂鸣器时,它会产生一个稳定的声音信号,直到电压去除。有源蜂鸣器的优点是易于使用,无需额外的驱动电路,适合简单的报警系统。它的缺点是音调通常不如无源蜂鸣器可调,且声音较为单一。

  • 软件系统设计
  • 4.1主函数:

    这段代码主要是实现了一个基于STM32的环境监测系统,集成了PM2.5传感器(GP2Y10)、温湿度传感器(DHT11)、OLED显示屏和蜂鸣器功能。代码的主要功能包括实时显示环境数据、调节PM2.5警告值、触发警报并通过OLED显示屏展示信息。以下是详细功能分解:

    4.1.1. 初始化操作:

    `OLED_Init()`:初始化OLED显示屏,用于显示信息。

    `EXIT_Init()`:初始化外部中断,设置按键(PB5和PB6)用于调节警告值。

    `Adc_Init()`:初始化ADC模块(虽然在代码中没有进一步使用,为了读取模拟传感器数据)。

    `Beep_Init()`:初始化蜂鸣器,连接到GPIOA的Pin4,用于报警。

    4.1.2 OLED显示:

    在OLED屏幕上显示环境数据,如PM2.5浓度、温度和湿度以及当前的警告阈值。用于在特定位置显示字符串,用于显示数字。

    4.1.3警告阈值的调整:

    初始警告阈值`warn_num`为10。

    通过按键PB5(增加警告阈值)和PB6(减少警告阈值)来调节`warn_num`值。按下PB5时,`warn_num`增加5,按下PB6时,`warn_num`减少5,且在0到1000之间限制。`Warning_Num()`会在OLED上实时显示当前的警告阈值。

    4.1.4PM2.5监测和报警:

    `Show_PM()`通过`GetGP2Y()`获取PM2.5传感器的值,并在OLED上显示。如果PM2.5值超过设定的`warn_num`阈值,蜂鸣器会触发报警(通过控制GPIOA的Pin4状态,蜂鸣器发出声音)。

    4.1.5温湿度显示:

    `Show_T_And_Wet()`通过`DHT11_REC_Data()`获取温湿度数据,并在OLED屏幕上显示。温度和湿度数据从`rec_data`数组中提取,并以特定格式显示在屏幕上。

    4.1.6外部中断处理:

    `EXIT_Init()`配置了外部中断,当按下PB5或PB6时会触发相应的中断,调整警告阈值。`EXTI9_5_IRQHandler()`是外部中断服务程序,用于处理PB5和PB6的按键输入。按键按下时,检查引脚电平,增加或减少`warn_num`值,并更新OLED显示。

    工作流程:

    系统启动时,初始化OLED显示、蜂鸣器、传感器等硬件,并在OLED上显示"PM2.5"、"T"(温度)、"Wet"(湿度)等信息。

    `Show_PM()`函数不断读取PM2.5值,并判断是否超过警告阈值,若超过,则触发蜂鸣器报警。

    `Show_T_And_Wet()`定时获取温湿度数据并显示在OLED上。

    通过按下PB5(增加)和PB6(减少)来调整`warn_num`值,系统会实时更新警告阈值。每次按键事件会触发中断,调整`warn_num`的值,并通过OLED更新显示。

    这段代码的核心功能是读取环境传感器数据(PM2.5、温湿度),并在OLED屏幕上显示。通过按钮输入,可以调节PM2.5报警阈值,并在值超过设定阈值时触发蜂鸣器报警。系统通过外部中断响应按键输入,实现用户交互。

    4.2OLED模块:

    这段代码是用于控制基于 I2C 协议的 OLED 显示屏,通过 STM32 微控制器进行通信。代码主要功能包括初始化 OLED 显示屏、发送命令、显示字符、数字等内容。具体来说,这段代码的主要功能可以分为以下几个部分:

    4.2.1 引脚配置与初始化:

    OLED_W_SCL(x)和OLED_W_SDA(x)宏定义用于设置 OLED 显示屏的 I2C 时钟 (SCL) 和数据 (SDA) 引脚。这些引脚是通过 STM32 的 GPIOB 端口控制的。

    `OLED_I2C_Init()` 函数初始化 GPIOB 的 8 号和 9 号引脚为开漏输出模式,并使能时钟。

    4.2.2 I2C 通信相关函数:

    OLED_I2C_Start()和OLED_I2C_Stop(): 用于控制 I2C 总线的启动和停止。I2C 的启动信号是通过拉低数据线和时钟线产生的,停止信号则是将数据线拉高。OLED_I2C_SendByte(uint8_t Byte): 用于通过 I2C 发送一个字节的数据。该函数逐位发送数据,每发送一个 bit 会产生一个时钟信号。

    4.2.3 OLED 命令与数据发送:

    OLED_WriteCommand(uint8_t Command): 发送 OLED 显示屏命令。OLED 显示屏通常在接收到命令后执行初始化或其他设置操作。OLED_WriteData(uint8_t Data): 发送显示数据。数据通常是用于控制显示内容,例如显示字符或图形。

    4.2.4 OLED 显示相关函数:

    OLED_SetCursor(uint8_t Y, uint8_t X): 设置 OLED 显示屏的光标位置。OLED 屏幕通常有多个行列,通过设置光标来指定要显示内容的位置。

    OLED_Clear(): 清空 OLED 显示屏,即将所有显示内容设置为 0(黑色显示)。

    OLED_ShowChar(uint8_t Line, uint8_t Column, char Char): 显示一个字符。字符通过调用 `OLED_F8x16` 字体数组来显示,其中该数组包含了每个字符的字形数据。

    OLED_ShowString(uint8_t Line, uint8_t Column, char *String): 显示一个字符串,通过调用 `OLED_ShowChar` 来依次显示每个字符。

    4.2.5 显示数字与进制转换相关函数:

    OLED_Pow(uint32_t X, uint32_t Y): 计算 X 的 Y 次方,供后续的数字显示使用。

    OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length): 显示一个正数(十进制),根据提供的长度显示该数字。

    OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length): 显示一个带符号的数字(十进制)。

    OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length): 显示一个十六进制数字。

    OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length): 显示一个二进制数字。

    4.2.6 OLED 初始化:

    OLED_Init(): 初始化 OLED 显示屏,进行一系列配置操作,设置显示模式、对比度、时钟频率等。它通过一系列的命令来配置显示屏的硬件设置,如复用率、偏移、显示方向、对比度等。

    在初始化过程中,首先关闭显示,然后设置显示参数,最后开启显示并清屏。

    这段代码实现了与 OLED 显示屏进行通信并控制显示内容的功能,包含了:初始化显示屏的硬件设置;支持字符、字符串、数字、进制转换等显示功;通过 I2C 协议发送命令和数据到 OLED 显示屏,控制屏幕的显示内容。

    4.3 DHT11 温湿度传感器:

    这段代码实现了与 DHT11 温湿度传感器的通信,目的是通过 STM32 微控制器获取环境的温度和湿度数据。代码分为几个主要部分,下面是每个部分的功能概括:

    4.3.1 GPIO 初始化:

    DH11_GPIO_Init_OUT()**: 初始化 STM32 的 GPIO 引脚为推挽输出模式,设置 DHT11 数据引脚为输出,用于发送信号到 DHT11。

    DH11_GPIO_Init_IN()**: 初始化 STM32 的 GPIO 引脚为浮空输入模式,设置 DHT11 数据引脚为输入,用于接收 DHT11 返回的数据。

    4.3.2主机发送开始信号:

    DHT11_Start(); 通过控制 GPIO 引脚,向 DHT11 发送启动信号。DHT11 在接收到启动信号后会开始发送温湿度数据。信号的发送过程包括:拉高数据线,并保持一定时间。拉低数据线至少 18 毫秒,准备开始通信。拉高数据线,并等待 DHT11 设备响应。

    4.3.3接收 DHT11 数据:

    DHT11_Rec_Byte(): 该函数用于接收 DHT11 传输的一个字节数据。数据是按位传输的,函数通过检测数据线的电平变化来解析每一位。

    DHT11 通过低电平开始信号,接着将每一位的数据按时序传输,每一位数据的时长区分为 0 或 1。

    该函数使用一个循环接收 8 位数据,拼接成一个字节并返回。

    DHT11_REC_Data(): 获取 DHT11 温湿度数据的主函数。该函数会按照 DHT11 的协议发送启动信号,并接收返回的数据。

    启动信号发送后,DHT11 会返回 5 个字节的数据:湿度高字节(R_H)、湿度低字节(R_L)、温度高字节(T_H)、温度低字节(T_L)和校验字节(CHECK)。

    如果接收到的数据的和与校验字节相符,则认为数据接收成功,存储湿度和温度数据。

    4.3.4数据校验:

    在接收到湿度和温度数据后,通过校验和验证接收到的数据是否正确。校验方式是:湿度高字节 + 湿度低字节 + 温度高字节 + 温度低字节 应该等于校验字节(CHECK)。

    如果校验成功,则将温湿度数据存储到 `rec_data[]` 数组中,其中:

    `rec_data[0]` 存储湿度高字节(RH)。

    `rec_data[1]` 存储湿度低字节(RL)。

    `rec_data[2]` 存储温度高字节(TH)。

    `rec_data[3]` 存储温度低字节(TL)。

    这段代码的主要功能是通过 STM32 与 DHT11 温湿度传感器进行通信,并获取温湿度数据。具体流程包括:初始化 GPIO 引脚为输出(发送信号)和输入(接收数据)。向 DHT11 发送启动信号,等待其响应并返回数据。解析 DHT11 返回的温湿度数据,并进行校验。如果数据校验通过,将湿度和温度的数据存储到 `rec_data` 数组中供后续使用。

    4.4GP2Y10模块:

    这段代码主要实现了通过 STM32 获取 GP2Y10 PM2.5 传感器的输出数据,并进行处理。它包括 ADC 初始化、读取传感器数据以及计算平均值等功能。具体来说,代码的功能可概括如下:

    4.4.1 ADC 初始化 (`Adc_Init`):

    配置 STM32 的 ADC 模块,使其能够从模拟输入通道读取数据。特别地:启用了 `ADC1` 和 `GPIOA`(PA0)及 `GPIOB`(PB1)的时钟。设置了 PA0 为模拟输入,用于连接 GP2Y10 传感器的模拟输出信号。配置了 ADC 的工作模式(独立模式、单次转换模式、右对齐数据等)和采样时间(239.5 个周期)。对 ADC 进行了复位和校准,确保准确的模拟转换。GP2Y_High` 和 `GP2Y_Low` 控制信号引脚,用来启动和停止 GP2Y10 传感器的工作。

    4.4.2获取 ADC 转换值 (`Get_Adc`):

    该函数通过指定 ADC 通道(0~3)来获取对应通道的 ADC 值。配置 ADC 的通道和采样时间,启动 ADC 转换并等待转换结束。最后返回 ADC 转换结果,即采样到的模拟值。

    4.4.3获取 GP2Y10 数据 (`GetGP2Y`):

    控制 GP2Y10 的工作状态:通过设置 `GP2Y_Low` 使传感器工作,并延时 280 微秒采样。通过 `Get_Adc(ADC_Channel_0)` 获取来自 GP2Y10 模拟输出的 ADC 值。设置 `GP2Y_High` 停止传感器输出数据。最后通过一个转换公式 `pm = 0.17*AD_PM – 0.1` 将 ADC 值转换为 PM2.5 浓度值,并返回该值。

    4.4.4获取 GP2Y10 数据的平均值 (`Get_GP2Y_Average`):

    该函数重复多次获取 GP2Y10 数据,并计算其平均值,以减小波动或噪声。`times` 参数表示测量次数,通过多次采样后,返回传感器输出值的平均值。这段代码的核心功能是通过 STM32 控制 ADC 采集 GP2Y10 PM2.5 传感器的模拟输出信号,并将其转换为 PM2.5 浓度值。通过控制 GPIO 引脚启动/停止传感器工作,并使用 ADC 获取模拟数据后,利用公式转换为实际的 PM2.5 浓度值。同时,该代码支持多次采样并返回平均值,从而提高数据的准确性和稳定性。

    五、课程设计总结

    5.1系统完成情况及功能说明

    5.1.1 温湿度监测 (SHT20 传感器)

    实现情况:已成功接入 SHT20 温湿度传感器,利用 I2C 总线与 STM32 进行数据通信。系统可以实时读取温湿度数据并显示在 OLED 屏幕上。

    功能:实时获取环境的温度和湿度,并在 OLED 屏幕上显示。

    5.1.2 PM2.5 浓度监测 (GP2Y10 传感器)

    实现情况:已成功通过 ADC 通道获取 GP2Y10 传感器的模拟输出数据,并将其转换为 PM2.5 浓度值。

    功能:通过控制 GP2Y10 的工作状态,实时监测空气中的 PM2.5 浓度。

    数据处理:采用公式将 ADC 数据转换为 PM2.5 浓度,且支持多次采样取平均值来提高数据的稳定性。

    5.1.3 OLED 显示功能

    实现情况:已通过 I2C 接口连接 SSD1306 OLED 显示屏,实时显示温度、湿度、PM2.5 浓度等数据。

    功能:显示当前环境质量信息,周期性刷新以确保数据实时更新。

    5.1.4报警功能(蜂鸣器)

    实现情况:已实现温湿度和 PM2.5 超过设定阈值时,触发蜂鸣器报警功能。

    功能:当监测数据超出设定阈值时,系统会通过蜂鸣器发出声音提示,帮助用户注意空气质量变化。

    用户设置:允许用户设置温湿度和 PM2.5 的报警阈值。

    5.1.5数据采集与显示

    实现情况:通过周期性采集传感器数据,并显示在 OLED 屏幕上。数据更新频率和刷新机制已经调整为合理的工作状态,以保证显示内容及时准确。

    功能:温湿度、PM2.5 浓度实时显示,确保用户能够即时了解环境质量。

    5.1.6按键设置功*

    实现情况:用户可以通过按键调整报警阈值,设置温度、湿度和 PM2.5 的报警上限和下限值。

    功能:通过外部按键实现对系统的手动配置,便于用户根据实际需求调整警报条件。

    5.2仪器缺陷

    数据存储:目前系统尚未实现数据存储功能。可以通过增加外部存储(如 SD 卡)来存储监测数据,便于后续查看历史数据和趋势分析。

    联网功能:当前系统没有联网功能,未来可以通过 Wi-Fi 或蓝牙模块实现数据上传,进行远程监控和报警。可将数据上传至云平台或手机应用,实现远程查看和报警通知。

    功耗优化:系统当前功耗适合实验室使用,但若部署于实际环境中(例如野外或无电源的地方),需要进一步优化功耗,增加电池管理功能。可能需要进入低功耗模式,延长电池寿命。

    支持更多传感器:目前只支持温湿度和 PM2.5 监测,未来可以扩展更多传感器,如 CO2、气压、空气质量传感器等,进一步提升系统的功能和环境监测能力。

    用户界面改:OLED 显示屏的界面可以进一步美化,显示更多的环境质量信息,或通过按键实现多页面显示,便于用户查看不同类型的数据。

    参考文献

    1. 吴建平,传感器原理及应用[M],北京: 机械工业出版社, 2021.
    2. 董锟、何娜,基于STM32的多参数环境检测仪的设计与实现,电脑知识与技术,2024.06.05.

    [3] 王珅,基于GP2Y1010AU0F传感器的PM2.5检测仪设计,工业加热,2020.02.28..

    附录

    附录一:硬件系统原理图

    附录二:软件程序清单

    Main.c:

    #include "Config.h"

    int warn_num = 10;

    int main(void)

    {

     OLED_Init();

     EXIT_Init();

     OLED_ShowString(1, 1, "PM2.5:");

     OLED_ShowString(2, 1, "T:");

     OLED_ShowString(3, 1, "Wet:");

     Adc_Init();

     Beep_Init();

     //GP2Yinit();

     while(1)

     {

      Warning_Num();

      Show_PM();

      Show_T_And_Wet();

     }

    }

    void Show_PM(void)

    {

     float num = GetGP2Y();

     OLED_ShowNum(1, 7, (int)num, 3);

     if(num > warn_num)

      GPIO_ResetBits(GPIOA, GPIO_Pin_4);

     else

      GPIO_SetBits(GPIOA, GPIO_Pin_4);

    }

    void Show_T_And_Wet()

    {

     Delay_s(1);

     DHT11_REC_Data();

     OLED_ShowNum(2, 3, rec_data[2], 3);

     OLED_ShowString(2, 6, ".");

     OLED_ShowNum(2, 7, rec_data[3], 2);

     OLED_ShowNum(3, 5, rec_data[0], 3); 

    }

    void Warning_Num(void)

    {

     OLED_ShowString(4, 1, "Warn:");

     OLED_ShowNum(4, 6, warn_num, 3);

    }

    void Beep_Init(void)

    {

     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

     GPIO_InitTypeDef GPIO_InitStructer;

     GPIO_InitStructer.GPIO_Mode=GPIO_Mode_Out_PP;

     GPIO_InitStructer.GPIO_Pin=GPIO_Pin_4;

     GPIO_InitStructer.GPIO_Speed=GPIO_Speed_50MHz;

     GPIO_Init(GPIOA,&GPIO_InitStructer);

     GPIO_SetBits(GPIOA, GPIO_Pin_4);

    }

    void EXIT_Init(void)//PB5和6为key设置,中断进行设置

    {//5+   6-

     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

     RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

     GPIO_InitTypeDef GPIO_InitStructure;

     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6;

     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

     GPIO_Init(GPIOB, &GPIO_InitStructure);

     GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource5);

     GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource6);

     EXTI_InitTypeDef EXTI_InitStructure;

     EXTI_InitStructure.EXTI_Line = EXTI_Line5 | EXTI_Line6;

     EXTI_InitStructure.EXTI_LineCmd = ENABLE;

     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;

     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿产生中断

     EXTI_Init(&EXTI_InitStructure);

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     NVIC_InitTypeDef NVIC_InitStructure;

     NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;

     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;

     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

     NVIC_Init(&NVIC_InitStructure);

    }

    void EXTI9_5_IRQHandler(void)

    {

     /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/

     if (EXTI_GetITStatus(EXTI_Line5) == SET)

     {

      /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/

      if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0)

      {

       if(warn_num > 1000)

       {

        warn_num = 1000;

       }

       else

       {

        warn_num += 5;

       }

      }

      EXTI_ClearITPendingBit(EXTI_Line5);

     }

     if (EXTI_GetITStatus(EXTI_Line6) == SET)

     {

      /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/

      if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 0)

      {

       if(warn_num < 0)

       {

        warn_num = 0;

       }

       else

       {

        warn_num -= 5;

       }

      }

      EXTI_ClearITPendingBit(EXTI_Line6);

     }

    }

    Delay.c:

    #include "stm32f10x.h"

    /**

      * @brief  微秒级延时

      * @param  xus 延时时长,范围:0~233015

      * @retval 无

      */

    void Delay_us(uint32_t xus)

    {

     SysTick->LOAD = 72 * xus;    //设置定时器重装值

     SysTick->VAL = 0x00;     //清空当前计数值

     SysTick->CTRL = 0x00000005;    //设置时钟源为HCLK,启动定时器

     while(!(SysTick->CTRL & 0x00010000)); //等待计数到0

     SysTick->CTRL = 0x00000004;    //关闭定时器

    }

    /**

      * @brief  毫秒级延时

      * @param  xms 延时时长,范围:0~4294967295

      * @retval 无

      */

    void Delay_ms(uint32_t xms)

    {

     while(xms–)

     {

      Delay_us(1000);

     }

    }

    /**

      * @brief  秒级延时

      * @param  xs 延时时长,范围:0~4294967295

      * @retval 无

      */

    void Delay_s(uint32_t xs)

    {

     while(xs–)

     {

      Delay_ms(1000);

     }

    }

    OLED.c:

    #include "stm32f10x.h"

    #include "OLED_Font.h"

    /*引脚配置*/

    #define OLED_W_SCL(x)  GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))

    #define OLED_W_SDA(x)  GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

    /*引脚初始化*/

    void OLED_I2C_Init(void)

    {

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

     GPIO_InitTypeDef GPIO_InitStructure;

      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;

     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

      GPIO_Init(GPIOB, &GPIO_InitStructure);

     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

      GPIO_Init(GPIOB, &GPIO_InitStructure);

     OLED_W_SCL(1);

     OLED_W_SDA(1);

    }

    /**

      * @brief  I2C开始

      * @param  无

      * @retval 无

      */

    void OLED_I2C_Start(void)

    {

     OLED_W_SDA(1);

     OLED_W_SCL(1);

     OLED_W_SDA(0);

     OLED_W_SCL(0);

    }

    /**

      * @brief  I2C停止

      * @param  无

      * @retval 无

      */

    void OLED_I2C_Stop(void)

    {

     OLED_W_SDA(0);

     OLED_W_SCL(1);

     OLED_W_SDA(1);

    }

    /**

      * @brief  I2C发送一个字节

      * @param  Byte 要发送的一个字节

      * @retval 无

      */

    void OLED_I2C_SendByte(uint8_t Byte)

    {

     uint8_t i;

     for (i = 0; i < 8; i++)

     {

      OLED_W_SDA(Byte & (0x80 >> i));

      OLED_W_SCL(1);

      OLED_W_SCL(0);

     }

     OLED_W_SCL(1); //额外的一个时钟,不处理应答信号

     OLED_W_SCL(0);

    }

    /**

      * @brief  OLED写命令

      * @param  Command 要写入的命令

      * @retval 无

      */

    void OLED_WriteCommand(uint8_t Command)

    {

     OLED_I2C_Start();

     OLED_I2C_SendByte(0x78);  //从机地址

     OLED_I2C_SendByte(0x00);  //写命令

     OLED_I2C_SendByte(Command);

     OLED_I2C_Stop();

    }

    /**

      * @brief  OLED写数据

      * @param  Data 要写入的数据

      * @retval 无

      */

    void OLED_WriteData(uint8_t Data)

    {

     OLED_I2C_Start();

     OLED_I2C_SendByte(0x78);  //从机地址

     OLED_I2C_SendByte(0x40);  //写数据

     OLED_I2C_SendByte(Data);

     OLED_I2C_Stop();

    }

    /**

      * @brief  OLED设置光标位置

      * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7

      * @param  X 以左上角为原点,向右方向的坐标,范围:0~127

      * @retval 无

      */

    void OLED_SetCursor(uint8_t Y, uint8_t X)

    {

     OLED_WriteCommand(0xB0 | Y);     //设置Y位置

     OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位

     OLED_WriteCommand(0x00 | (X & 0x0F));   //设置X位置低4位

    }

    /**

      * @brief  OLED清屏

      * @param  无

      * @retval 无

      */

    void OLED_Clear(void)

    {  

     uint8_t i, j;

     for (j = 0; j < 8; j++)

     {

      OLED_SetCursor(j, 0);

      for(i = 0; i < 128; i++)

      {

       OLED_WriteData(0x00);

      }

     }

    }

    /**

      * @brief  OLED显示一个字符

      * @param  Line 行位置,范围:1~4

      * @param  Column 列位置,范围:1~16

      * @param  Char 要显示的一个字符,范围:ASCII可见字符

      * @retval 无

      */

    void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)

    {       

     uint8_t i;

     OLED_SetCursor((Line – 1) * 2, (Column – 1) * 8);  //设置光标位置在上半部分

     for (i = 0; i < 8; i++)

     {

      OLED_WriteData(OLED_F8x16[Char – ' '][i]);   //显示上半部分内容

     }

     OLED_SetCursor((Line – 1) * 2 + 1, (Column – 1) * 8); //设置光标位置在下半部分

     for (i = 0; i < 8; i++)

     {

      OLED_WriteData(OLED_F8x16[Char – ' '][i + 8]);  //显示下半部分内容

     }

    }

    /**

      * @brief  OLED显示字符串

      * @param  Line 起始行位置,范围:1~4

      * @param  Column 起始列位置,范围:1~16

      * @param  String 要显示的字符串,范围:ASCII可见字符

      * @retval 无

      */

    void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)

    {

     uint8_t i;

     for (i = 0; String[i] != '\0'; i++)

     {

      OLED_ShowChar(Line, Column + i, String[i]);

     }

    }

    /**

      * @brief  OLED次方函数

      * @retval 返回值等于X的Y次方

      */

    uint32_t OLED_Pow(uint32_t X, uint32_t Y)

    {

     uint32_t Result = 1;

     while (Y–)

     {

      Result *= X;

     }

     return Result;

    }

    /**

      * @brief  OLED显示数字(十进制,正数)

      * @param  Line 起始行位置,范围:1~4

      * @param  Column 起始列位置,范围:1~16

      * @param  Number 要显示的数字,范围:0~4294967295

      * @param  Length 要显示数字的长度,范围:1~10

      * @retval 无

      */

    void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)

    {

     uint8_t i;

     for (i = 0; i < Length; i++)       

     {

      OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length – i – 1) % 10 + '0');

     }

    }

    /**

      * @brief  OLED显示数字(十进制,带符号数)

      * @param  Line 起始行位置,范围:1~4

      * @param  Column 起始列位置,范围:1~16

      * @param  Number 要显示的数字,范围:-2147483648~2147483647

      * @param  Length 要显示数字的长度,范围:1~10

      * @retval 无

      */

    void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)

    {

     uint8_t i;

     uint32_t Number1;

     if (Number >= 0)

     {

      OLED_ShowChar(Line, Column, '+');

      Number1 = Number;

     }

     else

     {

      OLED_ShowChar(Line, Column, '-');

      Number1 = -Number;

     }

     for (i = 0; i < Length; i++)       

     {

      OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length – i – 1) % 10 + '0');

     }

    }

    /**

      * @brief  OLED显示数字(十六进制,正数)

      * @param  Line 起始行位置,范围:1~4

      * @param  Column 起始列位置,范围:1~16

      * @param  Number 要显示的数字,范围:0~0xFFFFFFFF

      * @param  Length 要显示数字的长度,范围:1~8

      * @retval 无

      */

    void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)

    {

     uint8_t i, SingleNumber;

     for (i = 0; i < Length; i++)       

     {

      SingleNumber = Number / OLED_Pow(16, Length – i – 1) % 16;

      if (SingleNumber < 10)

      {

       OLED_ShowChar(Line, Column + i, SingleNumber + '0');

      }

      else

      {

       OLED_ShowChar(Line, Column + i, SingleNumber – 10 + 'A');

      }

     }

    }

    /**

      * @brief  OLED显示数字(二进制,正数)

      * @param  Line 起始行位置,范围:1~4

      * @param  Column 起始列位置,范围:1~16

      * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111

      * @param  Length 要显示数字的长度,范围:1~16

      * @retval 无

      */

    void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)

    {

     uint8_t i;

     for (i = 0; i < Length; i++)       

     {

      OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length – i – 1) % 2 + '0');

     }

    }

    /**

      * @brief  OLED初始化

      * @param  无

      * @retval 无

      */

    void OLED_Init(void)

    {

     uint32_t i, j;

     for (i = 0; i < 1000; i++)   //上电延时

     {

      for (j = 0; j < 1000; j++);

     }

     OLED_I2C_Init();   //端口初始化

     OLED_WriteCommand(0xAE); //关闭显示

     OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率

     OLED_WriteCommand(0x80);

     OLED_WriteCommand(0xA8); //设置多路复用率

     OLED_WriteCommand(0x3F);

     OLED_WriteCommand(0xD3); //设置显示偏移

     OLED_WriteCommand(0x00);

     OLED_WriteCommand(0x40); //设置显示开始行

     OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置

     OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置

     OLED_WriteCommand(0xDA); //设置COM引脚硬件配置

     OLED_WriteCommand(0x12);

     OLED_WriteCommand(0x81); //设置对比度控制

     OLED_WriteCommand(0xCF);

     OLED_WriteCommand(0xD9); //设置预充电周期

     OLED_WriteCommand(0xF1);

     OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别

     OLED_WriteCommand(0x30);

     OLED_WriteCommand(0xA4); //设置整个显示打开/关

     OLED_WriteCommand(0xA6); //设置正常/倒转显示

     OLED_WriteCommand(0x8D); //设置充电泵

     OLED_WriteCommand(0x14);

     OLED_WriteCommand(0xAF); //开启显示

     OLED_Clear();    //OLED清屏

    DHT11.c:

    #include "Config.h"

    //数据

    unsigned int rec_data[4];

    //对于stm32来说,是输出

    void DH11_GPIO_Init_OUT(void)

    {

     GPIO_InitTypeDef GPIO_InitStructure;

     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP; //推挽输出

     GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;

     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

     GPIO_Init(GPIOB, &GPIO_InitStructure);

    }

    //对于stm32来说,是输入

    void DH11_GPIO_Init_IN(void)

    {

     GPIO_InitTypeDef GPIO_InitStructure;

     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING; //浮空输入

     GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;

     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

     GPIO_Init(GPIOB, &GPIO_InitStructure);

    }

    //主机发送开始信号

    void DHT11_Start(void)

    {

     DH11_GPIO_Init_OUT(); //输出模式

     dht11_high; //先拉高

     Delay_us(30);

     dht11_low; //拉低电平至少18us

     Delay_ms(20);

     dht11_high; //拉高电平20~40us

     Delay_us(30);

     DH11_GPIO_Init_IN(); //输入模式

    }

    //获取一个字节

    char DHT11_Rec_Byte(void)

    {

     unsigned char i = 0;

     unsigned char data;

     for(i=0;i<8;i++) //1个数据就是1个字节byte,1个字节byte有8位bit

     {

      while( Read_Data == 0); //从1bit开始,低电平变高电平,等待低电平结束

      Delay_us(30); //延迟30us是为了区别数据0和数据1,0只有26~28us

      data <<= 1; //左移

      if( Read_Data == 1 ) //如果过了30us还是高电平的话就是数据1

      {

       data |= 1; //数据+1

      }

      while( Read_Data == 1 ); //高电平变低电平,等待高电平结束

     }

     return data;

    }

    //获取数据

    void DHT11_REC_Data(void)

    {

     unsigned int R_H,R_L,T_H,T_L;

     unsigned char RH,RL,TH,TL,CHECK;

     DHT11_Start(); //主机发送信号

     dht11_high; //拉高电平

     if( Read_Data == 0 ) //判断DHT11是否响应

     {

      while( Read_Data == 0); //低电平变高电平,等待低电平结束

      while( Read_Data == 1); //高电平变低电平,等待高电平结束

      R_H = DHT11_Rec_Byte();

      R_L = DHT11_Rec_Byte();

      T_H = DHT11_Rec_Byte();

      T_L = DHT11_Rec_Byte();

      CHECK = DHT11_Rec_Byte(); //接收5个数据

      dht11_low; //当最后一bit数据传送完毕后,DHT11拉低总线 50us

      Delay_us(55); //这里延时55us

      dht11_high; //随后总线由上拉电阻拉高进入空闲状态。

      if(R_H + R_L + T_H + T_L == CHECK) //和检验位对比,判断校验接收到的数据是否正确

      {

       RH = R_H;

       RL = R_L;

       TH = T_H;

       TL = T_L;

      }

     }

     rec_data[0] = RH;

     rec_data[1] = RL;

     rec_data[2] = TH;

     rec_data[3] = TL;

    }

    GP2Y10.c:

    #include "Config.h"

    //初始化ADC

    //这里我们仅以规则通道为例

    //我们默认将开启通道0~3 

    void  Adc_Init(void)

    {  

     ADC_InitTypeDef ADC_InitStructure;

     GPIO_InitTypeDef GPIO_InitStructure;

     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE );   //使能ADC1通道时钟

     RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M

     //PA0 作为模拟通道输入引脚                         

     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;

     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  //模拟输入引脚

     GPIO_Init(GPIOA, &GPIO_InitStructure);

     //PB1 作为脉冲输出引脚   

     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;                  

     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;           

     GPIO_Init(GPIOB, &GPIO_InitStructure);

     GP2Y_High;

     ADC_DeInit(ADC1);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值

     ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式

     ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式

     ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式

     ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动

     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐

     ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目

     ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器  

     ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1

     ADC_ResetCalibration(ADC1); //使能复位校准  

     while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束

     ADC_StartCalibration(ADC1);  //开启AD校准

     while(ADC_GetCalibrationStatus(ADC1));  //等待校准结束

    // ADC_SoftwareStartConvCmd(ADC1, ENABLE);  //使能指定的ADC1的软件转换启动功能

    }      

    //获得ADC值

    //ch:通道值 0~3

    u16 Get_Adc(u8 ch)   

    {

       //设置指定ADC的规则组通道,一个序列,采样时间

     ADC_RegularChannelConfig(ADC1, ch, 0, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期          

      

     ADC_SoftwareStartConvCmd(ADC1, ENABLE);  //使能指定的ADC1的软件转换启动功能 

      

     while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

     return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果

    }

    float GetGP2Y(void)

    {

        u32 AD_PM;  

     float pm;

        GP2Y_Low;

        Delay_us(280);

        AD_PM = Get_Adc(ADC_Channel_0); //PA0

        Delay_us(40);

        GP2Y_High;

        Delay_us(9680);

        pm = 0.17*AD_PM-0.1; //转换公式

        //printf("%f\n",pm);

     return pm;

    }

    u16 Get_GP2Y_Average(u8 times)

    {

     u32 pm_val=0;

     u8 t;

     for(t=0;t<times;t++)

     {

      pm_val+=GetGP2Y();

      Delay_ms(5);

     }

     return pm_val/times;

    }

    附录三:元器件清单表

    元器件

    个数(/个)

    Stm32f103z8t6最小系统板

    1

    OLED

    1

    GP2Y10

    1

    DHT11

    1

    按键

    2

    滤波电容

    1

    限流电阻

    1

    附录五:硬件实物图

    附录六:PCB版图

    附录七:操作说明书

    接通电源后,系统便可正常工作,每间隔1s会主动检测环境温湿度和PM2.5浓度,并显示在OLED屏上,并当PM2.5浓度超过设定阈值时通过蜂鸣器报警。除此外,装置上装有FB5、FB6两个按键,按下FB5按键,则设定阈值加5,按下FB6按键,则设定阈值减5。

    作者:xixi….嘻嘻

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于STM32的环境质量监测课程设计详解

    发表回复