基于STM32与华为云的智能家居环境监测系统设计
项目开发背景
随着现代科技的飞速发展和人们生活水平的不断提高,智能家居系统逐渐成为家庭生活的重要组成部分。环境监测作为智能家居的基础功能之一,能够实时感知室内环境参数,为用户提供健康、舒适的生活空间。室内温度、湿度、光照强度和PM2.5浓度等数据直接影响居住者的舒适度和健康状况,例如不适宜的温湿度可能导致身体不适,而高浓度的PM2.5则与呼吸道疾病相关。因此,开发一套高效、可靠的环境监测系统具有重要的现实意义。
传统环境监测方式往往依赖人工读取或简单设备,无法实现实时数据采集和远程监控,限制了及时性和便捷性。物联网技术的兴起为解决这一问题提供了新途径,通过将传感器数据上传至云平台,用户可以随时随地查看环境状况,并基于数据做出智能决策。华为云物联网平台作为成熟的云服务解决方案,能够提供稳定、安全的数据存储和处理能力,支持大规模设备连接和数据分析,为智能家居应用奠定了技术基础。
本项目基于STM32微控制器和华为云平台,设计了一个集数据采集、传输、显示和报警于一体的智能家居环境监测系统。STM32F103C8T6作为主控芯片,具有低功耗、高性能的特点,能够高效处理多传感器数据;结合Wi-Fi模块,实现与华为云的无缝连接,确保数据实时上传和存储。同时,QT上位机提供了友好的用户界面,使环境数据可视化,并支持阈值设置,增强系统的交互性和实用性。
该系统的开发不仅满足了家庭环境监测的基本需求,还通过声光报警功能提升了安全性,帮助用户及时应对环境异常。此外,项目采用洞洞板焊接和杜邦线连接方式,体现了硬件设计的灵活性和可扩展性,为后续功能升级预留了空间。整体而言,本项目旨在推动智能家居技术的普及,促进健康、节能的生活方式。
设计实现的功能
(1) 实时采集室内温度、湿度、光照强度及PM2.5浓度数据。
(2) 数据通过Wi-Fi模块上传至华为云物联网平台并存储。
(3) QT上位机实时显示环境数据变化曲线及数值。
(4) 上位机可设置环境参数阈值并下发至STM32,触发本地声光报警。
项目硬件模块组成
(1)STM32F103C8T6最小系统核心板(主控)
(2)DHT11温湿度传感器(温湿度采集)
(3)GY-30光照传感器(光照强度采集)
(4)GP2Y1051AU0F粉尘传感器(PM2.5采集)
(5)ESP8266-01S Wi-Fi模块(华为云通信)
(6)有源蜂鸣器及LED灯(声光报警)
(7)洞洞板焊接所有电路,杜邦线连接传感器与主控
设计意义
该智能家居环境监测系统设计实现了对室内环境关键参数的实时采集与监控,包括温度、湿度、光照强度和PM2.5浓度,这些数据直接反映了居住环境的舒适性与健康性。通过持续监测,用户可以及时了解室内环境状态,从而采取相应措施改善生活品质,例如调节空调、加湿器或空气净化器,以预防因环境不适导致的健康问题,如干燥引起的呼吸道不适或PM2.5超标带来的空气污染风险。
系统将采集的数据通过Wi-Fi模块上传至华为云物联网平台,实现了数据的远程存储与云端管理,这大大扩展了监控的范围和灵活性。用户可以通过互联网随时随地访问历史数据和实时信息,支持长期趋势分析和智能决策,同时为未来集成更多智能家居功能奠定基础,体现了物联网技术在提升家居智能化水平中的实际应用价值。
QT上位机提供了直观的数据可视化界面,以曲线和数值形式实时显示环境变化,帮助用户快速识别异常波动或趋势。阈值设置功能允许用户自定义环境参数 limits,并通过声光报警机制在本地触发警示,这使得系统能够主动响应环境变化,增强家居安全性和自动化程度,例如在PM2.5浓度过高时及时提醒用户采取行动,避免健康隐患。
硬件组成基于STM32主控和常见传感器模块,如DHT11、GY-30和GP2Y1051AU0F,确保了系统的成本效益和可靠性,同时通过洞洞板焊接和杜邦线连接,简化了制作过程,适合教育、原型开发或家庭DIY项目。这种设计展示了嵌入式系统与云计算技术的有效结合,推动了智能家居技术的普及和实践应用。
设计思路
该系统设计以STM32F103C8T6最小系统板为核心控制器,负责协调整个系统的运行。首先,通过连接DHT11温湿度传感器、GY-30光照传感器和GP2Y1051AU0F粉尘传感器,实时采集室内环境数据,包括温度、湿度、光照强度和PM2.5浓度。STM32通过ADC和数字接口读取传感器数据,并进行初步处理,确保数据的准确性和稳定性。
数据采集完成后,STM32通过串口通信控制ESP8266-01S Wi-Fi模块,将环境数据打包成JSON格式,并利用MQTT协议上传至华为云物联网平台。华为云平台负责数据的存储和管理,提供设备连接和数据订阅功能,确保数据可靠传输。Wi-Fi模块配置为STA模式,连接到本地路由器,实现与互联网的通信。
QT上位机作为用户界面,通过TCP/IP协议与华为云平台交互,实时订阅环境数据。上位机软件设计采用QT框架,绘制数据变化曲线图并显示数值,用户可直观监控环境状况。同时,上位机提供界面输入框,允许用户设置温度、湿度、光照和PM2.5的阈值,这些设置通过MQTT协议下发至STM32。
STM32接收来自华为云的阈值设置指令后,将其存储于内部Flash或变量中。在数据采集过程中,STM32实时比较当前环境数据与阈值,如果任何参数超出设定范围,则触发本地声光报警:通过GPIO控制有源蜂鸣器发出声音,并点亮LED灯,提醒用户注意环境变化。整个系统硬件基于洞洞板焊接,使用杜邦线连接传感器和主控板,确保电路简洁可靠。
框架图
+----------------+ +----------------+ +----------------+
| | | | | |
| DHT11 |----->| | | |
| (Temp/Humid) | | | | |
+----------------+ | | | |
| STM32F103 | | ESP8266 Wi-Fi |
+----------------+ | Core Board |----->| Module |
| | | (Main Ctrl) | | |
| GY-30 |----->| | | |
| (Light) | | | | |
+----------------+ | | | |
| | | |
+----------------+ | | | |
| | | | | |
| GP2Y1051 |----->| | | |
| (PM2.5) | | | | |
+----------------+ +----------------+ +----------------+
|
|
V
+----------------+
| |
| Huawei Cloud |
| IoT Platform |
| |
+----------------+
|
|
V
+----------------+
| |
| QT Upper |
| Computer |
| (Display & |
| Control) |
+----------------+
+----------------+ +----------------+
| | | |
| Buzzer & LED |<-----| STM32 |
| (Alarm) | | (Local Ctrl) |
+----------------+ +----------------+
系统总体设计
系统总体设计基于STM32F103C8T6最小系统核心板作为主控制器,负责协调整个智能家居环境监测系统的运行。该系统通过集成多种传感器实时采集室内环境数据,包括DHT11温湿度传感器用于测量温度和湿度,GY-30光照传感器用于检测光照强度,以及GP2Y1051AU0F粉尘传感器用于监测PM2.5浓度。这些传感器通过杜邦线连接到STM32主控板,利用洞洞板进行电路焊接,确保连接的稳定性和可靠性。
数据采集完成后,STM32主控板对传感器数据进行初步处理和格式化,然后通过ESP8266-01S Wi-Fi模块将数据上传至华为云物联网平台。Wi-Fi模块负责建立与华为云的通信连接,实现数据的实时传输和存储,确保环境数据能够远程访问和管理。
QT上位机作为用户界面,实时接收并显示从华为云平台获取的环境数据,包括数值显示和变化曲线图,帮助用户直观监控室内环境状况。同时,上位机允许用户设置环境参数的阈值,如温度、湿度、光照和PM2.5的上下限,这些设置通过华为云平台下发至STM32主控板。
当环境数据超过用户设置的阈值时,STM32主控板会触发本地声光报警系统,控制有源蜂鸣器和LED灯进行报警提示,从而实现对异常环境的及时响应。整个系统设计注重实用性和稳定性,确保数据采集、传输和报警功能的连贯运行。
系统功能总结
功能描述 | 实现方式 | 相关硬件 |
---|---|---|
实时采集室内温度、湿度、光照强度及PM2.5浓度数据 | 使用传感器模块进行数据采集 | DHT11温湿度传感器、GY-30光照传感器、GP2Y1051AU0F粉尘传感器 |
数据上传至华为云物联网平台并存储 | 通过Wi-Fi模块将数据发送到云平台 | ESP8266-01S Wi-Fi模块 |
QT上位机实时显示环境数据变化曲线及数值 | 在PC上运行QT应用程序,接收并显示数据 | QT上位机软件(运行于PC) |
设置环境参数阈值并触发本地声光报警 | 上位机设置阈值,通过Wi-Fi下发到STM32,控制报警装置 | 有源蜂鸣器、LED灯 |
设计的各个功能模块描述
数据采集模块负责实时获取室内环境参数,包括使用DHT11传感器采集温度和湿度数据,该传感器通过数字信号输出;GY-30光照传感器采集光照强度,基于I2C通信协议与主控交互;GP2Y1051AU0F粉尘传感器采集PM2.5浓度,通过模拟电压输出经ADC转换。所有传感器通过杜邦线连接到主控板,确保数据准确采集。
数据处理模块以STM32F103C8T6最小系统核心板为核心,对采集到的原始数据进行处理,包括ADC转换、数据校准和格式化,确保数据符合上传和显示要求。主控程序负责协调各传感器读取时序,并初步处理异常数据,为后续通信和报警提供基础。
通信模块依托ESP8266-01S Wi-Fi模块实现,该模块通过串口与STM32主控连接,负责将处理后的环境数据打包并通过MQTT或HTTP协议上传至华为云物联网平台。模块配置为自动连接网络并处理数据传输中的错误重试,确保数据可靠上传。
云平台模块基于华为云物联网平台构建,接收并存储上传的环境数据,提供数据持久化和查询功能。平台通过设备影子机制管理设备状态,并支持数据可视化接口,为上位机提供数据拉取和命令下发服务。
上位机模块采用QT开发,实现实时环境数据显示,包括数值和变化曲线图,通过HTTP或MQTT协议从华为云拉取数据。该模块允许用户设置温度、湿度、光照和PM2.5的阈值,并通过云平台或直接串口通信将阈值下发给STM32,触发本地报警逻辑。
报警模块由有源蜂鸣器和LED灯组成,当环境参数超过设定的阈值时,STM32主控驱动这些组件发出声光报警信号。报警逻辑在本地处理,确保即使网络中断也能及时响应,提高系统的可靠性。
上位机代码设计
以下是基于QT框架的C++上位机代码设计,用于实时显示环境数据(温度、湿度、光照强度、PM2.5浓度)的变化曲线和数值,并允许设置阈值下发至STM32。代码使用QT的图表模块(QChart)和MQTT客户端(QMqttClient)连接到华为云物联网平台。假设华为云MQTT broker的地址、端口、主题等已配置,实际使用时需替换为实际参数。
项目文件结构:
- •
main.cpp
:应用程序入口点。 - •
mainwindow.h
:主窗口类声明。 - •
mainwindow.cpp
:主窗口类实现。 - •
project.pro
:QT项目配置文件。
代码实现:
1. main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
2. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMqttClient>
#include <QChart>
#include <QChartView>
#include <QLineSeries>
#include <QValueAxis>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
QT_CHARTS_USE_NAMESPACE
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onConnectButtonClicked();
void onDisconnectButtonClicked();
void onSetThresholdButtonClicked();
void updateChart();
void onMessageReceived(const QByteArray &message, const QMqttTopicName &topic);
void onMqttConnected();
void onMqttDisconnected();
private:
Ui::MainWindow *ui;
QMqttClient *mqttClient;
QChart *temperatureChart;
QChart *humidityChart;
QChart *lightChart;
QChart *pm25Chart;
QLineSeries *temperatureSeries;
QLineSeries *humiditySeries;
QLineSeries *lightSeries;
QLineSeries *pm25Series;
QValueAxis *axisX;
QValueAxis *axisYTemperature;
QValueAxis *axisYHumidity;
QValueAxis *axisYLight;
QValueAxis *axisYPm25;
QTimer *dataTimer;
int timeCount;
void setupCharts();
void setupMqttClient();
void parseData(const QJsonObject &json);
};
#endif // MAINWINDOW_H
3. mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QDateTime>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
mqttClient(new QMqttClient(this)),
temperatureChart(new QChart()),
humidityChart(new QChart()),
lightChart(new QChart()),
pm25Chart(new QChart()),
temperatureSeries(new QLineSeries()),
humiditySeries(new QLineSeries()),
lightSeries(new QLineSeries()),
pm25Series(new QLineSeries()),
axisX(new QValueAxis()),
axisYTemperature(new QValueAxis()),
axisYHumidity(new QValueAxis()),
axisYLight(new QValueAxis()),
axisYPm25(new QValueAxis()),
dataTimer(new QTimer(this)),
timeCount(0)
{
ui->setupUi(this);
setupCharts();
setupMqttClient();
connect(ui->connectButton, &QPushButton::clicked, this, &MainWindow::onConnectButtonClicked);
connect(ui->disconnectButton, &QPushButton::clicked, this, &MainWindow::onDisconnectButtonClicked);
connect(ui->setThresholdButton, &QPushButton::clicked, this, &MainWindow::onSetThresholdButtonClicked);
connect(dataTimer, &QTimer::timeout, this, &MainWindow::updateChart);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::setupCharts()
{
// Temperature chart
temperatureChart->addSeries(temperatureSeries);
temperatureChart->setTitle("Temperature");
temperatureChart->setAnimationOptions(QChart::SeriesAnimations);
axisX->setTitleText("Time (s)");
axisX->setRange(0, 60);
axisYTemperature->setTitleText("°C");
axisYTemperature->setRange(0, 50);
temperatureChart->addAxis(axisX, Qt::AlignBottom);
temperatureChart->addAxis(axisYTemperature, Qt::AlignLeft);
temperatureSeries->attachAxis(axisX);
temperatureSeries->attachAxis(axisYTemperature);
ui->temperatureChartView->setChart(temperatureChart);
// Humidity chart
humidityChart->addSeries(humiditySeries);
humidityChart->setTitle("Humidity");
humidityChart->setAnimationOptions(QChart::SeriesAnimations);
axisYHumidity->setTitleText("%");
axisYHumidity->setRange(0, 100);
humidityChart->addAxis(axisX, Qt::AlignBottom);
humidityChart->addAxis(axisYHumidity, Qt::AlignLeft);
humiditySeries->attachAxis(axisX);
humiditySeries->attachAxis(axisYHumidity);
ui->humidityChartView->setChart(humidityChart);
// Light chart
lightChart->addSeries(lightSeries);
lightChart->setTitle("Light Intensity");
lightChart->setAnimationOptions(QChart::SeriesAnimations);
axisYLight->setTitleText("Lux");
axisYLight->setRange(0, 1000);
lightChart->addAxis(axisX, Qt::AlignBottom);
lightChart->addAxis(axisYLight, Qt::AlignLeft);
lightSeries->attachAxis(axisX);
lightSeries->attachAxis(axisYLight);
ui->lightChartView->setChart(lightChart);
// PM2.5 chart
pm25Chart->addSeries(pm25Series);
pm25Chart->setTitle("PM2.5 Concentration");
pm25Chart->setAnimationOptions(QChart::SeriesAnimations);
axisYPm25->setTitleText("μg/m3");
axisYPm25->setRange(0, 500);
pm25Chart->addAxis(axisX, Qt::AlignBottom);
pm25Chart->addAxis(axisYPm25, Qt::AlignLeft);
pm25Series->attachAxis(axisX);
pm25Series->attachAxis(axisYPm25);
ui->pm25ChartView->setChart(pm25Chart);
dataTimer->start(1000); // Update every second
}
void MainWindow::setupMqttClient()
{
mqttClient->setHostname("your_huawei_cloud_broker_url"); // Replace with actual broker URL
mqttClient->setPort(1883); // Default MQTT port
// Set username and password if required, e.g., mqttClient->setUsername("username");
// mqttClient->setPassword("password");
connect(mqttClient, &QMqttClient::connected, this, &MainWindow::onMqttConnected);
connect(mqttClient, &QMqttClient::disconnected, this, &MainWindow::onMqttDisconnected);
connect(mqttClient, &QMqttClient::messageReceived, this, &MainWindow::onMessageReceived);
}
void MainWindow::onConnectButtonClicked()
{
if (mqttClient->state() == QMqttClient::Disconnected) {
mqttClient->connectToHost();
}
}
void MainWindow::onDisconnectButtonClicked()
{
if (mqttClient->state() == QMqttClient::Connected) {
mqttClient->disconnectFromHost();
}
}
void MainWindow::onSetThresholdButtonClicked()
{
float tempThreshold = ui->tempThresholdSpinBox->value();
float humidityThreshold = ui->humidityThresholdSpinBox->value();
float lightThreshold = ui->lightThresholdSpinBox->value();
float pm25Threshold = ui->pm25ThresholdSpinBox->value();
QJsonObject thresholdData;
thresholdData["temp_threshold"] = tempThreshold;
thresholdData["humidity_threshold"] = humidityThreshold;
thresholdData["light_threshold"] = lightThreshold;
thresholdData["pm25_threshold"] = pm25Threshold;
QJsonDocument doc(thresholdData);
QByteArray message = doc.toJson();
// Publish to threshold topic; replace "device/threshold" with actual topic
mqttClient->publish(QMqttTopicName("device/threshold"), message);
}
void MainWindow::updateChart()
{
timeCount++;
if (timeCount > 60) {
timeCount = 0;
temperatureSeries->clear();
humiditySeries->clear();
lightSeries->clear();
pm25Series->clear();
}
}
void MainWindow::onMessageReceived(const QByteArray &message, const QMqttTopicName &topic)
{
QJsonDocument doc = QJsonDocument::fromJson(message);
if (doc.isNull()) {
return;
}
QJsonObject json = doc.object();
parseData(json);
}
void MainWindow::parseData(const QJsonObject &json)
{
float temperature = json["temperature"].toDouble();
float humidity = json["humidity"].toDouble();
float light = json["light"].toDouble();
float pm25 = json["pm25"].toDouble();
// Update labels
ui->tempLabel->setText(QString::number(temperature) + " °C");
ui->humidityLabel->setText(QString::number(humidity) + " %");
ui->lightLabel->setText(QString::number(light) + " Lux");
ui->pm25Label->setText(QString::number(pm25) + " μg/m3");
// Update charts
temperatureSeries->append(timeCount, temperature);
humiditySeries->append(timeCount, humidity);
lightSeries->append(timeCount, light);
pm25Series->append(timeCount, pm25);
}
void MainWindow::onMqttConnected()
{
ui->statusLabel->setText("Connected to Huawei Cloud");
// Subscribe to data topic; replace "device/data" with actual topic
mqttClient->subscribe(QMqttTopicFilter("device/data"));
}
void MainWindow::onMqttDisconnected()
{
ui->statusLabel->setText("Disconnected");
}
4. project.pro
(QT项目配置文件)
QT += core gui charts mqtt
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
使用说明:
- 1. 在QT Creator中创建新项目,替换文件内容。
- 2. 在UI设计器中,主窗口(MainWindow)应包含:
- • 四个QChartView对象(用于温度、湿度、光照、PM2.5图表),命名为
temperatureChartView
、humidityChartView
、lightChartView
、pm25ChartView
。 - • 四个QLabel对象用于显示当前数值,命名为
tempLabel
、humidityLabel
、lightLabel
、pm25Label
。 - • 四个QDoubleSpinBox对象用于设置阈值,命名为
tempThresholdSpinBox
、humidityThresholdSpinBox
、lightThresholdSpinBox
、pm25ThresholdSpinBox
。 - • 按钮:连接按钮(
connectButton
)、断开按钮(disconnectButton
)、设置阈值按钮(setThresholdButton
)。 - • 一个QLabel用于状态显示(
statusLabel
)。 - 3. 替换MQTT broker的地址、端口、主题等为实际华为云参数。
- 4. 编译并运行项目。
此代码实现了基本功能,实际应用中可能需要错误处理和更多优化。
模块代码设计
#include "stm32f10x.h"
// 引脚定义
#define DHT11_PIN GPIO_Pin_0
#define DHT11_PORT GPIOA
#define BUZZER_PIN GPIO_Pin_2
#define BUZZER_PORT GPIOA
#define LED_PIN GPIO_Pin_3
#define LED_PORT GPIOA
// ADC通道定义
#define PM25_ADC_CHANNEL ADC_Channel_1
#define PM25_ADC_DR_ADDR ((u32)0x4001244C)
// I2C定义
#define I2C_PORT GPIOB
#define I2C_SCL_PIN GPIO_Pin_6
#define I2C_SDA_PIN GPIO_Pin_7
#define I2Cx I2C1
#define I2C_SPEED 100000 // 100kHz
// UART定义
#define USARTx USART1
#define USARTx_IRQn USART1_IRQn
#define USARTx_IRQHandler USART1_IRQHandler
// GY-30光照传感器I2C地址
#define GY30_ADDRESS 0x23
#define GY30_READ_CMD 0x20
// 全局变量
volatile uint8_t uart_rx_buffer[100];
volatile uint8_t uart_rx_index = 0;
volatile uint8_t uart_rx_flag = 0;
float temperature_threshold = 30.0;
float humidity_threshold = 60.0;
float light_threshold = 300.0;
float pm25_threshold = 50.0;
// 函数声明
void SystemInit(void);
void GPIO_Init(void);
void I2C_Init(void);
void ADC_Init(void);
void USART_Init(void);
void NVIC_Init(void);
void DHT11_Start(void);
uint8_t DHT11_ReadByte(void);
void DHT11_ReadData(float *temp, float *humi);
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(uint8_t data);
uint8_t I2C_ReadByte(void);
void I2C_Ack(void);
void I2C_NAck(void);
uint16_t GY30_ReadLight(void);
uint16_t ADC_Read(void);
float PM25_GetConcentration(void);
void USART_SendString(char *str);
void ESP8266_SendData(float temp, float humi, uint16_t light, float pm25);
void CheckThresholds(float temp, float humi, uint16_t light, float pm25);
void Delay_ms(uint32_t nTime);
int main(void) {
SystemInit();
GPIO_Init();
I2C_Init();
ADC_Init();
USART_Init();
NVIC_Init();
float temperature, humidity;
uint16_t light;
float pm25;
while (1) {
DHT11_ReadData(&temperature, &humidity);
light = GY30_ReadLight();
pm25 = PM25_GetConcentration();
ESP8266_SendData(temperature, humidity, light, pm25);
CheckThresholds(temperature, humidity, light, pm25);
Delay_ms(5000); // 每5秒采集一次
}
}
void SystemInit(void) {
// 设置系统时钟为72MHz
RCC->CFGR |= RCC_CFGR_PLLMULL9; // PLL multiplier 9
RCC->CFGR |= RCC_CFGR_PLLSRC; // PLL source HSE
RCC->CR |= RCC_CR_HSEON; // Enable HSE
while (!(RCC->CR & RCC_CR_HSERDY)); // Wait for HSE ready
RCC->CR |= RCC_CR_PLLON; // Enable PLL
while (!(RCC->CR & RCC_CR_PLLRDY)); // Wait for PLL ready
RCC->CFGR |= RCC_CFGR_SW_PLL; // Switch to PLL
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // Wait for switch
}
void GPIO_Init(void) {
// 使能GPIO时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
// DHT11引脚(PA0)推挽输出/浮空输入
GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
GPIOA->CRL |= GPIO_CRL_MODE0_0; // Output mode, max speed 10 MHz
// 蜂鸣器(PA2)推挽输出
GPIOA->CRL &= ~(GPIO_CRL_MODE2 | GPIO_CRL_CNF2);
GPIOA->CRL |= GPIO_CRL_MODE2; // Output mode, max speed 50 MHz
// LED(PA3)推挽输出
GPIOA->CRL &= ~(GPIO_CRL_MODE3 | GPIO_CRL_CNF3);
GPIOA->CRL |= GPIO_CRL_MODE3; // Output mode, max speed 50 MHz
// I2C引脚(PB6 SCL, PB7 SDA)开漏输出
GPIOB->CRL &= ~(GPIO_CRL_MODE6 | GPIO_CRL_CNF6 | GPIO_CRL_MODE7 | GPIO_CRL_CNF7);
GPIOB->CRL |= (GPIO_CRL_CNF6_0 | GPIO_CRL_CNF7_0); // AF open drain
GPIOB->CRL |= (GPIO_CRL_MODE6 | GPIO_CRL_MODE7); // Output mode, max speed 50 MHz
// UART引脚(PA9 TX, PA10 RX)
GPIOA->CRH &= ~(GPIO_CRH_CNF9 | GPIO_CRH_MODE9 | GPIO_CRH_CNF10 | GPIO_CRH_MODE10);
GPIOA->CRH |= GPIO_CRH_CNF9_1; // AF push-pull for TX
GPIOA->CRH |= GPIO_CRH_MODE9; // Output mode, max speed 50 MHz
GPIOA->CRH |= GPIO_CRH_CNF10_0; // Floating input for RX
}
void I2C_Init(void) {
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // Enable I2C1 clock
I2Cx->CR1 &= ~I2C_CR1_PE; // Disable I2C
I2Cx->CR2 |= 36; // APB1 clock 36MHz, set FREQ to 36
I2Cx->CCR |= 180; // CCR = APB1 clock / (2 * I2C_SPEED) = 36M / (2*100k) = 180
I2Cx->TRISE |= 37; // TRISE = (1000ns / (1/36M)) + 1 = 37
I2Cx->CR1 |= I2C_CR1_PE; // Enable I2C
}
void ADC_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Enable ADC1 clock
ADC1->SQR1 &= ~ADC_SQR1_L; // 1 conversion
ADC1->SQR3 |= PM25_ADC_CHANNEL; // Channel 1
ADC1->CR2 |= ADC_CR2_ADON; // Enable ADC
// Calibration
ADC1->CR2 |= ADC_CR2_RSTCAL;
while (ADC1->CR2 & ADC_CR2_RSTCAL);
ADC1->CR2 |= ADC_CR2_CAL;
while (ADC1->CR2 & ADC_CR2_CAL);
}
void USART_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // Enable USART1 clock
USARTx->BRR = 72000000 / 115200; // Baud rate 115200
USARTx->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; // Enable TX, RX, USART
USARTx->CR1 |= USART_CR1_RXNEIE; // Enable RX interrupt
}
void NVIC_Init(void) {
NVIC->ISER[0] |= (1 << USARTx_IRQn); // Enable USART interrupt
}
void DHT11_Start(void) {
GPIOA->CRL &= ~GPIO_CRL_CNF0; // Output mode
GPIOA->BRR = DHT11_PIN; // Set low
Delay_ms(18);
GPIOA->BSRR = DHT11_PIN; // Set high
Delay_ms(30);
GPIOA->CRL |= GPIO_CRL_CNF0_0; // Input mode
}
uint8_t DHT11_ReadByte(void) {
uint8_t data = 0;
for (int i = 0; i < 8; i++) {
while (!(GPIOA->IDR & DHT11_PIN)); // Wait for high
Delay_ms(40);
if (GPIOA->IDR & DHT11_PIN) {
data |= (1 << (7 - i));
while (GPIOA->IDR & DHT11_PIN); // Wait for low
}
}
return data;
}
void DHT11_ReadData(float *temp, float *humi) {
uint8_t data[5];
DHT11_Start();
if (!(GPIOA->IDR & DHT11_PIN)) {
while (!(GPIOA->IDR & DHT11_PIN)); // Wait for high
while (GPIOA->IDR & DHT11_PIN); // Wait for low
for (int i = 0; i < 5; i++) {
data[i] = DHT11_ReadByte();
}
if (data[4] == (data[0] + data[1] + data[2] + data[3])) {
*humi = data[0] + data[1] * 0.1;
*temp = data[2] + data[3] * 0.1;
}
}
}
void I2C_Start(void) {
I2Cx->CR1 |= I2C_CR1_START;
while (!(I2Cx->SR1 & I2C_SR1_SB));
}
void I2C_Stop(void) {
I2Cx->CR1 |= I2C_CR1_STOP;
while (I2Cx->CR1 & I2C_CR1_STOP);
}
void I2C_SendByte(uint8_t data) {
I2Cx->DR = data;
while (!(I2Cx->SR1 & I2C_SR1_TXE));
}
uint8_t I2C_ReadByte(void) {
while (!(I2Cx->SR1 & I2C_SR1_RXNE));
return I2Cx->DR;
}
void I2C_Ack(void) {
I2Cx->CR1 |= I2C_CR1_ACK;
}
void I2C_NAck(void) {
I2Cx->CR1 &= ~I2C_CR1_ACK;
}
uint16_t GY30_ReadLight(void) {
I2C_Start();
I2C_SendByte(GY30_ADDRESS << 1);
I2C_SendByte(GY30_READ_CMD);
I2C_Start();
I2C_SendByte((GY30_ADDRESS << 1) | 0x01);
uint8_t high = I2C_ReadByte();
I2C_Ack();
uint8_t low = I2C_ReadByte();
I2C_NAck();
I2C_Stop();
return (high << 8) | low;
}
uint16_t ADC_Read(void) {
ADC1->CR2 |= ADC_CR2_ADON;
while (!(ADC1->SR & ADC_SR_EOC));
return ADC1->DR;
}
float PM25_GetConcentration(void) {
uint16_t adc_value = ADC_Read();
float voltage = adc_value * 3.3 / 4096; // Assume Vref=3.3V, 12-bit ADC
// Calibration formula for GP2Y1051AU0F: example linear conversion, adjust based on sensor datasheet
float concentration = (voltage - 0.6) * 100; // Example formula, replace with actual calibration
return concentration > 0 ? concentration : 0;
}
void USART_SendString(char *str) {
while (*str) {
while (!(USARTx->SR & USART_SR_TXE));
USARTx->DR = *str++;
}
}
void ESP8266_SendData(float temp, float humi, uint16_t light, float pm25) {
char buffer[100];
sprintf(buffer, "AT+MQTTPUB=0,\"topic\",\"{\"temp\":%.1f,\"humi\":%.1f,\"light\":%d,\"pm25\":%.1f}\"\r\n", temp, humi, light, pm25);
USART_SendString(buffer);
}
void CheckThresholds(float temp, float humi, uint16_t light, float pm25) {
if (temp > temperature_threshold || humi > humidity_threshold || light > light_threshold || pm25 > pm25_threshold) {
GPIOA->BSRR = BUZZER_PIN; // Buzzer on
GPIOA->BSRR = LED_PIN; // LED on
} else {
GPIOA->BRR = BUZZER_PIN; // Buzzer off
GPIOA->BRR = LED_PIN; // LED off
}
}
void Delay_ms(uint32_t nTime) {
for (volatile uint32_t i = 0; i < nTime * 7200; i++);
}
// USART1 interrupt handler
void USARTx_IRQHandler(void) {
if (USARTx->SR & USART_SR_RXNE) {
uint8_t data = USARTx->DR;
if (data == '\n') {
uart_rx_buffer[uart_rx_index] = '\0';
uart_rx_flag = 1;
uart_rx_index = 0;
} else {
uart_rx_buffer[uart_rx_index++] = data;
}
}
// Parse threshold if flag set
if (uart_rx_flag) {
// Example format: "THRESHOLD,TEMP,30.0,HUMI,60.0,LIGHT,300.0,PM25,50.0"
if (strncmp((char*)uart_rx_buffer, "THRESHOLD", 9) == 0) {
sscanf((char*)uart_rx_buffer, "THRESHOLD,TEMP,%f,HUMI,%f,LIGHT,%f,PM25,%f",
&temperature_threshold, &humidity_threshold, &light_threshold, &pm25_threshold);
}
uart_rx_flag = 0;
}
}
项目核心代码
#include "stm32f10x.h"
// 假设其他模块已经编写好,声明外部函数
extern void DHT11_Read(float *temp, float *humi);
extern uint16_t BH1750_ReadLight(void);
extern uint16_t ADC_ReadPM25(void);
extern void WiFi_SendData(float temp, float humi, uint16_t light, uint16_t pm25);
extern void Buzzer_On(void);
extern void Buzzer_Off(void);
extern void LED_On(void);
extern void LED_Off(void);
// 全局变量用于存储阈值
float temp_threshold = 30.0;
float humi_threshold = 80.0;
uint16_t light_threshold = 500;
uint16_t pm25_threshold = 100;
// UART接收缓冲区
#define RX_BUF_SIZE 64
volatile uint8_t uart_rx_buf[RX_BUF_SIZE];
volatile uint8_t uart_rx_index = 0;
volatile uint8_t uart_cmd_ready = 0;
// 系统初始化函数
void System_Init(void) {
// 启用时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_USART1EN | RCC_APB2ENR_ADC1EN | RCC_APB2ENR_AFIOEN;
// 初始化LED和蜂鸣器引脚 (PC13和PC14)
GPIOC->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_CNF14 | GPIO_CRH_MODE13 | GPIO_CRH_MODE14);
GPIOC->CRH |= GPIO_CRH_MODE13_0 | GPIO_CRH_MODE14_0; // 输出模式,最大速度10MHz
GPIOC->ODR |= GPIO_ODR_ODR13 | GPIO_ODR_ODR14; // 初始化为高电平(关闭)
// 初始化UART1 (PA9 TX, PA10 RX)
GPIOA->CRH &= ~(GPIO_CRH_CNF9 | GPIO_CRH_CNF10 | GPIO_CRH_MODE9 | GPIO_CRH_MODE10);
GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_0; // PA9: 推挽输出,最大速度10MHz
GPIOA->CRH |= GPIO_CRH_CNF10_0; // PA10: 浮空输入
USART1->BRR = 72000000 / 115200; // 假设系统时钟72MHz,波特率115200
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE | USART_CR1_RXNEIE; // 启用发送、接收、UART和接收中断
NVIC_EnableIRQ(USART1_IRQn); // 启用USART1中断
// 初始化I2C1 (PB6 SCL, PB7 SDA)
GPIOB->CRL &= ~(GPIO_CRL_CNF6 | GPIO_CRL_CNF7 | GPIO_CRL_MODE6 | GPIO_CRL_MODE7);
GPIOB->CRL |= GPIO_CRL_CNF6_1 | GPIO_CRL_CNF7_1; // 开漏输出
GPIOB->CRL |= GPIO_CRL_MODE6_0 | GPIO_CRL_MODE7_0; // 输出模式,最大速度10MHz
I2C1->CR2 = 36; // 设置I2C时钟为2MHz (72MHz/36=2MHz)
I2C1->CCR = 0x50; // 设置CCR for standard mode
I2C1->TRISE = 0x09; // 设置TRISE
I2C1->CR1 |= I2C_CR1_PE; // 启用I2C
// 初始化ADC1 for PM2.5传感器 (PA1)
GPIOA->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_MODE1);
GPIOA->CRL |= GPIO_CRL_CNF1_0; // 模拟输入
ADC1->SQR1 = 0; // 1 conversion
ADC1->SQR2 = 0;
ADC1->SQR3 = 1; // Channel 1
ADC1->SMPR2 = ADC_SMPR2_SMP1; // 采样时间
ADC1->CR2 |= ADC_CR2_ADON; // 启用ADC
// 简单延时以确保初始化完成
for(volatile int i = 0; i < 100000; i++);
}
// UART1中断处理函数
void USART1_IRQHandler(void) {
if(USART1->SR & USART_SR_RXNE) {
uint8_t data = USART1->DR;
if(uart_rx_index < RX_BUF_SIZE - 1) {
uart_rx_buf[uart_rx_index++] = data;
if(data == '\n') { // 假设命令以换行符结束
uart_rx_buf[uart_rx_index] = '\0';
uart_cmd_ready = 1;
uart_rx_index = 0;
}
} else {
uart_rx_index = 0; // 缓冲区溢出,重置
}
}
}
// 解析UART命令并设置阈值
void Parse_Command(void) {
if(uart_cmd_ready) {
uart_cmd_ready = 0;
// 简单解析示例: 假设命令格式为 "TEMP:25,HUMI:50,LIGHT:500,PM25:100"
sscanf((char*)uart_rx_buf, "TEMP:%f,HUMI:%f,LIGHT:%hu,PM25:%hu",
&temp_threshold, &humi_threshold, &light_threshold, &pm25_threshold);
}
}
// 检查传感器数据并触发报警
void Check_Alarm(float temp, float humi, uint16_t light, uint16_t pm25) {
if(temp > temp_threshold || humi > humi_threshold || light > light_threshold || pm25 > pm25_threshold) {
Buzzer_On();
LED_On();
} else {
Buzzer_Off();
LED_Off();
}
}
int main(void) {
System_Init();
float temperature, humidity;
uint16_t light_intensity, pm25_value;
while(1) {
// 读取传感器数据
DHT11_Read(&temperature, &humidity);
light_intensity = BH1750_ReadLight();
pm25_value = ADC_ReadPM25();
// 发送数据到华为云 via WiFi
WiFi_SendData(temperature, humidity, light_intensity, pm25_value);
// 检查并处理UART命令
Parse_Command();
// 检查报警条件
Check_Alarm(temperature, humidity, light_intensity, pm25_value);
// 简单延时
for(volatile int i = 0; i < 1000000; i++);
}
}
总结
本系统成功设计并实现了一个基于STM32微控制器与华为云平台的智能家居环境监测系统,能够实时采集室内温度、湿度、光照强度和PM2.5浓度等关键环境参数。通过集成多种传感器和Wi-Fi通信模块,系统实现了数据的准确采集与远程传输,为用户提供了全面的环境状态监控功能。
硬件方面采用STM32F103C8T6核心板作为主控单元,搭配DHT11温湿度传感器、GY-30光照传感器、GP2Y1051AU0F粉尘传感器进行数据采集,并通过ESP8266-01S模块将数据上传至华为云物联网平台。声光报警模块由有源蜂鸣器和LED灯组成,确保在环境参数超出阈值时及时发出本地警示。所有电路通过洞洞板焊接和杜邦线连接,体现了系统的实用性和可扩展性。
软件层面通过QT上位机实现了数据的实时曲线显示和数值监控,同时支持用户自定义阈值设置并下发给STM32,增强了系统的交互性和灵活性。整体设计不仅满足了智能家居环境监测的基本需求,还为未来功能扩展如自动化控制或大数据分析奠定了基础,具有较高的应用价值和推广前景。
- 点赞
- 收藏
- 关注作者
评论(0)