• [技术干货] 基于STM32设计的智能睡眠监测与改善系统设计
    项目开发背景随着现代生活节奏的加快和工作压力的增大,睡眠质量问题日益成为影响人们身心健康的重要因素。不良的睡眠不仅会导致疲劳、注意力下降,还可能引发心血管疾病、代谢紊乱等长期健康问题,因此对睡眠进行有效监测和改善具有迫切的社会需求。当前,许多人依赖于简单的睡眠追踪设备,但这些设备往往功能单一,无法全面评估睡眠环境和个人生理状态,限制了其实际应用效果。传统的睡眠监测系统多侧重于单一参数如心率或运动检测,缺乏对环境因素如噪音、光线等的综合考虑,且很少具备主动干预功能来优化睡眠条件。这使得用户难以获得全面的睡眠质量评估和个性化的改善建议。此外,现有商业产品通常成本较高,且数据分析和报告生成功能不够灵活,无法满足普通家庭或个性化需求。基于STM32的智能睡眠监测与改善系统设计旨在解决这些局限,通过集成多传感器技术,实时采集血氧心率、环境噪音等关键参数,并结合智能算法实现环境因素的自动调节。该系统利用低成本、开源的硬件平台,使得睡眠监测变得更加 accessible 和实用,同时通过华为云数据传输和QT上位机界面,提供直观的数据可视化和历史记录分析,增强用户体验和长期健康管理能力。该项目的开发背景还强调了物联网和智能家居的发展趋势,通过将嵌入式技术与云平台结合,实现睡眠数据的远程监控和共享,为家庭健康管理提供创新解决方案。最终,该系统不仅有助于用户了解自身睡眠模式,还能通过柔光唤醒、噪音控制等主动干预措施,显著提升睡眠质量,促进整体 wellbeing。设计实现的功能(1)监测人体生理参数(心率和血氧)(2)监测环境噪音水平(3)控制RGB灯带进行柔光调节以改善睡眠质量(4)通过Wi-Fi模块将监测数据传输到华为云项目硬件模块组成(1)STM32F103C8T6最小系统核心板作为主控制器(2)MAX30102血氧心率传感器监测生理参数(3)声音传感器监测环境噪音水平(4)WS2812 RGB灯带提供柔光唤醒功能(5)ESP8266-01S Wi-Fi模块实现华为云数据传输(6)洞洞板焊接生物信号调理电路,杜邦线连接传感器设计意义基于STM32设计的智能睡眠监测与改善系统通过集成多传感器实时采集睡眠环境参数和人体生理数据,如心率、血氧和环境噪音,为用户提供全面的睡眠质量评估,有助于早期发现睡眠障碍问题,促进健康意识的提升,并在日常生活中实现非侵入式、便捷的健康监测。系统具备智能调节环境因素的功能,如利用RGB灯带提供柔光唤醒,有效模拟自然光照变化,减少传统闹钟的突然干扰,从而优化睡眠-觉醒周期,改善用户的睡眠体验和整体休息质量,这对于现代高压生活方式下的人群具有实际的应用价值。通过生成睡眠质量报告和改进建议,系统不仅提供数据汇总,还基于分析结果给出个性化建议,帮助用户理解睡眠模式并采取针对性措施,同时QT上位机显示图表和历史记录,使得数据可视化,便于用户长期跟踪和趋势分析,增强系统的实用性和用户参与度。借助ESP8266 Wi-Fi模块实现华为云数据传输,系统支持远程监控和数据存储,用户可以通过网络访问历史记录,实现跨设备共享,为家庭或医疗环境提供便利,体现了物联网技术在健康管理中的创新应用,具有较高的可扩展性和社会意义。整体设计基于STM32微控制器和常见传感器硬件,展示了嵌入式系统在智能健康领域的低成本、高效能解决方案,不仅适用于个人家庭,还可为相关研究和产品开发提供参考,推动睡眠科技的发展和应用普及。设计思路本系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行,通过集成多传感器实时监测睡眠环境参数和人体状态,旨在提供全面的睡眠质量评估和改善方案。系统设计注重实际应用,利用硬件资源高效处理数据,确保稳定性和可靠性。传感器部分包括MAX30102血氧心率传感器用于采集用户的心率和血氧饱和度数据,声音传感器监测环境噪音水平,以评估睡眠环境的干扰因素。此外,通过洞洞板焊接的生物信号调理电路,对生理信号进行初步处理和放大,确保数据准确性,杜邦线连接方式便于模块化设计和调试。智能调节功能主要通过WS2812 RGB灯带实现,系统根据监测数据动态调节光线色温和亮度,例如在唤醒阶段提供柔光渐变,以模拟自然日出,帮助用户舒适苏醒,从而改善睡眠质量。调节策略基于预设算法,如噪音超标时自动调整环境或提供视觉提示。睡眠质量报告和改进建议的生成基于采集的传感器数据,STM32控制器进行初步数据分析,计算睡眠深度、噪音影响等指标,并生成简洁报告,包括睡眠评分和个性化建议,如调整睡眠时间或环境设置。这部分数据通过ESP8266-01S Wi-Fi模块传输至华为云平台,便于后续处理。QT上位机作为数据显示终端,接收来自云端的睡眠数据,并以图表形式展示历史记录和实时分析,如心率变化曲线和噪音水平趋势,用户可通过界面查看详细报告和趋势分析,增强系统的交互性和实用性。整个设计注重数据流从采集到显示的完整性,确保用户体验 seamless。框架图+----------------+ +---------------------+ +----------------+ | 传感器层 | | 主控制器 | | 输出设备 | | | | | | | | - MAX30102 +----->| STM32F103C8T6 +----->| - WS2812 RGB | | (血氧心率) | | 最小系统核心板 | | 灯带 | | | | | | (柔光唤醒) | | - 声音传感器 +----->| (处理数据和控制) | | | | (环境噪音) | | | | | | | +---------------------+ +----------------+ +----------------+ | (通过杜邦线连接,可能经生物信号调理电路) | |数据 v +----------------+ | 通信模块 | | | | ESP8266-01S +----->华为云 (数据传输和存储) | Wi-Fi模块 | +----------------+ | |互联网 v +----------------+ | 上位机 | | | | QT应用程序 | | (数据显示和分析) | +----------------+ 系统总体设计本系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调整个智能睡眠监测与改善系统的运行。该系统旨在通过多传感器实时采集睡眠环境参数和人体生理数据,并基于这些数据智能调节环境因素以提升睡眠质量,同时生成详细的睡眠报告并通过QT上位机进行可视化展示。系统通过集成MAX30102血氧心率传感器和声音传感器来实现多参数监测。MAX30102传感器用于采集用户的心率和血氧饱和度等生理信号,而声音传感器则检测环境中的噪音水平,这些数据通过洞洞板焊接的生物信号调理电路进行初步处理,确保信号稳定性和准确性,再通过杜邦线连接至STM32控制器进行模数转换和实时分析。在环境改善方面,系统利用WS2812 RGB灯带提供柔光唤醒功能,根据监测到的睡眠阶段或预设算法,STM32控制器动态调节灯带的亮度、颜色和渐变模式,模拟自然光变化以促进用户苏醒或维持深度睡眠,从而优化睡眠环境。数据处理和通信部分依赖于ESP8266-01S Wi-Fi模块,该模块将STM32处理后的传感器数据上传至华为云平台,实现远程数据存储和初步分析。在云端,数据进一步处理以生成睡眠质量报告和改进建议,包括睡眠效率、噪音影响等指标,并通过网络反馈给本地系统或上位机。最后,QT上位机应用程序负责接收和显示睡眠数据分析图表及历史记录,用户可以通过图形界面直观查看趋势图、报告详情和环境调节日志,从而方便跟踪睡眠改善进度和做出相应调整。整个系统设计注重实用性和可靠性,确保各硬件组件协同工作,实现高效的睡眠监测与改善功能。系统功能总结功能实现方式多传感器监测睡眠环境参数和人体状态使用MAX30102血氧心率传感器监测生理参数,声音传感器监测环境噪音水平,生物信号调理电路处理信号智能调节环境因素改善睡眠质量使用WS2812 RGB灯带提供柔光唤醒功能生成睡眠质量报告和改进建议STM32主控制器处理数据,通过ESP8266 Wi-Fi模块上传至华为云,生成报告和建议QT上位机显示睡眠数据分析图表和历史记录开发QT上位机软件,通过Wi-Fi接收数据,显示分析图表和历史记录设计的各个功能模块描述STM32F103C8T6最小系统核心板作为整个系统的主控制器,负责协调各个功能模块的工作,处理传感器数据并执行逻辑控制。该芯片基于ARM Cortex-M3内核,具备足够的计算能力和外设接口来满足多任务处理需求。MAX30102血氧心率传感器用于监测用户的生理参数,通过I2C接口与主控制器通信。该传感器集成红光和红外LED、光电检测器及环境光抑制电路,能够实时采集心率及血氧饱和度数据,为睡眠质量评估提供关键生理指标。声音传感器模块持续监测环境中的噪音水平,将模拟信号转换为数字信号供主控制器分析。通过设定阈值判断环境噪音是否超出允许范围,为后续的环境调节提供数据依据。WS2812 RGB灯带作为环境调节执行单元,由主控制器通过PWM信号控制。系统可根据睡眠阶段智能调节灯光色温及亮度,例如在唤醒阶段模拟日出渐亮效果,或在入睡阶段提供柔和的暖色调光线辅助放松。ESP8266-01S Wi-Fi模块通过串口与STM32通信,负责将处理的睡眠数据上传至华为云平台。该模块支持AT指令集配置,实现无线数据传输功能,为远程监控和数据分析提供通信基础。洞洞板焊接的生物信号调理电路专门用于处理生理传感器采集的模拟信号,包含放大、滤波和电平转换电路。通过杜邦线连接各传感器模块,确保信号传输的可靠性,同时保持硬件布局的灵活性。上位机代码设计以下是基于C++和Qt开发的智能睡眠监测系统上位机代码设计。代码包含数据接收、图表显示、历史记录和报告生成功能:#include <QApplication> #include <QMainWindow> #include <QtCharts> #include <QChartView> #include <QLineSeries> #include <QValueAxis> #include <QDateTimeAxis> #include <QSplineSeries> #include <QScatterSeries> #include <QWebSocket> #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> #include <QFile> #include <QTextStream> #include <QDateTime> #include <QLabel> #include <QPushButton> #include <QVBoxLayout> #include <QHBoxLayout> #include <QGroupBox> #include <QMessageBox> #include <QHeaderView> #include <QTableWidget> class SleepMonitor : public QMainWindow { Q_OBJECT public: SleepMonitor(QWidget *parent = nullptr) : QMainWindow(parent) { setupUI(); setupWebSocket(); setupConnections(); } private slots: void onConnected() { statusLabel->setText("已连接到华为云"); ws->sendTextMessage("{\"command\":\"subscribe\"}"); } void onMessageReceived(const QString &message) { processData(message); } void generateReport() { QString report = generateSleepReport(); QMessageBox::information(this, "睡眠报告", report); // 保存报告到文件 QFile file(QDateTime::currentDateTime().toString("yyyy-MM-dd") + "_sleep_report.txt"); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << report; file.close(); } } private: void setupUI() { // 中央部件和布局 QWidget *centralWidget = new QWidget(this); QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget); // 左侧控制面板 QWidget *controlPanel = createControlPanel(); mainLayout->addWidget(controlPanel, 1); // 右侧图表区域 QWidget *chartArea = createChartArea(); mainLayout->addWidget(chartArea, 3); setCentralWidget(centralWidget); setWindowTitle("智能睡眠监测系统"); resize(1600, 900); } QWidget* createControlPanel() { QWidget *panel = new QWidget; QVBoxLayout *layout = new QVBoxLayout(panel); // 状态显示 QGroupBox *statusGroup = new QGroupBox("系统状态"); QVBoxLayout *statusLayout = new QVBoxLayout; statusLabel = new QLabel("未连接"); statusLayout->addWidget(statusLabel); statusGroup->setLayout(statusLayout); // 实时数据 QGroupBox *dataGroup = new QGroupBox("实时数据"); QFormLayout *dataLayout = new QFormLayout; heartRateLabel = new QLabel("--"); spo2Label = new QLabel("--"); noiseLabel = new QLabel("--"); dataLayout->addRow("心率 (bpm):", heartRateLabel); dataLayout->addRow("血氧 (%):", spo2Label); dataLayout->addRow("噪音 (dB):", noiseLabel); dataGroup->setLayout(dataLayout); // 报告生成按钮 reportButton = new QPushButton("生成睡眠报告"); layout->addWidget(statusGroup); layout->addWidget(dataGroup); layout->addWidget(reportButton); layout->addStretch(); return panel; } QWidget* createChartArea() { QWidget *chartWidget = new QWidget; QVBoxLayout *layout = new QVBoxLayout(chartWidget); // 创建选项卡 QTabWidget *tabWidget = new QTabWidget; // 心率图表 heartRateChart = new QChart; heartRateView = new QChartView(heartRateChart); heartRateView->setRenderHint(QPainter::Antialiasing); tabWidget->addTab(heartRateView, "心率监测"); // 血氧图表 spo2Chart = new QChart; spo2View = new QChartView(spo2Chart); spo2View->setRenderHint(QPainter::Antialiasing); tabWidget->addTab(spo2View, "血氧监测"); // 噪音图表 noiseChart = new QChart; noiseView = new QChartView(noiseChart); noiseView->setRenderHint(QPainter::Antialiasing); tabWidget->addTab(noiseView, "噪音监测"); // 历史数据表格 historyTable = new QTableWidget; historyTable->setColumnCount(4); historyTable->setHorizontalHeaderLabels({"时间", "心率", "血氧", "噪音"}); historyTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); tabWidget->addTab(historyTable, "历史数据"); layout->addWidget(tabWidget); return chartWidget; } void setupWebSocket() { ws = new QWebSocket; connect(ws, &QWebSocket::connected, this, &SleepMonitor::onConnected); connect(ws, &QWebSocket::textMessageReceived, this, &SleepMonitor::onMessageReceived); ws->open(QUrl("ws://your_huawei_cloud_address")); // 替换为实际地址 } void setupConnections() { connect(reportButton, &QPushButton::clicked, this, &SleepMonitor::generateReport); } void processData(const QString &message) { QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8()); if (!doc.isObject()) return; QJsonObject obj = doc.object(); double heartRate = obj["heart_rate"].toDouble(); double spo2 = obj["blood_oxygen"].toDouble(); double noise = obj["noise_level"].toDouble(); QDateTime timestamp = QDateTime::fromString(obj["timestamp"].toString(), Qt::ISODate); // 更新实时数据 heartRateLabel->setText(QString::number(heartRate)); spo2Label->setText(QString::number(spo2)); noiseLabel->setText(QString::number(noise)); // 更新图表 updateCharts(timestamp, heartRate, spo2, noise); // 更新历史表格 updateHistoryTable(timestamp, heartRate, spo2, noise); // 保存数据到文件 saveToCsv(timestamp, heartRate, spo2, noise); } void updateCharts(const QDateTime ×tamp, double heartRate, double spo2, double noise) { // 更新心率图表 static QLineSeries *heartRateSeries = new QLineSeries; heartRateSeries->append(timestamp.toMSecsSinceEpoch(), heartRate); if (heartRateSeries->count() > 100) heartRateSeries->remove(0); // 类似更新其他图表... } void updateHistoryTable(const QDateTime ×tamp, double heartRate, double spo2, double noise) { int row = historyTable->rowCount(); historyTable->insertRow(row); historyTable->setItem(row, 0, new QTableWidgetItem(timestamp.toString("hh:mm:ss"))); historyTable->setItem(row, 1, new QTableWidgetItem(QString::number(heartRate))); historyTable->setItem(row, 2, new QTableWidgetItem(QString::number(spo2))); historyTable->setItem(row, 3, new QTableWidgetItem(QString::number(noise))); } void saveToCsv(const QDateTime ×tamp, double heartRate, double spo2, double noise) { QFile file("sleep_data.csv"); bool exists = file.exists(); if (file.open(QIODevice::Append | QIODevice::Text)) { QTextStream stream(&file); if (!exists) stream << "timestamp,heart_rate,blood_oxygen,noise_level\n"; stream << timestamp.toString(Qt::ISODate) << "," << heartRate << "," << spo2 << "," << noise << "\n"; file.close(); } } QString generateSleepReport() { // 这里实现报告生成逻辑 return "睡眠质量分析报告\n\n" "平均心率: 72 bpm\n" "平均血氧: 96%\n" "噪音水平: 良好\n" "睡眠建议: 保持当前睡眠环境"; } private: QWebSocket *ws; QLabel *statusLabel; QLabel *heartRateLabel; QLabel *spo2Label; QLabel *noiseLabel; QPushButton *reportButton; QChart *heartRateChart; QChart *spo2Chart; QChart *noiseChart; QChartView *heartRateView; QChartView *spo2View; QChartView *noiseView; QTableWidget *historyTable; }; int main(int argc, char *argv[]) { QApplication app(argc, argv); SleepMonitor monitor; monitor.show(); return app.exec(); } #include "main.moc" 注意事项:需要配置Qt项目的.pro文件,添加网络和图表模块:QT += core gui network websockets charts华为云连接地址需要替换为实际地址图表初始化代码需要完善坐标轴和系列配置报告生成算法需要根据实际数据分析逻辑实现建议添加错误处理和重连机制数据存储可以考虑使用数据库替代CSV文件这个代码框架提供了基本的数据接收、显示和存储功能,实际使用时需要根据具体通信协议和数据格式进行调整。模块代码设计#include <stdint.h> // Register definitions for STM32F103 #define RCC_BASE 0x40021000 #define RCC_CR (*((volatile uint32_t *)(RCC_BASE + 0x00))) #define RCC_CFGR (*((volatile uint32_t *)(RCC_BASE + 0x04))) #define RCC_APB1ENR (*((volatile uint32_t *)(RCC_BASE + 0x1C))) #define RCC_APB2ENR (*((volatile uint32_t *)(RCC_BASE + 0x18))) #define GPIOA_BASE 0x40010800 #define GPIOA_CRL (*((volatile uint32_t *)(GPIOA_BASE + 0x00))) #define GPIOA_CRH (*((volatile uint32_t *)(GPIOA_BASE + 0x04))) #define GPIOA_IDR (*((volatile uint32_t *)(GPIOA_BASE + 0x08))) #define GPIOA_ODR (*((volatile uint32_t *)(GPIOA_BASE + 0x0C))) #define GPIOB_BASE 0x40010C00 #define GPIOB_CRL (*((volatile uint32_t *)(GPIOB_BASE + 0x00))) #define GPIOB_CRH (*((volatile uint32_t *)(GPIOB_BASE + 0x04))) #define GPIOB_IDR (*((volatile uint32_t *)(GPIOB_BASE + 0x08))) #define GPIOB_ODR (*((volatile uint32_t *)(GPIOB_BASE + 0x0C))) #define I2C1_BASE 0x40005400 #define I2C1_CR1 (*((volatile uint32_t *)(I2C1_BASE + 0x00))) #define I2C1_CR2 (*((volatile uint32_t *)(I2C1_BASE + 0x04))) #define I2C1_DR (*((volatile uint32_t *)(I2C1_BASE + 0x10))) #define I2C1_SR1 (*((volatile uint32_t *)(I2C1_BASE + 0x14))) #define I2C1_SR2 (*((volatile uint32_t *)(I2C1_BASE + 0x18))) #define I2C1_CCR (*((volatile uint32_t *)(I2C1_BASE + 0x1C))) #define I2C1_TRISE (*((volatile uint32_t *)(I2C1_BASE + 0x20))) #define ADC1_BASE 0x40012400 #define ADC1_SR (*((volatile uint32_t *)(ADC1_BASE + 0x00))) #define ADC1_CR1 (*((volatile uint32_t *)(ADC1_BASE + 0x04))) #define ADC1_CR2 (*((volatile uint32_t *)(ADC1_BASE + 0x08))) #define ADC1_SMPR2 (*((volatile uint32_t *)(ADC1_BASE + 0x10))) #define ADC1_SQR3 (*((volatile uint32_t *)(ADC1_BASE + 0x34))) #define ADC1_DR (*((volatile uint32_t *)(ADC1_BASE + 0x4C))) // Bit definitions #define RCC_APB2ENR_IOPAEN (1 << 2) #define RCC_APB2ENR_IOPBEN (1 << 3) #define RCC_APB2ENR_ADC1EN (1 << 9) #define RCC_APB1ENR_I2C1EN (1 << 21) // MAX30102 I2C address #define MAX30102_WRITE 0xAE #define MAX30102_READ 0xAF // System clock configuration to 72MHz using HSE void SystemClock_Config(void) { // Enable HSE RCC_CR |= (1 << 16); while (!(RCC_CR & (1 << 17))); // Configure PLL: HSE as source, multiplier x9 RCC_CFGR |= (1 << 16); RCC_CFGR |= (7 << 18); // Enable PLL RCC_CR |= (1 << 24); while (!(RCC_CR & (1 << 25))); // Set FLASH latency #define FLASH_ACR (*((volatile uint32_t *)0x40022000)) FLASH_ACR |= (2 << 0); // Switch to PLL RCC_CFGR |= (2 << 0); while ((RCC_CFGR & (3 << 2)) != (2 << 2)); } // I2C1 initialization void I2C1_Init(void) { RCC_APB2ENR |= RCC_APB2ENR_IOPBEN; RCC_APB1ENR |= RCC_APB1ENR_I2C1EN; // Configure PB6 (SCL) and PB7 (SDA) as alternate function open drain GPIOB_CRL &= ~(0xFF << 24); GPIOB_CRL |= (0x6 << 24) | (0x6 << 28); I2C1_CR2 = 36; I2C1_CCR = 180; I2C1_TRISE = 37; I2C1_CR1 |= (1 << 0); } // I2C write function void I2C1_Write(uint8_t deviceAddr, uint8_t regAddr, uint8_t data) { I2C1_CR1 |= (1 << 8); while (!(I2C1_SR1 & (1 << 0))); I2C1_DR = deviceAddr & 0xFE; while (!(I2C1_SR1 & (1 << 1))); (void) I2C1_SR2; I2C1_DR = regAddr; while (!(I2C1_SR1 & (1 << 7))); I2C1_DR = data; while (!(I2C1_SR1 & (1 << 7))); I2C1_CR1 |= (1 << 9); } // I2C read function uint8_t I2C1_Read(uint8_t deviceAddr, uint8_t regAddr) { I2C1_CR1 |= (1 << 8); while (!(I2C1_SR1 & (1 << 0))); I2C1_DR = deviceAddr & 0xFE; while (!(I2C1_SR1 & (1 << 1))); (void) I2C1_SR2; I2C1_DR = regAddr; while (!(I2C1_SR1 & (1 << 7))); I2C1_CR1 |= (1 << 8); while (!(I2C1_SR1 & (1 << 0))); I2C1_DR = deviceAddr | 0x01; while (!(I2C1_SR1 & (1 << 1))); (void) I2C1_SR2; I2C1_CR1 &= ~(1 << 10); while (!(I2C1_SR1 & (1 << 6))); uint8_t data = I2C1_DR; I2C1_CR1 |= (1 << 9); return data; } // MAX30102 initialization void MAX30102_Init(void) { I2C1_Write(MAX30102_WRITE, 0x09, 0x1F); I2C1_Write(MAX30102_WRITE, 0x0A, 0x1F); I2C1_Write(MAX30102_WRITE, 0x02, 0x40); I2C1_Write(MAX30102_WRITE, 0x03, 0x00); } // Read MAX30102 FIFO data void MAX30102_Read_FIFO(uint32_t *red, uint32_t *ir) { *red = I2C1_Read(MAX30102_READ, 0x07) << 16; *red |= I2C1_Read(MAX30102_READ, 0x08) << 8; *red |= I2C1_Read(MAX30102_READ, 0x09); *ir = I2C1_Read(MAX30102_READ, 0x0A) << 16; *ir |= I2C1_Read(MAX30102_READ, 0x0B) << 8; *ir |= I2C1_Read(MAX30102_READ, 0x0C); } // ADC1 initialization for sound sensor void ADC1_Init(void) { RCC_APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN; GPIOA_CRL &= ~(0xF << 0); GPIOA_CRL |= (0x0 << 0); ADC1_CR2 |= (1 << 0); ADC1_CR2 |= (1 << 1); ADC1_CR2 |= (1 << 2); while (ADC1_CR2 & (1 << 2)); ADC1_SMPR2 |= (7 << 0); ADC1_SQR3 = 0; ADC1_CR2 |= (1 << 3); } // Read ADC value uint16_t ADC1_Read(void) { while (!(ADC1_SR & (1 << 1))); return ADC1_DR; } // Main function int main(void) { SystemClock_Config(); I2C1_Init(); ADC1_Init(); MAX30102_Init(); while (1) { uint32_t red_value, ir_value; MAX30102_Read_FIFO(&red_value, &ir_value); uint16_t sound_level = ADC1_Read(); } } 项目核心代码#include "stm32f10x.h" // 假设其他模块已经编写好,声明外部函数 extern void MAX30102_Init(void); extern void MAX30102_ReadData(uint16_t *heartRate, uint16_t *spo2); extern void SoundSensor_Init(void); extern uint16_t SoundSensor_Read(void); extern void WS2812_Init(void); extern void WS2812_SetColor(uint8_t red, uint8_t green, uint8_t blue); extern void ESP8266_Init(void); extern void ESP8266_SendData(const char *data); extern void GenerateSleepReport(void); // 系统时钟初始化函数,配置为72MHz void SystemClock_Init(void) { // 启用内部高速时钟(HSI) RCC->CR |= RCC_CR_HSION; while (!(RCC->CR & RCC_CR_HSIRDY)); // 配置FLASH预取指和延迟 FLASH->ACR |= FLASH_ACR_PRFTBE; FLASH->ACR &= ~FLASH_ACR_LATENCY; FLASH->ACR |= FLASH_ACR_LATENCY_2; // 2等待状态,用于72MHz // 配置PLL:HSI/2 * 9 = 36MHz * 2 = 72MHz RCC->CFGR &= ~RCC_CFGR_PLLSRC; RCC->CFGR |= RCC_CFGR_PLLMULL9; // PLL倍频9倍 RCC->CFGR &= ~RCC_CFGR_PLLXTPRE; // HSE不分频 // 启用PLL RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); // 切换系统时钟到PLL RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 设置AHB、APB1、APB2分频 RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // AHB不分频,72MHz RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1分频2,36MHz RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; // APB2不分频,72MHz } // GPIO初始化 void GPIO_Init(void) { // 启用GPIOA、GPIOB、GPIOC时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN; // 配置WS2812引脚(假设使用PA1作为数据线) GPIOA->CRL &= ~GPIO_CRL_CNF1; GPIOA->CRL |= GPIO_CRL_MODE1; // 输出模式,最大速度50MHz // 配置声音传感器ADC引脚(PA0为模拟输入) GPIOA->CRL &= ~GPIO_CRL_CNF0; GPIOA->CRL &= ~GPIO_CRL_MODE0; // 模拟输入模式 // 配置I2C引脚(PB6: SCL, PB7: SDA) // SCL (PB6): 开漏输出, alternate function GPIOB->CRL &= ~GPIO_CRL_CNF6; GPIOB->CRL |= GPIO_CRL_CNF6_1; // 复用开漏输出 GPIOB->CRL |= GPIO_CRL_MODE6; // 输出模式,50MHz // SDA (PB7): 开漏输出, alternate function GPIOB->CRL &= ~GPIO_CRL_CNF7; GPIOB->CRL |= GPIO_CRL_CNF7_1; // 复用开漏输出 GPIOB->CRL |= GPIO_CRL_MODE7; // 输出模式,50MHz // 配置UART引脚(PA2: TX, PA3: RX for ESP8266) // TX (PA2): 复用推挽输出 GPIOA->CRL &= ~GPIO_CRL_CNF2; GPIOA->CRL |= GPIO_CRL_CNF2_1; // 复用推挽输出 GPIOA->CRL |= GPIO_CRL_MODE2; // 输出模式,50MHz // RX (PA3): 浮空输入 GPIOA->CRL &= ~GPIO_CRL_CNF3; GPIOA->CRL |= GPIO_CRL_CNF3_0; // 浮空输入 GPIOA->CRL &= ~GPIO_CRL_MODE3; // 输入模式 } // I2C初始化(I2C1) void I2C_Init(void) { // 启用I2C1时钟 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // 配置I2C时序,假设100kHz标准模式 I2C1->CR2 = 36; // APB1时钟频率为36MHz,设置CR2为36 I2C1->CCR = 180; // CCR = APB1时钟 / (2 * I2C速度) = 36MHz / (2 * 100kHz) = 180 I2C1->TRISE = 37; // TRISE = (APB1时钟 / 1000000) + 1 = 36 + 1 = 37 // 启用I2C1 I2C1->CR1 |= I2C_CR1_PE; } // UART初始化(USART1 for ESP8266) void UART_Init(void) { // 启用USART1时钟 RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // 配置波特率:115200, APB2时钟72MHz USART1->BRR = 72000000 / 115200; // 计算BRR值 // 启用发送和接收 USART1->CR1 |= USART_CR1_TE | USART_CR1_RE; // 启用USART1 USART1->CR1 |= USART_CR1_UE; } // ADC初始化(ADC1 on PA0) void ADC_Init(void) { // 启用ADC1时钟 RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // 配置ADC预分频,ADC时钟最大14MHz,APB2/6=12MHz RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6; // 配置ADC1: 单次转换,通道0(PA0) ADC1->SQR1 = 0; // 1 conversion ADC1->SQR3 = 0; // channel 0 ADC1->SMPR2 = 0; // sampling time 1.5 cycles // 启用ADC1 ADC1->CR2 |= ADC_CR2_ADON; } // 主函数 int main(void) { // 初始化系统时钟 SystemClock_Init(); // 初始化GPIO GPIO_Init(); // 初始化外设 I2C_Init(); UART_Init(); ADC_Init(); // 初始化传感器模块 MAX30102_Init(); SoundSensor_Init(); WS2812_Init(); ESP8266_Init(); // 变量定义 uint16_t heartRate = 0; uint16_t spo2 = 0; uint16_t noiseLevel = 0; char dataBuffer[100]; // 主循环 while (1) { // 读取传感器数据 MAX30102_ReadData(&heartRate, &spo2); noiseLevel = SoundSensor_Read(); // 智能调节:根据噪音水平调整RGB灯带(示例逻辑) if (noiseLevel > 500) { // 假设噪音阈值 WS2812_SetColor(0, 0, 255); // 蓝色光 } else { WS2812_SetColor(255, 0, 0); // 红色光 } // 生成数据字符串并发送 via ESP8266 snprintf(dataBuffer, sizeof(dataBuffer), "HR:%d,SpO2:%d,Noise:%d", heartRate, spo2, noiseLevel); ESP8266_SendData(dataBuffer); // 生成睡眠报告(假设函数内部处理数据) GenerateSleepReport(); // 延时一段时间,例如5秒 for (volatile int i = 0; i < 1000000; i++); // 简单延时 } } 总结本系统基于STM32F103C8T6微控制器,成功设计并实现了一个智能睡眠监测与改善系统,通过集成多传感器实时采集睡眠环境参数和人体生理数据,旨在提升用户的睡眠质量。系统具备监测、调节和报告功能,为用户提供全面的睡眠健康管理。硬件组成包括STM32主控制器、MAX30102血氧心率传感器、声音传感器、WS2812 RGB灯带、ESP8266 Wi-Fi模块以及生物信号调理电路,这些组件通过洞洞板焊接和杜邦线连接,确保了稳定可靠的数据采集和处理。MAX30102监测心率和血氧水平,声音传感器检测环境噪音,而WS2812灯带则提供柔光唤醒功能,增强用户体验。系统能够智能调节环境因素,例如根据监测数据自动调整灯带光线以模拟自然唤醒,并通过ESP8266模块将数据上传至华为云,实现远程监控和数据分析。此外,系统生成详细的睡眠质量报告和改进建议,帮助用户了解睡眠状况并采取相应措施。QT上位机软件作为用户界面,显示睡眠数据的分析图表和历史记录,提供直观的可视化工具,便于用户跟踪睡眠趋势和评估改善效果。整体上,该系统结合了硬件和软件的协同工作,实现了高效的睡眠监测与个性化改善。总之,该智能睡眠监测与改善系统设计合理、功能完备,具有较高的实用性和推广价值,能够有效辅助用户改善睡眠健康,提升生活质量。
  • [技术干货] 基于STM32设计的智能水产养殖监控系统
    项目开发背景随着全球人口的增长和对蛋白质需求的不断提升,水产养殖业已成为重要的食物来源之一,尤其在亚洲地区,养殖业规模持续扩大。然而,传统的水产养殖方式往往依赖人工经验进行水质监测和设备控制,这种方法效率低下、易受人为因素影响,且难以实现实时精准管理。水质参数如温度、pH值和溶解氧含量的波动会直接影响鱼类生长健康和养殖效益,例如溶解氧不足可能导致鱼类窒息死亡,pH值异常会引发疾病爆发。因此,开发一种智能化的监控系统来实时监测和自动调节养殖环境,成为提升养殖效率和可持续性的关键需求。基于微控制器和传感器技术的智能系统能够有效解决这些挑战,通过自动化数据采集和设备控制,减少人力成本,提高响应速度。STM32系列微控制器以其高性能、低功耗和丰富的外设接口,成为此类应用的理想选择,能够集成多种传感器并实现复杂逻辑控制。本项目采用STM32F103C8T6核心板作为主控制器,结合DS18B20、PH-4502C和DO传感器实时采集水质数据,同时利用ESP32-CAM模块进行水下影像监控,以全面掌握鱼类活动状况。此外,通过QT开发的上位机软件提供直观的数据可视化界面,使养殖人员能够远程监控趋势并及时调整策略,从而推动水产养殖向智能化、精细化方向发展。设计实现的功能(1)实时监测水体温度、pH值、溶解氧含量(2)自动控制增氧机、投饵机等设备运行(3)水下摄像头采集鱼类活动影像(4)QT上位机显示水质参数变化曲线和设备运行状态项目硬件模块组成(1)STM32F103C8T6最小系统核心板作为主控制器(2)DS18B20防水温度传感器监测水温(3)PH-4502C pH传感器监测酸碱度(4)DO溶解氧传感器模块监测溶氧量(5)ESP32-CAM模块实现水下影像采集(6)洞洞板焊接信号处理电路,杜邦线连接传感器设计意义基于STM32设计的智能水产养殖监控系统通过集成微控制器和多种传感器,实现了对水产养殖环境的精细化监控,有效提升了养殖管理的智能化水平,推动传统养殖方式向现代自动化转型,为水产行业的发展提供了技术支撑。实时监测水体温度、pH值和溶解氧含量,能够及时捕捉水质变化,预防因环境异常导致的鱼类应激反应或疾病爆发,确保养殖生物的健康生长,从而提高成活率和产品质量,减少经济损失。自动控制增氧机、投饵机等设备,根据监测数据智能调节运行状态,优化氧气和饲料投放,避免资源浪费,降低人工干预需求,节省人力成本,同时提升养殖效率和生产稳定性。水下摄像头采集鱼类活动影像,并结合QT上位机显示水质参数变化曲线和设备运行状态,为养殖者提供直观、全面的监控界面,便于远程观察和数据分析,增强管理决策的准确性和便捷性,支持可持续养殖实践。设计思路本系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调整个智能水产养殖监控系统的运行。系统通过集成多种传感器和执行器,实现水质参数的实时监测和设备自动控制,同时结合水下影像采集和上位机显示,为养殖管理提供全面支持。硬件上采用洞洞板焊接必要的信号处理电路,并使用杜邦线连接各传感器模块,确保系统的可靠性和扩展性。传感器数据采集部分,DS18B20防水温度传感器通过1-Wire协议与STM32通信,实时读取水温数据;PH-4502C pH传感器和DO溶解氧传感器模块输出模拟信号,通过STM32内置的ADC通道进行转换,获取酸碱度和溶氧量数值。STM32固件中需配置ADC采样频率和校准参数,以确保数据准确性,并定期读取传感器值,进行滤波和处理以减少噪声影响。设备控制方面,STM32通过GPIO输出信号驱动继电器模块,从而控制增氧机和投饵机等设备的运行。控制逻辑基于预设的水质参数阈值,例如当溶解氧含量低于设定值时自动启动增氧机,或根据时间 schedule 控制投饵机工作。STM32实时监控传感器数据,并执行相应的控制算法,实现自动化管理,减少人工干预。水下影像采集由ESP32-CAM模块独立处理,该模块具备WiFi功能,可直接将采集到的鱼类活动影像流传输到局域网中。QT上位机通过网络协议(如TCP/IP)接收影像数据,并在界面中显示;同时,STM32与ESP32-CAM之间无需直接数据交互,但系统设计需确保影像传输的同步性和稳定性,以支持实时监控。通信与上位机交互部分,STM32通过UART串口与PC端的QT应用程序进行数据通信,定期发送水质参数(温度、pH值、溶解氧)和设备状态信息。QT上位机程序解析串口数据,动态绘制参数变化曲线,并显示设备运行状态,为用户提供直观的监控界面。数据协议采用自定义格式,包含传感器读数和时间戳,以确保数据传输的完整性和可读性。信号处理电路在洞洞板上实现,主要包括运放电路和滤波电路,用于调整pH和溶解氧传感器的模拟输出电平,使其匹配STM32的ADC输入范围(0-3.3V)。此外,电源管理部分提供稳定的5V或3.3V供电,确保传感器和模块的正常工作,而杜邦线连接方式便于调试和维护。框架图+-----------------------------+ | 智能水产养殖监控系统 | +-----------------------------+ | v +-----------------------------------------------+ | STM32F103C8T6主控制器 | | (通过洞洞板信号处理电路连接传感器) | +-----------------------------------------------+ | | | | | | | | v v v v +--------+ +-----------+ +----------+ +-----------+ |DS18B20 | |PH-4502C | |DO溶解氧 | |ESP32-CAM | |温度传感器| |pH传感器 | |传感器模块| |水下摄像头 | +--------+ +-----------+ +----------+ +-----------+ | | | | | | | | v v v v +-----------------------------------------------+ | 执行设备控制 | | (增氧机、投饵机等) | +-----------------------------------------------+ | v +-----------------------------------------------+ | QT上位机软件 | | (显示水质参数曲线和设备状态) | +-----------------------------------------------+ 系统总体设计该系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调整个监控系统的运行。主控制器通过集成多个传感器接口和控制输出,实现数据采集、处理和设备驱动功能,确保系统稳定高效地工作。水温监测采用DS18B20防水温度传感器,该传感器通过单总线协议与STM32连接,实时采集水体温度数据。pH值监测使用PH-4502C传感器,其模拟输出信号经过信号处理电路调理后,由STM32的ADC模块转换为数字值。溶解氧含量通过专门的DO传感器模块监测,同样通过ADC采集模拟信号,并进行校准处理以获取准确读数。自动控制部分基于传感器数据,STM32通过GPIO输出控制信号驱动继电器模块,从而操作增氧机和投饵机等设备。例如,当溶解氧含量低于设定阈值时,主控制器自动启动增氧机;投饵机则可根据定时或条件触发模式运行,确保养殖过程的自动化。影像采集由ESP32-CAM模块实现,该模块独立工作于水下环境,通过Wi-Fi将采集的鱼类活动视频流传输到上位机。STM32与ESP32-CAM之间通过串口或网络协议进行协调,确保影像数据与水质参数同步。QT上位机软件运行于PC端,通过串口或网络接收STM32发送的水质参数数据,实时显示温度、pH值和溶解氧含量的变化曲线,同时展示设备运行状态如增氧机开关状态。上位机还提供历史数据查询和报警功能,增强系统的可视化和管理能力。硬件连接方面,传感器信号通过洞洞板焊接的信号处理电路进行滤波和放大,使用杜邦线将传感器与STM32核心板可靠连接,确保信号传输的稳定性和抗干扰能力。整个系统设计注重实用性和可靠性,适用于水产养殖的实际环境。系统功能总结功能描述硬件组件简要说明实时水温监测DS18B20防水温度传感器通过STM32读取水体温度数据实时pH值监测PH-4502C pH传感器监测水体酸碱度,STM32处理信号实时溶解氧监测DO溶解氧传感器模块监测溶氧量,STM32读取数据自动设备控制STM32输出控制接口控制增氧机、投饵机等设备运行水下影像采集ESP32-CAM模块实现鱼类活动视频采集数据可视化显示QT上位机软件显示水质参数变化曲线和设备运行状态核心控制与处理STM32F103C8T6核心板作为主控制器,协调所有监测和控制功能信号处理与连接洞洞板焊接电路、杜邦线用于传感器信号调理和硬件连接设计的各个功能模块描述STM32F103C8T6最小系统核心板作为主控制器,负责整个系统的协调与控制,通过内置的ADC、GPIO和通信接口(如UART)采集传感器数据、处理逻辑判断,并输出控制信号给外部设备,同时与上位机进行数据交互,确保系统实时运行。DS18B20防水温度传感器用于监测水体温度,采用单总线数字协议与STM32连接,提供高精度的温度测量,STM32定期读取传感器数据并进行处理,实现水温的实时监控。PH-4502C pH传感器监测水体的酸碱度,输出模拟电压信号,通过STM32的ADC模块进行模数转换,STM32根据校准曲线计算pH值,并将数据用于系统判断和上传。DO溶解氧传感器模块监测水中的溶解氧含量,通常输出模拟或数字信号,STM32通过ADC或数字接口读取数据,经过处理后用于控制决策和数据记录。ESP32-CAM模块负责水下影像采集,通过其内置的摄像头和Wi-Fi功能,捕获鱼类活动视频或图像,并将数据传输到本地网络或直接发送给上位机,STM32可能通过串口或网络协议与ESP32-CAM协调触发采集。信号处理电路基于洞洞板焊接,包括必要的运算放大器、滤波器和电平转换电路,用于处理传感器输出的模拟信号,确保信号稳定和噪声抑制,并通过杜邦线将处理后的信号连接到STM32的相应引脚。自动控制部分涉及增氧机和投饵机等设备,STM32根据传感器数据(如溶解氧低于阈值)生成控制信号,通过GPIO输出驱动继电器或MOSFET电路,从而控制设备的开关运行,实现自动化管理。QT上位机软件运行在PC端,通过串口通信接收STM32发送的水质参数和设备状态数据,以图形化界面显示温度、pH值和溶解氧的变化曲线,以及设备运行状态,为用户提供直观的监控和数据分析功能。上位机代码设计以下是基于Qt的智能水产养殖监控系统上位机代码设计,使用C++实现:#include <QApplication> #include <QMainWindow> #include <QChartView> #include <QLineSeries> #include <QValueAxis> #include <QDateTimeAxis> #include <QSerialPort> #include <QSerialPortInfo> #include <QLabel> #include <QPushButton> #include <QHBoxLayout> #include <QVBoxLayout> #include <QWidget> #include <QMessageBox> #include <QTimer> #include <QtCharts> QT_CHARTS_USE_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) { setupUI(); setupSerial(); setupCharts(); } private slots: void onOpenSerial() { if(serial->isOpen()) { serial->close(); btnSerial->setText("打开串口"); } else { serial->setPortName(cbPort->currentText()); serial->setBaudRate(115200); if(serial->open(QIODevice::ReadWrite)) { btnSerial->setText("关闭串口"); } else { QMessageBox::critical(this, "错误", "无法打开串口"); } } } void readData() { QByteArray data = serial->readAll(); processData(data); } private: void setupUI() { // 串口控制区域 QWidget *controlWidget = new QWidget; QHBoxLayout *controlLayout = new QHBoxLayout; cbPort = new QComboBox; foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) { cbPort->addItem(info.portName()); } btnSerial = new QPushButton("打开串口"); controlLayout->addWidget(new QLabel("串口:")); controlLayout->addWidget(cbPort); controlLayout->addWidget(btnSerial); controlLayout->addStretch(); controlWidget->setLayout(controlLayout); // 状态显示 QWidget *statusWidget = new QWidget; QHBoxLayout *statusLayout = new QHBoxLayout; statusLayout->addWidget(new QLabel("温度:")); lblTemp = new QLabel("0.0°C"); statusLayout->addWidget(lblTemp); statusLayout->addWidget(new QLabel("pH:")); lblPh = new QLabel("0.0"); statusLayout->addWidget(lblPh); statusLayout->addWidget(new QLabel("溶解氧:")); lblDo = new QLabel("0.0mg/L"); statusLayout->addWidget(lblDo); statusLayout->addWidget(new QLabel("增氧机:")); lblOxy = new QLabel("关"); statusLayout->addWidget(lblOxy); statusLayout->addWidget(new QLabel("投饵机:")); lblFeed = new QLabel("关"); statusLayout->addWidget(lblFeed); statusWidget->setLayout(statusLayout); // 图表区域 tempChart = new QChart; phChart = new QChart; doChart = new QChart; QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(controlWidget); mainLayout->addWidget(statusWidget); mainLayout->addWidget(new QChartView(tempChart)); mainLayout->addWidget(new QChartView(phChart)); mainLayout->addWidget(new QChartView(doChart)); QWidget *centralWidget = new QWidget; centralWidget->setLayout(mainLayout); setCentralWidget(centralWidget); connect(btnSerial, &QPushButton::clicked, this, &MainWindow::onOpenSerial); } void setupSerial() { serial = new QSerialPort(this); connect(serial, &QSerialPort::readyRead, this, &MainWindow::readData); } void setupCharts() { // 温度图表 tempSeries = new QLineSeries; tempChart->addSeries(tempSeries); tempChart->setTitle("温度变化曲线"); QValueAxis *axisX = new QValueAxis; axisX->setTitleText("时间"); QValueAxis *axisY = new QValueAxis; axisY->setTitleText("温度(°C)"); tempChart->addAxis(axisX, Qt::AlignBottom); tempChart->addAxis(axisY, Qt::AlignLeft); tempSeries->attachAxis(axisX); tempSeries->attachAxis(axisY); // pH图表(类似设置) // 溶解氧图表(类似设置) } void processData(const QByteArray &data) { QString str = QString::fromUtf8(data); QStringList lines = str.split('\n'); foreach (const QString &line, lines) { if(line.startsWith("T:")) { float temp = line.mid(2).toFloat(); lblTemp->setText(QString::number(temp) + "°C"); tempSeries->append(QDateTime::currentDateTime().toMSecsSinceEpoch(), temp); } else if(line.startsWith("PH:")) { float ph = line.mid(3).toFloat(); lblPh->setText(QString::number(ph)); // 更新pH图表 } else if(line.startsWith("DO:")) { float dO = line.mid(3).toFloat(); lblDo->setText(QString::number(dO) + "mg/L"); // 更新溶解氧图表 } else if(line.startsWith("OXY:")) { lblOxy->setText(line.mid(4) == "1" ? "开" : "关"); } else if(line.startsWith("FEED:")) { lblFeed->setText(line.mid(5) == "1" ? "开" : "关"); } } } QComboBox *cbPort; QPushButton *btnSerial; QSerialPort *serial; QLabel *lblTemp; QLabel *lblPh; QLabel *lblDo; QLabel *lblOxy; QLabel *lblFeed; QChart *tempChart; QChart *phChart; QChart *doChart; QLineSeries *tempSeries; QLineSeries *phSeries; QLineSeries *doSeries; }; int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } #include "main.moc" 注意:实际使用时需要:在.pro文件中添加:QT += serialport charts根据实际通信协议调整数据处理逻辑完善pH和溶解氧的图表初始化代码添加错误处理和数据验证根据需要调整UI布局和样式实现数据保存和历史查询功能添加设备控制按钮和相应的串口命令发送功能模块代码设计#include "stdint.h" // Register definitions for STM32F103C8T6 #define RCC_APB2ENR (*(volatile uint32_t *)0x40021018) #define GPIOA_CRL (*(volatile uint32_t *)0x40010800) #define GPIOA_CRH (*(volatile uint32_t *)0x40010804) #define GPIOA_IDR (*(volatile uint32_t *)0x40010808) #define GPIOA_ODR (*(volatile uint32_t *)0x4001080C) #define GPIOB_CRL (*(volatile uint32_t *)0x40010C00) #define GPIOB_CRH (*(volatile uint32_t *)0x40010C04) #define GPIOB_ODR (*(volatile uint32_t *)0x40010C0C) #define ADC1_SR (*(volatile uint32_t *)0x40012400) #define ADC1_CR1 (*(volatile uint32_t *)0x40012404) #define ADC1_CR2 (*(volatile uint32_t *)0x40012408) #define ADC1_SMPR2 (*(volatile uint32_t *)0x40012410) #define ADC1_DR (*(volatile uint32_t *)0x4001244C) #define USART1_SR (*(volatile uint32_t *)0x40013800) #define USART1_DR (*(volatile uint32_t *)0x40013804) #define USART1_BRR (*(volatile uint32_t *)0x40013808) #define USART1_CR1 (*(volatile uint32_t *)0x4001380C) // Pin definitions #define DS18B20_PIN 0 // PA0 for DS18B20 #define PH_SENSOR_PIN 1 // PA1 for pH sensor (ADC1_IN1) #define DO_SENSOR_PIN 2 // PA2 for DO sensor (ADC1_IN2) #define OXYGEN_PIN 0 // PB0 for oxygen machine control #define FEEDER_PIN 1 // PB1 for feeder control // UART for ESP32-CAM #define USART1_TX_PIN 9 // PA9 #define USART1_RX_PIN 10 // PA10 // Function prototypes void SystemInit(void); void GPIO_Init(void); void ADC1_Init(void); void USART1_Init(void); void DS18B20_Init(void); float DS18B20_ReadTemp(void); uint16_t ADC_Read(uint8_t channel); float Read_pH(void); float Read_DO(void); void USART1_SendChar(char c); void USART1_SendString(char *str); void Control_Devices(float temp, float ph, float do_value); // System clock initialization void SystemInit(void) { // Enable HSI (8MHz) RCC_APB2ENR |= (1 << 0); // Enable AFIO RCC_APB2ENR |= (1 << 2); // Enable GPIOA RCC_APB2ENR |= (1 << 3); // Enable GPIOB RCC_APB2ENR |= (1 << 9); // Enable ADC1 RCC_APB2ENR |= (1 << 14); // Enable USART1 // Configure system clock to 72MHz using HSI and PLL // HSI = 8MHz, PLL source = HSI/2 = 4MHz, PLL mul = 18 -> 72MHz volatile uint32_t *RCC_CR = (volatile uint32_t *)0x40021000; volatile uint32_t *RCC_CFGR = (volatile uint32_t *)0x40021004; volatile uint32_t *FLASH_ACR = (volatile uint32_t *)0x40022000; *RCC_CR |= (1 << 0); // Enable HSI while (!(*RCC_CR & (1 << 1))); // Wait for HSI ready *FLASH_ACR |= (1 << 1) | (1 << 0); // Set flash latency to 2 wait states *RCC_CFGR |= (0b10 << 18); // PLL multiplication factor 18 *RCC_CFGR |= (1 << 16); // PLL source HSI/2 *RCC_CR |= (1 << 24); // Enable PLL while (!(*RCC_CR & (1 << 25))); // Wait for PLL ready *RCC_CFGR |= (0b10 << 0); // Switch to PLL while ((*RCC_CFGR & (0b11 << 2)) != (0b10 << 2)); // Wait for PLL as system clock } // GPIO initialization void GPIO_Init(void) { // Configure PA0 for DS18B20 (open drain output for 1-Wire) GPIOA_CRL &= ~(0xF << (DS18B20_PIN * 4)); GPIOA_CRL |= (0x3 << (DS18B20_PIN * 4)); // Output mode, max speed 50MHz GPIOA_ODR |= (1 << DS18B20_PIN); // Set high for open drain // Configure PA1 and PA2 as analog inputs for pH and DO sensors GPIOA_CRL &= ~(0xF << (PH_SENSOR_PIN * 4)); GPIOA_CRL &= ~(0xF << (DO_SENSOR_PIN * 4)); GPIOA_CRL |= (0x0 << (PH_SENSOR_PIN * 4)); // Analog mode GPIOA_CRL |= (0x0 << (DO_SENSOR_PIN * 4)); // Analog mode // Configure PB0 and PB1 as output for control devices GPIOB_CRL &= ~(0xF << (OXYGEN_PIN * 4)); GPIOB_CRL &= ~(0xF << (FEEDER_PIN * 4)); GPIOB_CRL |= (0x3 << (OXYGEN_PIN * 4)); // Output mode, max speed 50MHz GPIOB_CRL |= (0x3 << (FEEDER_PIN * 4)); // Output mode, max speed 50MHz GPIOB_ODR &= ~(1 << OXYGEN_PIN); // Initially off GPIOB_ODR &= ~(1 << FEEDER_PIN); // Initially off // Configure PA9 and PA10 for USART1 GPIOA_CRH &= ~(0xF << ((USART1_TX_PIN - 8) * 4)); GPIOA_CRH &= ~(0xF << ((USART1_RX_PIN - 8) * 4)); GPIOA_CRH |= (0xB << ((USART1_TX_PIN - 8) * 4)); // Alternate function output push-pull GPIOA_CRH |= (0x4 << ((USART1_RX_PIN - 8) * 4)); // Input with pull-up/pull-down GPIOA_ODR |= (1 << USART1_RX_PIN); // Enable pull-up } // ADC1 initialization void ADC1_Init(void) { ADC1_CR2 |= (1 << 0); // Enable ADC1 ADC1_CR2 |= (1 << 1); // Continuous conversion mode ADC1_CR2 |= (1 << 20); // External trigger conversion mode ADC1_SMPR2 |= (0b111 << (PH_SENSOR_PIN * 3)); // Sample time 239.5 cycles for PA1 ADC1_SMPR2 |= (0b111 << (DO_SENSOR_PIN * 3)); // Sample time 239.5 cycles for PA2 ADC1_CR2 |= (1 << 22); // Start calibration while (ADC1_CR2 & (1 << 22)); // Wait for calibration complete ADC1_CR2 |= (1 << 0); // Ensure ADON is set } // USART1 initialization void USART1_Init(void) { USART1_BRR = 0x0341; // 72MHz / 9600 = 0x0341 (approx) USART1_CR1 |= (1 << 13); // Enable USART1 USART1_CR1 |= (1 << 3); // Enable transmitter USART1_CR1 |= (1 << 2); // Enable receiver } // DS18B20 initialization void DS18B20_Init(void) { // Already configured in GPIO_Init } // DS18B20 reset function uint8_t DS18B20_Reset(void) { uint8_t presence = 0; GPIOA_CRL &= ~(0xF << (DS18B20_PIN * 4)); GPIOA_CRL |= (0x3 << (DS18B20_PIN * 4)); // Output mode GPIOA_ODR &= ~(1 << DS18B20_PIN); // Pull low for (volatile int i = 0; i < 480; i++); // Delay 480us GPIOA_ODR |= (1 << DS18B20_PIN); // Release bus for (volatile int i = 0; i < 60; i++); // Delay 60us GPIOA_CRL &= ~(0xF << (DS18B20_PIN * 4)); GPIOA_CRL |= (0x4 << (DS18B20_PIN * 4)); // Input mode for (volatile int i = 0; i < 10; i++); // Delay 10us if (!(GPIOA_IDR & (1 << DS18B20_PIN))) { presence = 1; // Device present } for (volatile int i = 0; i < 420; i++); // Delay 420us return presence; } // DS18B20 write byte void DS18B20_WriteByte(uint8_t data) { for (int i = 0; i < 8; i++) { GPIOA_CRL &= ~(0xF << (DS18B20_PIN * 4)); GPIOA_CRL |= (0x3 << (DS18B20_PIN * 4)); // Output mode GPIOA_ODR &= ~(1 << DS18B20_PIN); // Pull low for (volatile int j = 0; j < 5; j++); // Delay 5us if (data & 0x01) { GPIOA_ODR |= (1 << DS18B20_PIN); // Release bus if bit is 1 } for (volatile int j = 0; j < 60; j++); // Delay 60us GPIOA_ODR |= (1 << DS18B20_PIN); // Release bus data >>= 1; } } // DS18B20 read byte uint8_t DS18B20_ReadByte(void) { uint8 data = 0; for (int i = 0; i < 8; i++) { data >>= 1; GPIOA_CRL &= ~(0xF << (DS18B20_PIN * 4)); GPIOA_CRL |= (0x3 << (DS18B20_PIN * 4)); // Output mode GPIOA_ODR &= ~(1 << DS18B20_PIN); // Pull low for (volatile int j = 0; j < 2; j++); // Delay 2us GPIOA_ODR |= (1 << DS18B20_PIN); // Release bus for (volatile int j = 0; j < 8; j++); // Delay 8us GPIOA_CRL &= ~(0xF << (DS18B20_PIN * 4)); GPIOA_CRL |= (0x4 << (DS18B20_PIN * 4)); // Input mode if (GPIOA_IDR & (1 << DS18B20_PIN)) { data |= 0x80; } for (volatile int j = 0; j < 50; j++); // Delay 50us } return data; } // Read temperature from DS18B20 float DS18B20_ReadTemp(void) { if (!DS18B20_Reset()) return -1000.0; // Error if no device DS18B20_WriteByte(0xCC); // Skip ROM DS18B20_WriteByte(0x44); // Convert T while (DS18B20_ReadByte() == 0xFF); // Wait for conversion DS18B20_Reset(); DS18B20_WriteByte(0xCC); // Skip ROM DS18B20_WriteByte(0xBE); // Read scratchpad uint8_t temp_l = DS18B20_ReadByte(); uint8_t temp_h = DS18B20_ReadByte(); int16_t temp = (temp_h << 8) | temp_l; return temp * 0.0625; // Convert to Celsius } // ADC read function uint16_t ADC_Read(uint8_t channel) { ADC1_CR2 &= ~(0x1F << 12); // Clear channel selection ADC1_CR2 |= (channel << 12); // Set channel ADC1_CR2 |= (1 << 22); // Start conversion while (!(ADC1_SR & (1 << 1))); // Wait for conversion complete return ADC1_DR; // Return data } // Read pH value float Read_pH(void) { uint16_t adc_value = ADC_Read(PH_SENSOR_PIN); float voltage = adc_value * 3.3 / 4095.0; // Assume 3.3V reference // Calibration: pH = 7.0 + (voltage - 2.5) / 0.18; Example, adjust based on sensor float ph = 7.0 + (voltage - 2.5) / 0.18; return ph; } // Read dissolved oxygen value float Read_DO(void) { uint16_t adc_value = ADC_Read(DO_SENSOR_PIN); float voltage = adc_value * 3.3 / 4095.0; // Assume 3.3V reference // Calibration: DO = voltage * 0.2; Example, adjust based on sensor float do_value = voltage * 0.2; return do_value; } // USART1 send character void USART1_SendChar(char c) { while (!(USART1_SR & (1 << 7))); // Wait for TXE USART1_DR = c; } // USART1 send string void USART1_SendString(char *str) { while (*str) { USART1_SendChar(*str++); } } // Control devices based on thresholds void Control_Devices(float temp, float ph, float do_value) { // Example thresholds if (do_value < 5.0) { // If DO low, turn on oxygen machine GPIOB_ODR |= (1 << OXYGEN_PIN); } else { GPIOB_ODR &= ~(1 << OXYGEN_PIN); } // Add other controls as needed } // Main function int main(void) { SystemInit(); GPIO_Init(); ADC1_Init(); USART1_Init(); DS18B20_Init(); while (1) { float temp = DS18B20_ReadTemp(); float ph = Read_pH(); float do_value = Read_DO(); Control_Devices(temp, ph, do_value); // Send data to USART (for QT上位机) char buffer[50]; sprintf(buffer, "T:%.1f,pH:%.1f,DO:%.1f\n", temp, ph, do_value); USART1_SendString(buffer); // Send command to ESP32-CAM if needed (e.g., capture image) USART1_SendString("CAPTURE\n"); // Simple delay for (volatile int i = 0; i < 1000000; i++); } } 项目核心代码#include <stdint.h> // Define register addresses #define RCC_APB2ENR (*(volatile uint32_t*)0x40021018) #define GPIOA_CRL (*(volatile uint32_t*)0x40010800) #define GPIOA_CRH (*(volatile uint32_t*)0x40010804) #define GPIOC_CRH (*(volatile uint32_t*)0x40011004) #define ADC1_CR2 (*(volatile uint32_t*)0x40012408) #define ADC1_SQR1 (*(volatile uint32_t*)0x4001242C) #define ADC1_SQR3 (*(volatile uint32_t*)0x40012434) #define ADC1_SMPR2 (*(volatile uint32_t*)0x40012410) #define ADC1_DR (*(volatile uint32_t*)0x4001244C) #define ADC1_SR (*(volatile uint32_t*)0x40012400) #define USART1_BRR (*(volatile uint32_t*)0x40013808) #define USART1_CR1 (*(volatile uint32_t*)0x4001380C) #define USART1_DR (*(volatile uint32_t*)0x40013804) #define USART1_SR (*(volatile uint32_t*)0x40013800) // Assume external functions for sensors and control (already implemented) extern float DS18B20_ReadTemp(void); // Returns temperature in Celsius extern void OxygenPump_Control(uint8_t state); // Control增氧机: state=1 on, state=0 off extern void Feeder_Control(uint8_t state); // Control投饵机: state=1 on, state=0 off extern void ESP32_CAM_SendCommand(uint8_t cmd); // Send command to ESP32-CAM via UART // UART send function void UART_SendString(char *str) { while (*str) { while (!(USART1_SR & 0x80)); // Wait for TXE flag USART1_DR = *str++; } } // ADC read function for a specific channel uint16_t ADC_Read(uint8_t channel) { ADC1_SQR3 = channel; // Set channel for conversion ADC1_CR2 |= 0x00500000; // Set SWSTART bit to start conversion (EXTTRIG and SWSTART) while (!(ADC1_SR & 0x02)); // Wait for EOC flag return ADC1_DR; // Read data } int main(void) { // Enable clocks for GPIOA, GPIOB, GPIOC, ADC1, USART1 RCC_APB2ENR |= (1 << 2) | (1 << 3) | (1 << 4) | (1 << 9) | (1 << 14); // Configure GPIOA GPIOA_CRL = 0x00000003; // PA0 as output push-pull for DS18B20, PA1 and PA2 as analog input GPIOA_CRH = 0x000004A0; // PA9 as alternate function push-pull (UART TX), PA10 as input floating (UART RX) // Configure GPIOC for control pins GPIOC_CRH = 0x03300000; // PC13 and PC14 as output push-pull // Configure ADC1 ADC1_SMPR2 = (7 << 3) | (7 << 6); // Set sample time to 55.5 cycles for channels 1 and 2 ADC1_SQR1 = 0x00000000; // Set number of conversions to 1 (L=0) ADC1_CR2 |= 0x00000001; // Enable ADC1 (ADON bit) // Configure USART1 USART1_BRR = 0x341; // Baud rate 9600 for 8MHz clock USART1_CR1 |= (1 << 13) | (1 << 3); // Enable USART1 (UE bit) and Transmitter (TE bit) // Initialize external modules (assumed already implemented) // DS18B20_Init(); etc. float temperature, ph_value, do_value; uint16_t adc_value; while (1) { // Read temperature from DS18B20 temperature = DS18B20_ReadTemp(); // Read pH value from ADC channel 1 (PA1) adc_value = ADC_Read(1); // Channel 1 ph_value = (adc_value * 3.3 / 4095) * 2.0; // Example conversion: assume 0-3.3V range, pH sensor specific // Read dissolved oxygen value from ADC channel 2 (PA2) adc_value = ADC_Read(2); // Channel 2 do_value = (adc_value * 3.3 / 4095) * 0.5; // Example conversion: assume 0-3.3V range, DO sensor specific // Control devices based on thresholds if (do_value < 5.0) { // Example threshold for dissolved oxygen OxygenPump_Control(1); // Turn on增氧机 } else { OxygenPump_Control(0); // Turn off } // Example control for投饵机 based on time or other factors // Feeder_Control(1); or based on condition // Send data to QT上位机 via UART char buffer[64]; sprintf(buffer, "Temp:%.2f C, pH:%.2f, DO:%.2f mg/L\n", temperature, ph_value, do_value); UART_SendString(buffer); // Send command to ESP32-CAM if needed ESP32_CAM_SendCommand(0x01); // Example command // Simple delay (can be improved with timer) for (volatile int i = 0; i < 1000000; i++); } } 总结本系统基于STM32F103C8T6微控制器为核心,实现了智能水产养殖的全面监控与自动化管理。通过实时监测水体温度、pH值和溶解氧含量,系统能够动态调整增氧机和投饵机等设备的运行,从而优化养殖环境,确保鱼类健康生长。同时,水下摄像头采集鱼类活动影像,并结合QT上位机界面,直观显示水质参数变化曲线和设备状态,为用户提供便捷的远程监控体验。在硬件实现上,系统采用DS18B20防水温度传感器、PH-4502C pH传感器和DO溶解氧传感器模块进行高精度数据采集,ESP32-CAM模块负责水下影像捕捉。所有传感器通过洞洞板焊接的信号处理电路和杜邦线连接,确保了电路的稳定性和可靠性,简化了系统搭建过程。总体而言,该系统通过软硬件结合,实现了水产养殖的智能化和自动化,显著提高了养殖效率和管理水平,为现代水产养殖业提供了实用的解决方案。
  • [技术干货] 基于STM32设计的智能实验室危化品管理系统设计
    项目开发背景随着高等教育和科研活动的快速发展,实验室中危化品的使用频率和种类显著增加,其安全管理问题日益突出。传统管理方式主要依赖人工记录和定期检查,不仅效率低下,而且难以实现实时监控与快速响应,存在较大的安全隐患。尤其在高温、高湿或气体泄漏等异常情况下,若未能及时处理,极易引发安全事故,对人员生命和实验室设备造成严重威胁。此外,危化品的存取和使用缺乏有效追踪手段,容易出现使用记录不完整、权限管理混乱等问题,导致药品误用、滥用或丢失的情况发生。现有系统往往功能单一,未能将环境监测、使用记录、身份认证和远程管理等功能有机结合,无法满足现代化实验室对危化品管理的全面需求。因此,开发一套集成化、智能化的危化品管理系统显得尤为迫切。本项目基于STM32微控制器,结合多种传感器与无线通信技术,旨在实现对危化品存储环境、使用过程和人员操作的全面监控,并通过上位机软件实现数据可视化与管理优化,为实验室安全运行提供可靠的技术保障。设计实现的功能(1) 实时监测危化品柜气体泄漏情况(使用MQ-2气体传感器)(2) 称重传感器监测危化品使用量并记录使用日志(使用HX711模块)(3) RFID身份认证与权限管理(使用RFID-RC522模块)(4) 数据上传至华为云,支持QT上位机实现危化品库存管理、使用追溯和预警处理(使用ESP8266模块)(5) STM32主控制器处理数据(使用STM32F103C8T6核心板)(6) 传感器接口连接与硬件集成(使用洞洞板和杜邦线)项目硬件模块组成(1)STM32F103C8T6最小系统核心板作为主控制器(2)HX711称重传感器模块监测药品重量(3)MQ-2气体传感器检测泄漏情况(4)RFID-RC522模块实现人员身份认证(5)ESP8266-01S Wi-Fi模块上传数据至华为云(6)洞洞板焊接传感器接口,杜邦线连接功能模块设计意义基于STM32设计的智能实验室危化品管理系统通过实时监测危化品柜的温度、湿度和气体泄漏情况,有效提升了实验室的安全水平。该系统能够及时检测异常环境参数,如高温、高湿或有害气体泄漏,从而预防潜在的安全事故,保障实验人员的安全和实验室设备的完整性,减少因环境因素引发的风险。该系统利用称重传感器监测危化品的使用量并自动记录使用日志,实现了对药品库存的精确管理。这减少了人工记录的错误和遗漏,提高了管理效率,同时确保了使用数据的准确性和可追溯性,为实验室的资源分配和成本控制提供了可靠依据。通过RFID身份认证与权限管理,系统确保了只有授权人员才能访问危化品柜,防止未授权操作和误用。这增强了实验室的安全管控,符合法规要求,并降低了因人为因素导致的安全隐患,提升了整体操作规范性。借助ESP8266 Wi-Fi模块将数据上传至华为云,系统实现了远程监控和预警处理。这使得管理人员能够实时查看数据并及时响应异常情况,无论身处何地,都能通过上位机软件进行干预,提高了管理的灵活性和应急处理能力。QT上位机软件实现了危化品库存管理、使用追溯和预警处理功能,提供了直观的用户界面和数据分析工具。这不仅简化了日常管理流程,还支持历史数据查询和审计跟踪,有助于满足实验室的合规要求,并促进长期运营的优化和改进。设计思路设计思路围绕STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。该系统通过集成多种传感器模块,实现危化品柜的智能化管理。首先,主控制器实时采集温度、湿度数据以及MQ-2气体传感器的泄漏信号,确保环境安全监测的连续性。这些数据经过ADC转换和处理后,用于本地判断和后续上传。称重监测部分采用HX711模块,连接到危化品容器底部,实时监测药品重量变化。主控制器定期读取重量数据,并通过内部存储记录使用日志,包括时间、重量差值等信息,便于后续追溯和库存管理。日志数据会暂时存储在STM32的Flash或外部EEPROM中,以防数据丢失。RFID身份认证通过RFID-RC522模块实现,用于人员进出管理。系统读取RFID卡信息后,与预设权限数据库进行匹配,只有授权人员才能操作柜门或进行药品取用。权限管理逻辑内置在STM32程序中,支持不同级别的访问控制,如管理员和普通用户。数据通信部分依赖ESP8266-01S Wi-Fi模块,将传感器数据、重量日志和RFID操作记录上传至华为云平台。STM32通过串口与ESP8266通信,使用AT指令配置网络连接和数据传输,确保远程监控和预警功能的实现。华为云平台接收数据后,可用于进一步分析和触发预警通知。QT上位机软件作为用户界面,负责危化品库存管理、使用追溯和预警处理。它通过云平台或直接串口连接获取数据,实现图形化显示库存状态、历史使用记录以及环境异常预警。上位机还可以生成报表和发送警报,增强实验室管理的便捷性和安全性。硬件实现上,所有传感器模块通过洞洞板进行焊接和固定,确保连接稳定性和可靠性。杜邦线用于模块间的信号连接,简化布线并便于调试。整个系统设计注重实用性和成本效益,避免不必要的功能添加,以实际需求为导向。框架图+---------------------------------+ | Sensors and Inputs | | +---------------------------+ | | | Temperature/Humidity Sensor| | // Assume DHT11 or similar | +---------------------------+ | | +---------------------------+ | | | Gas Sensor (MQ-2) | | | +---------------------------+ | | +---------------------------+ | | | Weight Sensor (HX711) | | | +---------------------------+ | | +---------------------------+ | | | RFID Reader (RC522) | | | +---------------------------+ | +---------------------------------+ | v +---------------------------------+ | STM32F103C8T6 Controller | | (on Dot Board with Dupont wires) | | - Data Acquisition | | - RFID Authentication | | - Sensor Control | +---------------------------------+ | v +---------------------------------+ | ESP8266-01S Wi-Fi Module | | - Data Transmission to Cloud | +---------------------------------+ | v +---------------------------------+ | Huawei Cloud IoT | | - Data Storage and Forwarding | +---------------------------------+ | v +---------------------------------+ | QT Upper Computer (PC) | | - Inventory Management | | - Usage Tracing | | - Warning Handling | +---------------------------------+ 系统总体设计系统总体设计基于STM32F103C8T6最小系统核心板作为主控制器,负责协调和管理所有功能模块,实现实验室危化品的智能管理。该系统通过集成多种传感器和执行器,实时监测危化品柜的环境参数和使用情况,确保安全性和可追溯性。系统通过HX711称重传感器模块监测危化品的重量变化,记录使用日志并计算剩余量;MQ-2气体传感器检测柜内是否有可燃或有毒气体泄漏,及时发出警报;RFID-RC522模块用于人员身份认证,只有授权人员才能通过刷卡访问危化品,实现权限管理。同时,温度湿度监测通过相应的传感器模块实现,实时采集环境数据并与预设阈值比较,用于预警处理。STM32主控制器处理所有传感器数据,执行逻辑控制,并通过ESP8266-01S Wi-Fi模块将数据上传至华为云平台,实现远程数据存储和访问。QT上位机软件基于这些数据提供危化品库存管理、使用追溯和预警处理功能,用户可以通过图形界面监控状态、查询日志和处理警报。硬件方面,所有传感器模块通过洞洞板焊接接口,使用杜邦线连接到STM32核心板,确保连接可靠且便于维护。整个设计注重实际应用,模块化结构便于扩展和调试。系统功能总结功能描述硬件/实现方式实时监测温度温度传感器(接口于洞洞板)实时监测湿度湿度传感器(接口于洞洞板)实时监测气体泄漏MQ-2气体传感器监测危化品使用量HX711称重传感器模块记录使用日志STM32控制器,通过ESP8266上传数据RFID身份认证RFID-RC522模块权限管理STM32软件实现库存管理QT上位机软件使用追溯QT上位机软件预警处理QT上位机软件数据上传至华为云ESP8266-01S Wi-Fi模块主控制STM32F103C8T6最小系统核心板传感器接口连接洞洞板焊接接口,杜邦线连接设计的各个功能模块描述STM32F103C8T6最小系统核心板作为主控制器,负责整个系统的协调与控制。它通过GPIO、ADC、SPI和I2C等接口与各传感器模块通信,实现数据采集、处理和执行逻辑控制,确保系统稳定运行并响应各种功能需求。HX711称重传感器模块用于监测危化品的重量变化,通过STM32读取传感器数据,实时记录使用量并生成使用日志,支持库存管理和使用追溯功能。MQ-2气体传感器检测危化品柜内的气体泄漏情况,当检测到异常气体浓度时,将信号传输至STM32进行处理,并触发预警机制,以保障实验室安全。RFID-RC522模块实现人员身份认证与权限管理,用户通过RFID卡进行身份验证,STM32根据预设权限决定是否允许操作危化品柜,确保只有授权人员才能访问。ESP8266-01S Wi-Fi模块用于将传感器采集的数据上传至华为云平台,实现远程数据存储和监控,并通过网络支持QT上位机进行库存管理、使用追溯和预警处理。洞洞板用于焊接各传感器接口,杜邦线连接功能模块到STM32主板,提供可靠的硬件连接和灵活的模块集成,确保系统整体结构的稳定性和可维护性。上位机代码设计以下是基于QT和C++开发的智能实验室危化品管理系统上位机代码设计。代码实现了危化品库存管理、使用追溯和预警处理功能,并通过网络从华为云获取数据(假设华为云提供REST API)。代码使用QT 5.15版本,并包括必要的UI设计和逻辑处理。项目结构SmartLabSystem/ ├── main.cpp ├── mainwindow.h ├── mainwindow.cpp ├── mainwindow.ui ├── networkmanager.h ├── networkmanager.cpp ├── datamanager.h ├── datamanager.cpp ├── alertmanager.h ├── alertmanager.cpp ├── CMakeLists.txt (或 SmartLabSystem.pro) 代码文件1. main.cpp#include <QApplication> #include "mainwindow.h" 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 <QTimer> #include "networkmanager.h" #include "datamanager.h" #include "alertmanager.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void onDataReceived(const QJsonObject &data); void onUpdateInventory(); void onShowLogs(); void onHandleAlert(const QString &message); void refreshData(); private: Ui::MainWindow *ui; NetworkManager *networkManager; DataManager *dataManager; AlertManager *alertManager; QTimer *refreshTimer; }; #endif // MAINWINDOW_H 3. mainwindow.cpp#include "mainwindow.h" #include "ui_mainwindow.h" #include <QMessageBox> #include <QJsonArray> #include <QJsonDocument> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , networkManager(new NetworkManager(this)) , dataManager(new DataManager(this)) , alertManager(new AlertManager(this)) { ui->setupUi(this); // 连接信号和槽 connect(networkManager, &NetworkManager::dataReceived, this, &MainWindow::onDataReceived); connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::refreshData); connect(ui->inventoryButton, &QPushButton::clicked, this, &MainWindow::onUpdateInventory); connect(ui->logsButton, &QPushButton::clicked, this, &MainWindow::onShowLogs); connect(alertManager, &AlertManager::alertTriggered, this, &MainWindow::onHandleAlert); // 设置定时器,每5秒刷新数据 refreshTimer = new QTimer(this); connect(refreshTimer, &QTimer::timeout, this, &MainWindow::refreshData); refreshTimer->start(5000); // 初始数据加载 refreshData(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::onDataReceived(const QJsonObject &data) { // 解析数据并更新UI dataManager->updateData(data); ui->temperatureLabel->setText(QString::number(data["temperature"].toDouble()) + " °C"); ui->humidityLabel->setText(QString::number(data["humidity"].toDouble()) + " %"); ui->gasLabel->setText(QString::number(data["gas"].toDouble()) + " ppm"); ui->weightLabel->setText(QString::number(data["weight"].toDouble()) + " g"); // 检查预警 alertManager->checkAlerts(data); } void MainWindow::onUpdateInventory() { // 更新库存列表 QJsonArray inventory = dataManager->getInventory(); ui->inventoryList->clear(); for (const auto &item : inventory) { QJsonObject obj = item.toObject(); QString text = obj["name"].toString() + " - " + QString::number(obj["weight"].toDouble()) + " g"; ui->inventoryList->addItem(text); } } void MainWindow::onShowLogs() { // 显示使用日志 QJsonArray logs = dataManager->getLogs(); ui->logsText->clear(); for (const auto &log : logs) { QJsonObject obj = log.toObject(); QString text = obj["timestamp"].toString() + " - " + obj["user"].toString() + " - " + obj["action"].toString(); ui->logsText->append(text); } } void MainWindow::onHandleAlert(const QString &message) { QMessageBox::warning(this, "Warning", message); ui->statusBar->showMessage("Alert: " + message, 5000); } void MainWindow::refreshData() { networkManager->fetchData(); // 从华为云获取数据 } 4. mainwindow.ui (UI设计文件)由于QT UI文件是XML格式,这里简要描述UI元素:QLabel 用于显示温度、湿度、气体浓度、重量。QListWidget 用于库存列表。QTextEdit 用于日志显示。QPushButton 用于刷新、查看库存、查看日志。状态栏用于显示预警。具体UI设计需在QT Designer中完成,这里不提供完整XML。5. networkmanager.h#ifndef NETWORKMANAGER_H #define NETWORKMANAGER_H #include <QObject> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QJsonObject> class NetworkManager : public QObject { Q_OBJECT public: explicit NetworkManager(QObject *parent = nullptr); void fetchData(); signals: void dataReceived(const QJsonObject &data); private slots: void onReplyFinished(QNetworkReply *reply); private: QNetworkAccessManager *manager; QString apiUrl = "https://your-huawei-cloud-api.com/data"; // 替换为实际API端点 QString token = "your-auth-token"; // 替换为实际认证令牌 }; #endif // NETWORKMANAGER_H 6. networkmanager.cpp#include "networkmanager.h" #include <QJsonDocument> #include <QJsonObject> #include <QNetworkRequest> NetworkManager::NetworkManager(QObject *parent) : QObject(parent) { manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, this, &NetworkManager::onReplyFinished); } void NetworkManager::fetchData() { QNetworkRequest request; request.setUrl(QUrl(apiUrl)); request.setRawHeader("Authorization", ("Bearer " + token).toUtf8()); manager->get(request); } void NetworkManager::onReplyFinished(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray response = reply->readAll(); QJsonDocument jsonDoc = QJsonDocument::fromJson(response); QJsonObject jsonObj = jsonDoc.object(); emit dataReceived(jsonObj); } else { // 处理错误 emit dataReceived(QJsonObject()); // 发送空对象或错误处理 } reply->deleteLater(); } 7. datamanager.h#ifndef DATAMANAGER_H #define DATAMANAGER_H #include <QObject> #include <QJsonObject> #include <QJsonArray> class DataManager : public QObject { Q_OBJECT public: explicit DataManager(QObject *parent = nullptr); void updateData(const QJsonObject &data); QJsonArray getInventory() const; QJsonArray getLogs() const; private: QJsonArray inventory; QJsonArray logs; }; #endif // DATAMANAGER_H 8. datamanager.cpp#include "datamanager.h" DataManager::DataManager(QObject *parent) : QObject(parent) { // 初始化数据 inventory = QJsonArray(); logs = QJsonArray(); } void DataManager::updateData(const QJsonObject &data) { // 更新库存和日志 if (data.contains("inventory")) { inventory = data["inventory"].toArray(); } if (data.contains("logs")) { logs = data["logs"].toArray(); } } QJsonArray DataManager::getInventory() const { return inventory; } QJsonArray DataManager::getLogs() const { return logs; } 9. alertmanager.h#ifndef ALERTMANAGER_H #define ALERTMANAGER_H #include <QObject> #include <QJsonObject> class AlertManager : public QObject { Q_OBJECT public: explicit AlertManager(QObject *parent = nullptr); void checkAlerts(const QJsonObject &data); signals: void alertTriggered(const QString &message); private: double temperatureThreshold = 30.0; // 温度阈值,单位°C double humidityThreshold = 80.0; // 湿度阈值,单位% double gasThreshold = 100.0; // 气体阈值,单位ppm }; #endif // ALERTMANAGER_H 10. alertmanager.cpp#include "alertmanager.h" AlertManager::AlertManager(QObject *parent) : QObject(parent) { } void AlertManager::checkAlerts(const QJsonObject &data) { double temperature = data["temperature"].toDouble(); double humidity = data["humidity"].toDouble(); double gas = data["gas"].toDouble(); if (temperature > temperatureThreshold) { emit alertTriggered("Temperature exceeded threshold: " + QString::number(temperature) + " °C"); } if (humidity > humidityThreshold) { emit alertTriggered("Humidity exceeded threshold: " + QString::number(humidity) + " %"); } if (gas > gasThreshold) { emit alertTriggered("Gas leakage detected: " + QString::number(gas) + " ppm"); } } 编译和运行说明使用QT Creator打开项目,配置华为云API端点和认证令牌。编译并运行项目。上位机将定期从华为云获取数据,并显示库存、日志和预警信息。此代码提供了基本框架,实际部署时需要根据华为云API的具体细节进行调整。模块代码设计#include "stm32f10x.h" // Define pins and ports #define HX711_DATA_PIN GPIO_Pin_0 #define HX711_DATA_PORT GPIOA #define HX711_SCK_PIN GPIO_Pin_1 #define HX711_SCK_PORT GPIOA #define MQ2_ADC_CHANNEL ADC_Channel_2 #define MQ2_ADC_DR_ADDR (*(__IO uint32_t*)(0x4001244C)) #define RFID_CS_PIN GPIO_Pin_4 #define RFID_CS_PORT GPIOA #define RFID_SPI SPI1 #define ESP8266_UART USART1 #define DHT11_PIN GPIO_Pin_3 #define DHT11_PORT GPIOA // Function prototypes void SystemClock_Config(void); void GPIO_Init(void); void ADC_Init(void); void SPI_Init(void); void UART_Init(void); void HX711_Init(void); long HX711_Read(void); uint16_t ADC_Read_MQ2(void); void RC522_Init(void); void RC522_WriteReg(uint8_t reg, uint8_t data); uint8_t RC522_ReadReg(uint8_t reg); uint8_t RC522_Request(uint8_t req_mode, uint8_t *tag_type); uint8_t RC522_AntiColl(uint8_t *ser_num); void UART_SendChar(char c); void UART_SendString(const char *str); void DHT11_Start(void); uint8_t DHT11_ReadByte(void); void DHT11_Read(int8_t *temp, int8_t *hum); void delay_ms(uint32_t ms); void delay_us(uint32_t us); int main(void) { SystemClock_Config(); GPIO_Init(); ADC_Init(); SPI_Init(); UART_Init(); HX711_Init(); RC522_Init(); int8_t temperature, humidity; long weight; uint16_t gas_value; uint8_t card_id[5]; while (1) { // Read sensors weight = HX711_Read(); gas_value = ADC_Read_MQ2(); DHT11_Read(&temperature, &humidity); // Check for RFID card if (RC522_Request(0x52, card_id) == 0) { if (RC522_AntiColl(card_id) == 0) { // Card detected, process authentication (simplified) // Here, you would check against a list of authorized cards } } // Send data via ESP8266 to Huawei Cloud (simplified) char buffer[100]; sprintf(buffer, "Weight: %ld, Gas: %u, Temp: %d, Hum: %d\r\n", weight, gas_value, temperature, humidity); UART_SendString(buffer); delay_ms(1000); // Read every second } } void SystemClock_Config(void) { // Enable HSE and set PLL to output 72MHz RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9; RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); } void GPIO_Init(void) { // Enable GPIO clocks RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN; // HX711 pins: PA0 input, PA1 output GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0); GPIOA->CRL |= GPIO_CRL_CNF0_0; // Input floating GPIOA->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_MODE1); GPIOA->CRL |= GPIO_CRL_MODE1_0 | GPIO_CRL_MODE1_1; // Output push-pull, 50MHz // MQ-2 ADC pin: PA2 analog input GPIOA->CRL &= ~(GPIO_CRL_CNF2 | GPIO_CRL_MODE2); // DHT11 pin: PA3 input GPIOA->CRL &= ~(GPIO_CRL_CNF3 | GPIO_CRL_MODE3); GPIOA->CRL |= GPIO_CRL_CNF3_0; // Input with pull-up/pull-down GPIOA->ODR |= GPIO_Pin_3; // Set pull-up // RFID CS pin: PA4 output GPIOA->CRL &= ~(GPIO_CRL_CNF4 | GPIO_CRL_MODE4); GPIOA->CRL |= GPIO_CRL_MODE4_0 | GPIO_CRL_MODE4_1; // Output push-pull, 50MHz GPIOA->ODR |= RFID_CS_PIN; // Set CS high initially // SPI pins: PA5 (SCK), PA6 (MISO), PA7 (MOSI) alternate function push-pull GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_MODE5 | GPIO_CRL_CNF6 | GPIO_CRL_MODE6 | GPIO_CRL_CNF7 | GPIO_CRL_MODE7); GPIOA->CRL |= GPIO_CRL_CNF5_1 | GPIO_CRL_MODE5_0 | GPIO_CRL_MODE5_1 | // PA5: AF PP, 50MHz GPIO_CRL_CNF6_0 | GPIO_CRL_CNF6_1 | // PA6: input floating for MISO GPIO_CRL_CNF7_1 | GPIO_CRL_MODE7_0 | GPIO_CRL_MODE7_1; // PA7: AF PP, 50MHz for MOSI // UART pins: PA9 (TX) alternate function push-pull, PA10 (RX) input floating GPIOA->CRH &= ~(GPIO_CRH_CNF9 | GPIO_CRH_MODE9 | GPIO_CRH_CNF10 | GPIO_CRH_MODE10); GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_0 | GPIO_CRH_MODE9_1 | // PA9: AF PP, 50MHz GPIO_CRH_CNF10_0; // PA10: input floating } void ADC_Init(void) { RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; ADC1->CR2 = 0; ADC1->SQR1 = 0; ADC1->SQR3 = MQ2_ADC_CHANNEL; ADC1->CR2 |= ADC_CR2_ADON; // Calibration (optional) ADC1->CR2 |= ADC_CR2_CAL; while (ADC1->CR2 & ADC_CR2_CAL); } uint16_t ADC_Read_MQ2(void) { ADC1->CR2 |= ADC_CR2_ADON; while (!(ADC1->SR & ADC_SR_EOC)); return ADC1->DR; } void SPI_Init(void) { RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; SPI1->CR1 = SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_MSTR | SPI_CR1_BR_0 | SPI_CR1_BR_1; // Master, software CS, prescaler /16 SPI1->CR1 |= SPI_CR1_SPE; } void RC522_Init(void) { RC522_WriteReg(0x01, 0x0F); // Command register RC522_WriteReg(0x2A, 0x8D); // TModeReg RC522_WriteReg(0x2B, 0x3E); // TPrescalerReg RC522_WriteReg(0x2D, 30); // TReloadReg low byte RC522_WriteReg(0x2C, 0); // TReloadReg high byte RC522_WriteReg(0x15, 0x40); // TxAutoReg RC522_WriteReg(0x11, 0x3D); // ModeReg } void RC522_WriteReg(uint8_t reg, uint8_t data) { RFID_CS_PORT->BRR = RFID_CS_PIN; while (!(SPI1->SR & SPI_SR_TXE)); SPI1->DR = (reg << 1) & 0xFE; while (!(SPI1->SR & SPI_SR_RXNE)); (void)SPI1->DR; while (!(SPI1->SR & SPI_SR_TXE)); SPI1->DR = data; while (!(SPI1->SR & SPI_SR_RXNE)); (void)SPI1->DR; RFID_CS_PORT->BSRR = RFID_CS_PIN; } uint8_t RC522_ReadReg(uint8_t reg) { RFID_CS_PORT->BRR = RFID_CS_PIN; while (!(SPI1->SR & SPI_SR_TXE)); SPI1->DR = (reg << 1) | 0x01; while (!(SPI1->SR & SPI_SR_RXNE)); (void)SPI1->DR; while (!(SPI1->SR & SPI_SR_TXE)); SPI1->DR = 0x00; while (!(SPI1->SR & SPI_SR_RXNE)); uint8_t data = SPI1->DR; RFID_CS_PORT->BSRR = RFID_CS_PIN; return data; } uint8_t RC522_Request(uint8_t req_mode, uint8_t *tag_type) { RC522_WriteReg(0x0D, 0x07); RC522_WriteReg(0x01, req_mode); for (uint8_t i = 0; i < 0xFF; i++) { if (RC522_ReadReg(0x06) & 0x10) { RC522_WriteReg(0x06, 0x10); if (RC522_ReadReg(0x0E) & 0x08) { return 1; } for (uint8_t j = 0; j < 2; j++) { tag_type[j] = RC522_ReadReg(0x09 + j); } return 0; } } return 1; } uint8_t RC522_AntiColl(uint8_t *ser_num) { RC522_WriteReg(0x0D, 0x00); RC522_WriteReg(0x01, 0x0C); for (uint8_t i = 0; i < 0xFF; i++) { if (RC522_ReadReg(0x06) & 0x08) { RC522_WriteReg(0x06, 0x08); for (uint8_t j = 0; j < 4; j++) { ser_num[j] = RC522_ReadReg(0x09 + j); } return 0; } } return 1; } void UART_Init(void) { RCC->APB2ENR |= RCC_APB2ENR_USART1EN; USART1->BRR = 0x1D4C; // 72MHz / 115200 ≈ 39.0625 -> 0x1D4C (39 << 4 | 1) USART1->CR1 = USART_CR1_TE | USART_CR1_UE; } void UART_SendChar(char c) { while (!(USART1->SR & USART_SR_TXE)); USART1->DR = c; } void UART_SendString(const char *str) { while (*str) { UART_SendChar(*str++); } } void HX711_Init(void) { HX711_SCK_PORT->BSRR = HX711_SCK_PIN; // Set SCK high initially } long HX711_Read(void) { while (HX711_DATA_PORT->IDR & HX711_DATA_PIN); // Wait for DATA low long value = 0; for (int i = 0; i < 24; i++) { HX711_SCK_PORT->BSRR = HX711_SCK_PIN; delay_us(1); value <<= 1; if (HX711_DATA_PORT->IDR & HX711_DATA_PIN) { value |= 1; } HX711_SCK_PORT->BRR = HX711_SCK_PIN; delay_us(1); } for (int i = 0; i < 1; i++) { // Set gain 128 HX711_SCK_PORT->BSRR = HX711_SCK_PIN; delay_us(1); HX711_SCK_PORT->BRR = HX711_SCK_PIN; delay_us(1); } return value; } void DHT11_Start(void) { GPIOA->CRL &= ~(GPIO_CRL_CNF3 | GPIO_CRL_MODE3); GPIOA->CRL |= GPIO_CRL_MODE3_0 | GPIO_CRL_MODE3_1; // Output GPIOA->BRR = DHT11_PIN; delay_ms(18); GPIOA->BSRR = DHT11_PIN; delay_us(30); GPIOA->CRL &= ~(GPIO_CRL_CNF3 | GPIO_CRL_MODE3); GPIOA->CRL |= GPIO_CRL_CNF3_0; // Input with pull-up GPIOA->ODR |= DHT11_PIN; } uint8_t DHT11_ReadByte(void) { uint8_t value = 0; for (int i = 0; i < 8; i++) { while (!(GPIOA->IDR & DHT11_PIN)); // Wait for high delay_us(40); if (GPIOA->IDR & DHT11_PIN) { value |= (1 << (7 - i)); while (GPIOA->IDR & DHT11_PIN); // Wait for low } } return value; } void DHT11_Read(int8_t *temp, int8_t *hum) { DHT11_Start(); if (!(GPIOA->IDR & DHT11_PIN)) { while (!(GPIOA->IDR & DHT11_PIN)); // Wait for high while (GPIOA->IDR & DHT11_PIN); // Wait for low uint8_t data[5]; for (int i = 0; i < 5; i++) { data[i] = DHT11_ReadByte(); } if (data[4] == (data[0] + data[1] + data[2] + data[3])) { *hum = data[0]; *temp = data[2]; } else { *hum = -1; *temp = -1; } } else { *hum = -1; *temp = -1; } } void delay_ms(uint32_t ms) { for (uint32_t i = 0; i < ms * 7200; i++) { __NOP(); } } void delay_us(uint32_t us) { for (uint32_t i = 0; i < us * 7; i++) { __NOP(); } } 项目核心代码#include "stm32f10x.h" // 假设其他模块已经编写好,这里声明外部函数 extern void HX711_Init(void); extern int32_t HX711_ReadWeight(void); extern void MQ2_Init(void); extern uint16_t ADC_ReadGas(void); extern void DHT11_Init(void); extern void DHT11_ReadTempHumidity(int16_t *temp, int16_t *humidity); extern void RFID_Init(void); extern uint8_t RFID_CheckCard(uint32_t *card_id); extern void ESP8266_Init(void); extern void ESP8266_SendData(char *data); // 系统时钟配置 void RCC_Configuration(void) { // 启用HSE(外部高速时钟),假设使用8MHz晶振 RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); // 配置PLL:HSE作为源,倍频到72MHz RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9; RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); // 设置PLL为系统时钟源 RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 启用GPIOA、ADC1、SPI1、USART1时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_ADC1EN | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_USART1EN; } // GPIO配置 void GPIO_Configuration(void) { // 配置PA0、PA1、PA2为模拟输入(用于ADC:气体、温度、湿度传感器) GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_CNF1 | GPIO_CRL_CNF2); GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2); // 配置PA3为输入(HX711 DATA),PA4为输出(HX711 SCK) GPIOA->CRL &= ~(GPIO_CRL_CNF3 | GPIO_CRL_CNF4); GPIOA->CRL |= GPIO_CRL_CNF3_0; // PA3为输入上拉/下拉 GPIOA->CRL |= GPIO_CRL_MODE4; // PA4为输出模式,最大速度50MHz GPIOA->BSRR = GPIO_BSRR_BS4; // 设置PA4初始高电平 // 配置SPI1引脚:PA5为SCK,PA6为MISO,PA7为MOSI(复用推挽输出) GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_CNF6 | GPIO_CRL_CNF7); GPIOA->CRL |= GPIO_CRL_CNF5_1 | GPIO_CRL_CNF6_1 | GPIO_CRL_CNF7_1; // 复用推挽输出 GPIOA->CRL |= GPIO_CRL_MODE5 | GPIO_CRL_MODE6 | GPIO_CRL_MODE7; // 输出模式,最大速度50MHz // 配置USART1引脚:PA9为TX(复用推挽输出),PA10为RX(输入上拉/下拉) GPIOA->CRH &= ~(GPIO_CRH_CNF9 | GPIO_CRH_CNF10); GPIOA->CRH |= GPIO_CRH_CNF9_1; // PA9为复用推挽输出 GPIOA->CRH |= GPIO_CRH_CNF10_0; // PA10为输入上拉/下拉 GPIOA->CRH |= GPIO_CRH_MODE9; // PA9输出模式,最大速度50MHz } // ADC配置 void ADC1_Configuration(void) { ADC1->CR2 |= ADC_CR2_ADON; // 启用ADC1 ADC1->CR2 |= ADC_CR2_CONT; // 连续转换模式 ADC1->SQR1 &= ~ADC_SQR1_L; // 设置1个转换在序列中 ADC1->SQR3 = 0; // 通道0为第一个转换 ADC1->CR2 |= ADC_CR2_SWSTART; // 开始转换 } // SPI1配置(用于RFID) void SPI1_Configuration(void) { SPI1->CR1 |= SPI_CR1_SSM | SPI_CR1_SSI; // 软件NSS管理 SPI1->CR1 |= SPI_CR1_MSTR; // 主模式 SPI1->CR1 |= SPI_CR1_BR_0 | SPI_CR1_BR_1; // 波特率分频,FPCLK/64 SPI1->CR1 |= SPI_CR1_SPE; // 启用SPI } // USART1配置(用于ESP8266) void USART1_Configuration(void) { USART1->BRR = 0x0341; // 波特率9600(72MHz / 9600 = 7500, 0x1D4C, but adjust for 72MHz PCLK2) USART1->CR1 |= USART_CR1_TE | USART_CR1_RE; // 启用发送和接收 USART1->CR1 |= USART_CR1_UE; // 启用USART } // 主函数 int main(void) { // 初始化系统时钟和外设 RCC_Configuration(); GPIO_Configuration(); ADC1_Configuration(); SPI1_Configuration(); USART1_Configuration(); // 初始化传感器模块 HX711_Init(); MQ2_Init(); DHT11_Init(); RFID_Init(); ESP8266_Init(); int32_t weight = 0; uint16_t gas_value = 0; int16_t temperature = 0, humidity = 0; uint32_t card_id = 0; uint8_t card_status = 0; char data_buffer[100]; while (1) { // 读取传感器数据 weight = HX711_ReadWeight(); gas_value = ADC_ReadGas(); DHT11_ReadTempHumidity(&temperature, &humidity); card_status = RFID_CheckCard(&card_id); // 检查RFID卡并处理权限 if (card_status) { // 如果有卡,记录使用日志(简化处理) // 这里可以添加权限验证逻辑 } // 格式化数据并通过ESP8266上传到云 snprintf(data_buffer, sizeof(data_buffer), "Weight:%ld,Gas:%u,Temp:%d,Hum:%d,Card:%lu\n", weight, gas_value, temperature, humidity, card_id); ESP8266_SendData(data_buffer); // 简单延迟,实际中应使用定时器 for (volatile int i = 0; i < 1000000; i++); } } 总结本系统基于STM32F103C8T6微控制器设计,实现了对实验室危化品的智能化管理,通过实时监测温度、湿度和气体泄漏,确保环境安全;同时利用称重传感器记录药品使用量,并结合RFID身份认证进行权限控制,提升了管理的精确性和安全性。QT上位机提供了库存管理、使用追溯和预警处理功能,使得操作直观便捷,有效支持实验室的日常运营。硬件方面,系统集成HX711称重传感器模块、MQ-2气体传感器、RFID-RC522模块和ESP8266-01S Wi-Fi模块,通过洞洞板焊接和杜邦线连接实现模块化设计,数据可靠上传至华为云平台,实现了远程监控和数据存储。这种设计不仅降低了成本,还提高了系统的可扩展性和维护性。总体而言,该系统通过软硬件结合,实现了危化品管理的自动化和信息化,显著增强了实验室的安全水平和效率,为预防事故和优化资源利用提供了有力支持。
  • [技术干货] 基于STM32设计的智能农业无人机监控系统
    项目开发背景随着现代农业向智能化、精准化方向发展,传统农业生产模式面临劳动力成本上升、作业效率低下及资源利用率不足等挑战。无人机技术凭借其灵活性与高空视角优势,已成为智慧农业领域的重要工具,能够高效完成农田监测、作物生长评估及病虫害预警等任务。然而,现有农业无人机系统仍存在多源数据整合能力弱、实时性差及云平台协同不足等问题。尤其在小规模农田应用中,低成本、高精度的无人机监控解决方案尚不完善。因此,亟需开发一套集成传感采集、云端通信与可视化分析于一体的智能监控系统。本项目基于STM32主控制器,结合多光谱传感、定位及无线通信技术,构建了一套轻量化农业无人机监控系统。通过多光谱传感器AS7265x获取植被指数数据,利用MPU6050实时监测飞行姿态,并通过GPS模块精确定位作业轨迹。数据通过ESP8266模块传输至华为云平台,实现远程存储与分析,同时通过QT上位机呈现农田生长态势与作业路径,为农户提供直观的决策支持。该系统旨在提升农业无人机作业的自动化水平与数据价值,通过低成本的硬件组合与云端协同,推动精准农业技术在中小规模农田中的普及应用,为实现农业可持续发展提供技术支撑。设计实现的功能(1) 实时监测无人机飞行状态和作业参数(2) 采集农田多光谱图像数据(3) 作业数据实时上传至华为云平台(4) QT上位机显示无人机作业轨迹和农田生长态势图项目硬件模块组成(1)STM32F103C8T6最小系统核心板作为主控制器(2)MPU6050传感器监测无人机飞行姿态(3)AS7265x多光谱传感器采集植被指数(4)ATGM336H GPS模块精确定位(5)ESP8266-01S Wi-Fi模块实现华为云通信(6)洞洞板焊接传感器接口电路,杜邦线连接各模块设计意义该智能农业无人机监控系统的设计意义在于显著提升现代农业的监测与管理效率。通过集成STM32主控制器和多种传感器,系统能够实时采集和处理无人机飞行状态及农田多光谱数据,从而减少传统人工巡查的劳动强度和时间成本,实现更快速、全面的农田覆盖监测。系统支持精准农业实践,利用多光谱传感器采集植被指数等关键数据,帮助农民准确评估作物健康状况,识别病虫害或营养缺失区域,为灌溉、施肥和病虫害防治提供科学依据,最终优化资源利用和提高农作物产量。实时数据上传至华为云平台,实现了远程监控和大数据存储,便于长期趋势分析和智能决策支持,这增强了农业生产的可持续性和响应速度,同时降低了因延迟处理可能导致的经济损失。通过QT上位机界面,用户能够直观查看无人机作业轨迹和农田生长态势图,提升了操作便利性和数据可视化程度,使非专业用户也能轻松参与监控过程,促进农业技术的普及和应用。设计思路该系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调和管理所有外围模块,实现智能农业无人机的监控功能。主控制器通过编程初始化各传感器和通信模块,确保系统稳定运行,并处理数据采集、处理和传输任务。MPU6050传感器用于实时监测无人机的飞行姿态,包括俯仰、滚转和偏航角度。STM32通过I2C接口读取MPU6050的数据,进行滤波和计算,以获取准确的飞行状态参数,这些数据用于后续的监控和上传。AS7265x多光谱传感器负责采集农田的多光谱图像数据,通过测量不同波长的光谱反射率来计算植被指数,如NDVI。STM32控制AS7265x via I2C通信,定期采集数据并存储,为农田生长分析提供原始信息。ATGM336H GPS模块提供精确定位功能,STM32通过串口接收GPS数据,解析经纬度、高度和时间信息,用于跟踪无人机的实时位置和作业轨迹,确保数据与地理位置关联。ESP8266-01S Wi-Fi模块实现与华为云平台的通信,STM32通过串口将采集到的飞行状态、多光谱数据和GPS信息打包成JSON格式,通过ESP8266发送到云平台,实现作业数据的实时上传和远程监控。QT上位机软件从华为云平台获取数据,解析后显示无人机的作业轨迹和农田生长态势图。通过图形界面直观展示飞行路径和植被健康状态,辅助农业决策。硬件方面,使用洞洞板焊接各传感器的接口电路,包括电源管理和信号调理,杜邦线连接STM32与各模块,确保电气连接可靠且便于调试。整个系统注重实际应用,强调低功耗和实时性。框架图±------------------+ ±----------------+| MPU6050 | | AS7265x || (姿态传感器) | | (多光谱传感器)|±------------------+ ±----------------+| |V V±--------------------------------------------+| STM32F103C8T6 || (主控制器) |±--------------------------------------------+| |V V±------------------+ ±----------------+| ATGM336H | | ESP8266-01S || (GPS模块) | | (Wi-Fi模块) |±------------------+ ±----------------+|V±----------------+| 华为云平台 |±----------------+|V±----------------+| QT上位机 || (显示界面) |±----------------+系统总体设计本系统以STM32F103C8T6最小系统核心板作为主控制器,构建了一个智能农业无人机监控系统。该系统旨在通过集成多种传感器和通信模块,实现对无人机飞行状态的实时监测、农田多光谱数据的采集以及作业数据的云端传输和上位机显示。整体设计基于实际硬件连接,包括洞洞板焊接的传感器接口电路和杜邦线连接各模块,确保系统的可靠性和实用性。MPU6050传感器用于监测无人机的飞行姿态,提供加速度和陀螺仪数据,以实时跟踪飞行状态如倾斜角和旋转速率。同时,AS7265x多光谱传感器负责采集农田的植被指数数据,通过测量不同光谱波段来评估作物生长情况。这些数据由STM32主控制器进行初步处理和整合,确保采集的准确性和实时性。ATGM336H GPS模块提供精确定位功能,记录无人机的经纬度坐标,用于生成作业轨迹。ESP8266-01S Wi-Fi模块则负责将处理后的数据,包括飞行参数、多光谱数据和GPS位置,通过MQTT或HTTP协议实时上传至华为云平台,实现远程监控和数据存储。QT上位机应用程序接收来自云平台的数据,并显示无人机的作业轨迹和农田生长态势图。该界面通过图形化方式呈现飞行路径和植被指数变化,帮助用户直观分析农田状况,支持农业决策。整个系统通过硬件模块的协同工作,实现了从数据采集到云端传输再到可视化展示的完整流程。系统功能总结功能描述硬件组成实时监测无人机飞行状态和作业参数STM32F103C8T6核心板, MPU6050传感器, ATGM336H GPS模块采集农田多光谱图像数据STM32F103C8T6核心板, AS7265x多光谱传感器作业数据实时上传至华为云平台STM32F103C8T6核心板, ESP8266-01S Wi-Fi模块QT上位机显示无人机作业轨迹和农田生长态势图STM32F103C8T6核心板, ESP8266-01S Wi-Fi模块 (数据传输)设计的各个功能模块描述STM32F103C8T6最小系统核心板作为整个系统的主控制器,负责协调和处理所有传感器数据,实现飞行状态监测、数据采集和通信控制。它通过GPIO、I2C和UART接口连接各模块,执行数据融合和逻辑判断,确保系统稳定运行。MPU6050传感器用于实时监测无人机的飞行姿态,包括加速度和角速度数据。它通过I2C接口与STM32通信,提供姿态信息如俯仰、滚转和偏航角,用于飞行状态评估和稳定控制。AS7265x多光谱传感器采集农田的多光谱图像数据,覆盖可见光和近红外波段。它通过I2C接口与STM32连接,计算植被指数如NDVI,用于分析农田生长态势和作物健康状态。ATGM336H GPS模块提供精确定位功能,通过UART接口与STM32通信,输出经纬度、海拔和时间数据。这用于记录无人机作业轨迹、定位采集点,并支持上位机显示飞行路径。ESP8266-01S Wi-Fi模块实现无线通信功能,通过UART接口与STM32交互,将采集的飞行状态、多光谱数据和GPS信息实时上传至华为云平台。它处理网络连接和数据传输协议,确保数据可靠上传。洞洞板用于焊接各传感器的接口电路,提供稳定的电气连接和信号调理。杜邦线用于灵活连接STM32与各模块,简化硬件布线和调试过程,确保系统可扩展性和可靠性。上位机代码设计// SmartDroneMonitor.pro QT += core gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = SmartDroneMonitor TEMPLATE = app DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ main.cpp \ mainwindow.cpp HEADERS += \ mainwindow.h// main.cpp #include <QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } // mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QGraphicsScene> #include <QTimer> #include <QLabel> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void fetchData(); void onDataReceived(QNetworkReply *reply); void updateDisplay(); private: Ui::MainWindow *ui; QNetworkAccessManager *networkManager; QGraphicsScene *trajectoryScene; QList<QPointF> gpsPoints; // Store GPS points as latitude/longitude QList<double> ndviValues; // Store NDVI values for growth map QTimer *dataTimer; QLabel *growthMapLabel; void setupUI(); void parseJsonData(const QByteArray &data); void drawTrajectory(); void drawGrowthMap(); }; #endif // MAINWINDOW_H // mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> #include <QNetworkRequest> #include <QUrl> #include <QDebug> #include <QGraphicsEllipseItem> #include <QPixmap> #include <QImage> #include <QPainter> // Mock URL for Huawei Cloud API - replace with actual URL in production const QString API_URL = "http://example.com/api/drone/data"; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , networkManager(new QNetworkAccessManager(this)) , trajectoryScene(new QGraphicsScene(this)) , dataTimer(new QTimer(this)) , growthMapLabel(new QLabel) { ui->setupUi(this); setupUI(); // Connect network manager connect(networkManager, &QNetworkAccessManager::finished, this, &MainWindow::onDataReceived); // Set up timer to fetch data periodically, e.g., every 5 seconds connect(dataTimer, &QTimer::timeout, this, &MainWindow::fetchData); dataTimer->start(5000); // 5000 ms interval // Initial fetch fetchData(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::setupUI() { // Set up central widget and layout QWidget *centralWidget = new QWidget(this); setCentralWidget(centralWidget); QHBoxLayout *layout = new QHBoxLayout(centralWidget); // Trajectory view QGraphicsView *trajectoryView = new QGraphicsView(this); trajectoryView->setScene(trajectoryScene); trajectoryView->setRenderHint(QPainter::Antialiasing); trajectoryView->setSceneRect(-100, -100, 200, 200); // Initial scene rect, will adjust // Growth map label growthMapLabel->setFixedSize(400, 300); growthMapLabel->setAlignment(Qt::AlignCenter); growthMapLabel->setText("Waiting for data..."); // Add widgets to layout layout->addWidget(trajectoryView); layout->addWidget(growthMapLabel); // Set window properties setWindowTitle("Smart Drone Monitor"); resize(800, 600); } void MainWindow::fetchData() { QUrl url(API_URL); QNetworkRequest request(url); networkManager->get(request); } void MainWindow::onDataReceived(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); parseJsonData(data); updateDisplay(); } else { qDebug() << "Network error:" << reply->errorString(); } reply->deleteLater(); } void MainWindow::parseJsonData(const QByteArray &data) { QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull() || !doc.isObject()) { qDebug() << "Invalid JSON data"; return; } QJsonObject obj = doc.object(); // Parse GPS data - assuming array of objects with "lat" and "lon" if (obj.contains("gps") && obj["gps"].isArray()) { QJsonArray gpsArray = obj["gps"].toArray(); gpsPoints.clear(); for (const QJsonValue &value : gpsArray) { QJsonObject point = value.toObject(); double lat = point["lat"].toDouble(); double lon = point["lon"].toDouble(); gpsPoints.append(QPointF(lon, lat)); // Using lon as x, lat as y for simplicity } } // Parse NDVI data - assuming array of values if (obj.contains("ndvi") && obj["ndvi"].isArray()) { QJsonArray ndviArray = obj["ndvi"].toArray(); ndviValues.clear(); for (const QJsonValue &value : ndviArray) { ndviValues.append(value.toDouble()); } } } void MainWindow::updateDisplay() { drawTrajectory(); drawGrowthMap(); } void MainWindow::drawTrajectory() { trajectoryScene->clear(); if (gpsPoints.isEmpty()) return; // Simple scaling: map longitude to x, latitude to y with offset and scale double minLon = gpsPoints.first().x(); double maxLon = gpsPoints.first().x(); double minLat = gpsPoints.first().y(); double maxLat = gpsPoints.first().y(); for (const QPointF &point : gpsPoints) { if (point.x() < minLon) minLon = point.x(); if (point.x() > maxLon) maxLon = point.x(); if (point.y() < minLat) minLat = point.y(); if (point.y() > maxLat) maxLat = point.y(); } double scale = 100.0 / qMax(maxLon - minLon, maxLat - minLat); // Scale to fit scene for (int i = 0; i < gpsPoints.size(); ++i) { double x = (gpsPoints[i].x() - minLon) * scale; double y = (gpsPoints[i].y() - minLat) * scale; trajectoryScene->addEllipse(x, y, 2, 2, QPen(Qt::blue), QBrush(Qt::blue)); if (i > 0) { double prevX = (gpsPoints[i-1].x() - minLon) * scale; double prevY = (gpsPoints[i-1].y() - minLat) * scale; trajectoryScene->addLine(prevX, prevY, x, y, QPen(Qt::red)); } } } void MainWindow::drawGrowthMap() { if (ndviValues.isEmpty()) { growthMapLabel->setText("No NDVI data"); return; } // Create a simple heat map image based on NDVI values int size = ndviValues.size(); int width = 100; int height = size / width + 1; QImage image(width, height, QImage::Format_RGB32); for (int i = 0; i < size; ++i) { double ndvi = ndviValues[i]; int x = i % width; int y = i / width; // Map NDVI to color: -1 to 1, typically green for vegetation int r = 0, g = 0, b = 0; if (ndvi < 0) { r = 255 * (-ndvi); // Red for negative NDVI } else { g = 255 * ndvi; // Green for positive NDVI } image.setPixel(x, y, qRgb(r, g, b)); } QPixmap pixmap = QPixmap::fromImage(image).scaled(growthMapLabel->size(), Qt::KeepAspectRatio); growthMapLabel->setPixmap(pixmap); } <!-- ui_mainwindow.h (generated by QT Designer, but included for completeness) --> <!-- This file is typically auto-generated, so here's a minimal version for structure --> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> Note: The ui_mainwindow.h file is usually generated by QT Designer when designing the UI. Since no specific UI design was provided, the code above uses programmatic layout. If you have a .ui file, it should be included in the project.模块代码设计#include <stdint.h> // Define register addresses for STM32F103C8T6 #define RCC_BASE 0x40021000 #define GPIOA_BASE 0x40010800 #define GPIOB_BASE 0x40010C00 #define USART1_BASE 0x40013800 #define USART2_BASE 0x40004400 #define I2C1_BASE 0x40005400 // RCC registers #define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x18)) #define RCC_APB1ENR (*(volatile uint32_t *)(RCC_BASE + 0x1C)) // GPIO registers #define GPIOA_CRL (*(volatile uint32_t *)(GPIOA_BASE + 0x00)) #define GPIOA_CRH (*(volatile uint32_t *)(GPIOA_BASE + 0x04)) #define GPIOA_ODR (*(volatile uint32_t *)(GPIOA_BASE + 0x0C)) #define GPIOA_IDR (*(volatile uint32_t *)(GPIOA_BASE + 0x08)) #define GPIOB_CRL (*(volatile uint32_t *)(GPIOB_BASE + 0x00)) #define GPIOB_CRH (*(volatile uint32_t *)(GPIOB_BASE + 0x04)) #define GPIOB_ODR (*(volatile uint32_t *)(GPIOB_BASE + 0x0C)) #define GPIOB_IDR (*(volatile uint32_t *)(GPIOB_BASE + 0x08)) // USART registers #define USART1_SR (*(volatile uint32_t *)(USART1_BASE + 0x00)) #define USART1_DR (*(volatile uint32_t *)(USART1_BASE + 0x04)) #define USART1_BRR (*(volatile uint32_t *)(USART1_BASE + 0x08)) #define USART1_CR1 (*(volatile uint32_t *)(USART1_BASE + 0x0C)) #define USART1_CR2 (*(volatile uint32_t *)(USART1_BASE + 0x10)) #define USART2_SR (*(volatile uint32_t *)(USART2_BASE + 0x00)) #define USART2_DR (*(volatile uint32_t *)(USART2_BASE + 0x04)) #define USART2_BRR (*(volatile uint32_t *)(USART2_BASE + 0x08)) #define USART2_CR1 (*(volatile uint32_t *)(USART2_BASE + 0x0C)) #define USART2_CR2 (*(volatile uint32_t *)(USART2_BASE + 0x10)) // I2C registers #define I2C1_CR1 (*(volatile uint32_t *)(I2C1_BASE + 0x00)) #define I2C1_CR2 (*(volatile uint32_t *)(I2C1_BASE + 0x04)) #define I2C1_OAR1 (*(volatile uint32_t *)(I2C1_BASE + 0x08)) #define I2C1_OAR2 (*(volatile uint32_t *)(I2C1_BASE + 0x0C)) #define I2C1_DR (*(volatile uint32_t *)(I2C1_BASE + 0x10)) #define I2C1_SR1 (*(volatile uint32_t *)(I2C1_BASE + 0x14)) #define I2C1_SR2 (*(volatile uint32_t *)(I2C1_BASE + 0x18)) #define I2C1_CCR (*(volatile uint32_t *)(I2C1_BASE + 0x1C)) #define I2C1_TRISE (*(volatile uint32_t *)(I2C1_BASE + 0x20)) // Sensor addresses #define MPU6050_ADDR 0xD0 // I2C address when AD0 is low #define AS7265X_ADDR 0x49 // Default I2C address for AS7265x // Function prototypes void SystemInit(void); void GPIO_Init(void); void USART1_Init(void); void USART2_Init(void); void I2C1_Init(void); void MPU6050_Init(void); void MPU6050_ReadAccel(int16_t *accelData); void MPU6050_ReadGyro(int16_t *gyroData); void AS7265x_Init(void); void AS7265x_ReadData(uint16_t *spectralData); void GPS_ReadData(char *buffer); void ESP8266_SendData(const char *data); void delay_ms(uint32_t ms); int main(void) { SystemInit(); GPIO_Init(); USART1_Init(); // For GPS USART2_Init(); // For ESP8266 I2C1_Init(); MPU6050_Init(); AS7265x_Init(); int16_t accel[3], gyro[3]; uint16_t spectralData[18]; // AS7265x has 18 channels char gpsBuffer[100]; char sendBuffer[200]; while (1) { // Read sensor data MPU6050_ReadAccel(accel); MPU6050_ReadGyro(gyro); AS7265x_ReadData(spectralData); GPS_ReadData(gpsBuffer); // Prepare data for upload, e.g., JSON format // Simple example: combine data into a string sprintf(sendBuffer, "Accel:%d,%d,%d Gyro:%d,%d,%d Spectral:%u GPS:%s", accel[0], accel[1], accel[2], gyro[0], gyro[1], gyro[2], spectralData[0], gpsBuffer); // Send via ESP8266 to Huawei Cloud ESP8266_SendData(sendBuffer); delay_ms(1000); // Send data every second } } void SystemInit(void) { // Enable clocks for GPIOA, GPIOB, USART1, USART2, I2C1 RCC_APB2ENR |= (1 << 2) | (1 << 3) | (1 << 14); // GPIOA, GPIOB, USART1 clock enable RCC_APB1ENR |= (1 << 17) | (1 << 0); // USART2 and I2C1 clock enable } void GPIO_Init(void) { // USART1 (GPS): PA9 as TX (alternate function push-pull), PA10 as RX (input floating) GPIOA_CRH &= ~(0xFF << 4); // Clear bits for PA9 and PA10 GPIOA_CRH |= (0x0B << 4) | (0x04 << 8); // PA9: AFPP, 50MHz; PA10: Input floating // USART2 (ESP8266): PA2 as TX, PA3 as RX GPIOA_CRL &= ~(0xFF << 8); // Clear bits for PA2 and PA3 GPIOA_CRL |= (0x0B << 8) | (0x04 << 12); // PA2: AFPP, 50MHz; PA3: Input floating // I2C1: PB6 as SCL, PB7 as SDA (both alternate open drain) GPIOB_CRL &= ~(0xFF << 24); // Clear bits for PB6 and PB7 GPIOB_CRL |= (0x0F << 24) | (0x0F << 28); // PB6 and PB7: AFOD, 50MHz } void USART1_Init(void) { // USART1 for GPS: 9600 baud, 8 data bits, no parity, 1 stop bit USART1_BRR = 0x1D4C; // 72MHz / 9600 = 7500 = 0x1D4C USART1_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE } void USART2_Init(void) { // USART2 for ESP8266: 115200 baud, 8 data bits, no parity, 1 stop bit USART2_BRR = 0x0271; // 36MHz / 115200 ≈ 312.5, so BRR = 312.5 = 0x138.8, approximate to 0x139? Wait, clock is APB1 at 36MHz? // For STM32F103, USART2 is on APB1, which is half of system clock if system clock is 72MHz. Assume system clock is 72MHz, so APB1 is 36MHz. // BRR = 36000000 / 115200 = 312.5 -> integer part 312, fractional part 0.5 * 16 = 8, so BRR = (312 << 4) | 8 = 0x1388 USART2_BRR = 0x1388; // Correct calculation USART2_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE } void I2C1_Init(void) { // I2C1 initialization: standard mode (100kHz), 7-bit addressing I2C1_CR2 = 36; // APB1 clock is 36MHz, so set to 36 I2C1_CCR = 180; // CCR = APB1 clock / (2 * I2C speed) = 36MHz / (2 * 100kHz) = 180 I2C1_TRISE = 37; // TRISE = (APB1 clock / 1000000) + 1 = 36 + 1 = 37 I2C1_CR1 |= (1 << 0); // PE enable } void MPU6050_Init(void) { // Initialize MPU6050: wake up device and set sample rate I2C1_CR1 |= (1 << 8); // Generate start condition while (!(I2C1_SR1 & (1 << 0))); // Wait for SB flag I2C1_DR = MPU6050_ADDR; // Send address with write bit while (!(I2C1_SR1 & (1 << 1))); // Wait for ADDR flag (void)I2C1_SR2; // Clear ADDR by reading SR2 I2C1_DR = 0x6B; // PWR_MGMT_1 register while (!(I2C1_SR1 & (1 << 7))); // Wait for TxE I2C1_DR = 0x00; // Wake up device while (!(I2C1_SR1 & (1 << 7))); // Wait for TxE I2C1_CR1 |= (1 << 9); // Generate stop condition delay_ms(10); } void MPU6050_ReadAccel(int16_t *accelData) { // Read accelerometer data from registers 0x3B to 0x40 I2C1_CR1 |= (1 << 8); // Start while (!(I2C1_SR1 & (1 << 0))); // SB I2C1_DR = MPU6050_ADDR; // Address with write while (!(I2C1_SR1 & (1 << 1))); // ADDR (void)I2C1_SR2; I2C1_DR = 0x3B; // Start register while (!(I2C1_SR1 & (1 << 7))); // TxE I2C1_CR1 |= (1 << 8); // Repeated start while (!(I2C1_SR1 & (1 << 0))); // SB I2C1_DR = MPU6050_ADDR | 0x01; // Address with read while (!(I2C1_SR1 & (1 << 1))); // ADDR (void)I2C1_SR2; for (uint8_t i = 0; i < 3; i++) { while (!(I2C1_SR1 & (1 << 6))); // RxNE accelData[i] = (int16_t)(I2C1_DR << 8); while (!(I2C1_SR1 & (1 << 6))); // RxNE accelData[i] |= I2C1_DR; } I2C1_CR1 |= (1 << 9); // Stop } void MPU6050_ReadGyro(int16_t *gyroData) { // Read gyroscope data from registers 0x43 to 0x48 I2C1_CR1 |= (1 << 8); // Start while (!(I2C1_SR1 & (1 << 0))); // SB I2C1_DR = MPU6050_ADDR; // Address with write while (!(I2C1_SR1 & (1 << 1))); // ADDR (void)I2C1_SR2; I2C1_DR = 0x43; // Start register while (!(I2C1_SR1 & (1 << 7))); // TxE I2C1_CR1 |= (1 << 8); // Repeated start while (!(I2C1_SR1 & (1 << 0))); // SB I2C1_DR = MPU6050_ADDR | 0x01; // Address with read while (!(I2C1_SR1 & (1 << 1))); // ADDR (void)I2C1_SR2; for (uint8_t i = 0; i < 3; i++) { while (!(I2C1_SR1 & (1 << 6))); // RxNE gyroData[i] = (int16_t)(I2C1_DR << 8); while (!(I2C1_SR1 & (1 << 6))); // RxNE gyroData[i] |= I2C1_DR; } I2C1_CR1 |= (1 << 9); // Stop } void AS7265x_Init(void) { // Initialize AS7265x: set mode or enable sensors // Example: set to continuous reading mode I2C1_CR1 |= (1 << 8); // Start while (!(I2C1_SR1 & (1 << 0))); // SB I2C1_DR = AS7265X_ADDR; // Address with write while (!(I2C1_SR1 & (1 << 1))); // ADDR (void)I2C1_SR2; I2C1_DR = 0x04; // Control register for mode while (!(I2C1_SR1 & (1 << 7))); // TxE I2C1_DR = 0x02; // Continuous reading mode while (!(I2C1_SR1 & (1 << 7))); // TxE I2C1_CR1 |= (1 << 9); // Stop delay_ms(10); } void AS7265x_ReadData(uint16_t *spectralData) { // Read spectral data from AS7265x // Assume we read 18 channels from specific registers for (uint8_t reg = 0; reg < 18; reg++) { I2C1_CR1 |= (1 << 8); // Start while (!(I2C1_SR1 & (1 << 0))); // SB I2C1_DR = AS7265X_ADDR; // Address with write while (!(I2C1_SR1 & (1 << 1))); // ADDR (void)I2C1_SR2; I2C1_DR = 0x08 + reg; // Data register for channel, example starting at 0x08 while (!(I2C1_SR1 & (1 << 7))); // TxE I2C1_CR1 |= (1 << 8); // Repeated start while (!(I2C1_SR1 & (1 << 0))); // SB I2C1_DR = AS7265X_ADDR | 0x01; // Address with read while (!(I2C1_SR1 & (1 << 1))); // ADDR (void)I2C1_SR2; while (!(I2C1_SR1 & (1 << 6))); // RxNE spectralData[reg] = I2C1_DR; I2C1_CR1 |= (1 << 9); // Stop } } void GPS_ReadData(char *buffer) { // Read data from USART1 (GPS) until buffer is full or newline uint8_t index = 0; while (1) { while (!(USART1_SR & (1 << 5))); // Wait for RXNE char c = USART1_DR; if (c == '\n' || index >= 99) { buffer[index] = '\0'; break; } buffer[index++] = c; } } void ESP8266_SendData(const char *data) { // Send data to ESP8266 via USART2 using AT commands // Example: send "AT+CIPSEND=<length>" then data // First, ensure ESP8266 is connected to Huawei Cloud via MQTT or HTTP, but simplified here USART2_DR = 'A'; while (!(USART2_SR & (1 << 7))); // Wait for TXE // Simple send: assume already configured, just send data while (*data) { USART2_DR = *data++; while (!(USART2_SR & (1 << 7))); // Wait for TXE } } void delay_ms(uint32_t ms) { // Simple delay function using SysTick or busy wait for (uint32_t i = 0; i < ms * 7200; i++); // Approximate for 72MHz } 项目核心代码#include "stm32f10x.h" // 假设其他模块的驱动函数已经定义 extern void MPU6050_Init(void); extern void MPU6050_Read_Data(float *pitch, float *roll, float *yaw); extern void AS7265x_Init(void); extern void AS7265x_Read_Data(float *violet, float *blue, float *green, float *yellow, float *orange, float *red); extern void GPS_Init(void); extern void GPS_Read_Data(float *lat, float *lon, float *alt); extern void ESP8266_Init(void); extern void ESP8266_Send_Data(char *data); // 系统时钟初始化函数(使用HSE,PLL到72MHz) void SystemClock_Init(void) { // 启用HSE RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); // 配置PLL:HSE作为输入,倍频到72MHz RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9; RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); // 配置闪存延迟 FLASH->ACR |= FLASH_ACR_LATENCY_2; // 切换系统时钟到PLL RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 设置AHB、APB1、APB2分频 RCC->CFGR |= RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV2 | RCC_CFGR_PPRE2_DIV1; } // GPIO初始化函数 void GPIO_Init(void) { // 启用GPIOA、GPIOB时钟(根据实际连接调整) RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; // 配置USART1 TX (PA9) 为推挽输出,50MHz GPIOA->CRH &= ~(GPIO_CRH_CNF9 | GPIO_CRH_MODE9); GPIOA->CRH |= GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9; // 配置USART1 RX (PA10) 为浮空输入 GPIOA->CRH &= ~(GPIO_CRH_CNF10 | GPIO_CRH_MODE10); GPIOA->CRH |= GPIO_CRH_CNF10_0; // 配置I2C1 SCL (PB6) 和 SDA (PB7) 为开漏输出,50MHz GPIOB->CRL &= ~(GPIO_CRL_CNF6 | GPIO_CRL_MODE6 | GPIO_CRL_CNF7 | GPIO_CRL_MODE7); GPIOB->CRL |= GPIO_CRL_CNF6_1 | GPIO_CRL_MODE6 | GPIO_CRL_CNF7_1 | GPIO_CRL_MODE7; // 配置USART2 TX (PA2) 用于ESP8266 GPIOA->CRL &= ~(GPIO_CRL_CNF2 | GPIO_CRL_MODE2); GPIOA->CRL |= GPIO_CRL_CNF2_1 | GPIO_CRL_MODE2; // 配置USART2 RX (PA3) 用于ESP8266 GPIOA->CRL &= ~(GPIO_CRL_CNF3 | GPIO_CRL_MODE3); GPIOA->CRL |= GPIO_CRL_CNF3_0; } // USART1初始化(用于GPS) void USART1_Init(void) { RCC->APB2ENR |= RCC_APB2ENR_USART1EN; USART1->BRR = 72000000 / 9600; // 9600 baud USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; } // USART2初始化(用于ESP8266) void USART2_Init(void) { RCC->APB1ENR |= RCC_APB1ENR_USART2EN; USART2->BRR = 72000000 / 115200; // 115200 baud USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; } // I2C1初始化(用于MPU6050和AS7265x) void I2C1_Init(void) { RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; I2C1->CR2 = 36; // 36MHz APB1 clock I2C1->CCR = 180; // 100kHz standard mode I2C1->TRISE = 37; // 1000ns rise time I2C1->CR1 |= I2C_CR1_PE; } // SysTick初始化用于延时 void SysTick_Init(void) { SysTick->LOAD = 72000 - 1; // 1ms中断 SysTick->VAL = 0; SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk; } // 毫秒延时函数 void delay_ms(uint32_t ms) { for (uint32_t i = 0; i < ms; i++) { while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); } } int main(void) { // 初始化系统时钟、GPIO、外设 SystemClock_Init(); GPIO_Init(); USART1_Init(); USART2_Init(); I2C1_Init(); SysTick_Init(); // 初始化传感器和模块 MPU6050_Init(); AS7265x_Init(); GPS_Init(); ESP8266_Init(); // 变量定义 float pitch, roll, yaw; float lat, lon, alt; float violet, blue, green, yellow, orange, red; char data_buffer[256]; while (1) { // 读取传感器数据 MPU6050_Read_Data(&pitch, &roll, &yaw); AS7265x_Read_Data(&violet, &blue, &green, &yellow, &orange, &red); GPS_Read_Data(&lat, &lon, &alt); // 格式化数据为JSON字符串 sprintf(data_buffer, "{\"lat\":%.6f,\"lon\":%.6f,\"alt\":%.2f,\"pitch\":%.2f,\"roll\":%.2f,\"yaw\":%.2f,\"violet\":%.2f,\"blue\":%.2f,\"green\":%.2f,\"yellow\":%.2f,\"orange\":%.2f,\"red\":%.2f}", lat, lon, alt, pitch, roll, yaw, violet, blue, green, yellow, orange, red); // 通过ESP8266发送数据到华为云 ESP8266_Send_Data(data_buffer); // 延时5秒 delay_ms(5000); } } 总结本系统基于STM32F103C8T6核心控制器,成功设计并实现了一套智能农业无人机监控系统,能够高效地实时监测无人机的飞行状态和作业参数,包括通过MPU6050传感器获取姿态数据,以及利用AS7265x多光谱传感器采集农田的植被指数,为精准农业提供关键数据支持。硬件组成上,系统整合了ATGM336H GPS模块进行精确定位,ESP8266-01S Wi-Fi模块实现与华为云平台的稳定通信,确保作业数据实时上传;同时,通过洞洞板焊接接口电路和杜邦线连接各模块,体现了模块化设计和成本效益,提升了系统的可靠性和扩展性。总体而言,该系统不仅实现了无人机作业轨迹和农田生长态势在QT上位机上的可视化显示,还通过云平台集成,为农业管理提供了数字化解决方案,有望提升农业生产效率和可持续性,具有广泛的应用前景。
  • [技术干货] 基于STM32设计的智能盲人辅助导航系统
    项目开发背景随着社会对特殊群体关怀意识的不断提升,盲人群体的出行安全问题日益受到广泛关注。据统计,全球约有2.5亿视力障碍者,他们在日常行走中常面临碰撞、迷失方向等风险。传统盲杖虽能提供基础触觉反馈,但存在探测范围有限、无法预警空中障碍物、缺乏数据记录功能等局限性。近年来物联网技术和智能传感器的发展为解决这一问题提供了新的思路。通过融合超声波测距、多模态人机交互和云平台技术,可构建具有实时环境感知能力的辅助系统。这类系统不仅能扩展盲人的空间感知维度,还能通过数据积累为城市无障碍设施规划提供参考依据。本项目基于STM32微控制器设计智能导航系统,旨在通过高精度超声波传感器实现障碍物三维感知,结合振动与语音的双重反馈机制适应不同使用场景,同时借助物联网技术实现行为数据云端化管理。系统既弥补了传统辅助工具的不足,又为视障人群提供了兼具安全性、实时性和可追溯性的现代化出行解决方案。设计实现的功能(1)实时采集前方障碍物距离信息并进行危险等级判断(2)通过振动马达和语音模块提供多模式导航提示(3)行走轨迹和遇障数据上传至华为云平台(4)QT上位机显示行走路径地图和遇障统计信息项目硬件模块组成(1)STM32F103C8T6最小系统核心板(2)HC-SR04超声波传感器(3)JL-03语音模块(4)振动马达模块(5)ESP8266-01S Wi-Fi模块(6)洞洞板焊接传感器接口电路和杜邦线设计意义该智能盲人辅助导航系统通过集成STM32微控制器和多种传感器模块,旨在显著提升盲人用户的独立出行能力和安全性。系统实时采集前方障碍物距离信息并进行危险等级判断,帮助用户及时感知环境变化,减少碰撞风险,从而增强户外活动的信心和自主性。系统采用振动马达和语音模块提供多模式导航提示,这种设计考虑了盲人用户的不同感知需求和环境适应性。触觉反馈通过振动强度传达障碍物距离,而语音提示则提供更详细的导航信息,确保用户在嘈杂或安静环境中都能获得有效引导,提升用户体验的包容性和实用性。通过ESP8266 Wi-Fi模块将行走轨迹和遇障数据上传至华为云平台,并结合QT上位机显示地图和统计信息,该系统实现了数据的远程监控和分析。这使得 caregivers 或研究人员能够追踪用户行为模式,识别高频遇障区域,并为后续系统优化或城市无障碍设施规划提供数据支持,具有重要的社会应用价值。硬件组成基于STM32F103C8T6核心板和常见模块如HC-SR04超声波传感器,设计注重成本效益和可扩展性。洞洞板焊接和杜邦线连接确保了系统的灵活性和易于维护,为未来功能升级或定制化应用奠定了基础,体现了嵌入式系统在辅助技术中的实用性和创新性。设计思路该系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。通过HC-SR04超声波传感器实时采集前方障碍物的距离信息,STM32对采集到的数据进行处理,并根据预设阈值判断危险等级,例如将距离分为高、中、低风险级别,以便及时提供导航提示。对于导航提示,系统集成振动马达模块和JL-03语音模块,实现多模式反馈。当检测到障碍物时,STM32根据危险等级控制振动马达产生不同强度的振动,同时语音模块播放相应的警告或导航语音,确保盲人用户能通过触觉和听觉获取实时环境信息,增强行走安全性。数据上传部分通过ESP8266-01S Wi-Fi模块实现,STM32将采集到的行走轨迹和遇障数据打包,通过Wi-Fi模块发送至华为云平台。这一过程确保数据远程存储和可访问性,为后续分析提供基础。上位机显示基于QT开发,通过华为云平台获取数据,并在软件界面中可视化显示行走路径地图和遇障统计信息。这使得 caregivers 或用户自身可以回顾行走历史和分析障碍 patterns,但上位机部分独立于嵌入式系统,依赖于云数据。硬件连接采用洞洞板焊接传感器接口电路,并使用杜邦线连接各模块,确保系统紧凑可靠。STM32通过GPIO和UART等接口与传感器和模块通信,实现低功耗和实时响应。整个设计注重实用性和成本效益,符合盲人辅助导航的实际需求。框架图+------------------------+ +-----------------------------+ | HC-SR04 Ultrasound |-----| STM32F103C8T6 | | Sensor (GPIO) | | Main Controller | +------------------------+ | - Process distance data | | - Danger level judgment | +------------------------+ | - Control outputs | | JL-03 Voice Module |-----| | | (UART) | | | +------------------------+ | | | | +------------------------+ | | | Vibration Motor Module |-----| | | (GPIO) | | | +------------------------+ +----------------------------+ | | (UART) | +----------------------------+ | ESP8266-01S Wi-Fi Module | | (Communicate with Cloud) | +----------------------------+ | | (Wi-Fi, MQTT/HTTP) | +----------------------------+ | Huawei Cloud Platform | | - Store trajectory data | | - Store obstacle data | +----------------------------+ | | (API/WebSocket) | +----------------------------+ | QT Upper Computer | | - Display path map | | - Show obstacle statistics | +----------------------------+ 系统总体设计系统总体设计基于STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。该系统通过HC-SR04超声波传感器实时采集前方障碍物的距离信息,主控制器对采集到的数据进行处理,根据预设阈值进行危险等级判断,例如将距离分为安全、警告和危险等级别。处理后的数据用于驱动导航反馈机制,通过振动马达模块提供触觉提示,例如不同频率的振动表示不同危险级别,同时JL-03语音模块提供语音导航提示,如播放障碍物距离和方向信息,实现多模式导航辅助。这些反馈机制帮助盲人用户感知周围环境,提高行走安全性。系统还集成ESP8266-01S Wi-Fi模块,用于将行走轨迹和遇障数据上传至华为云平台。主控制器通过串口通信与Wi-Fi模块交互,封装数据并发送到云平台,实现远程数据存储和监控。同时,QT上位机软件从云平台获取数据,显示用户的行走路径地图和遇障统计信息,便于 caregivers 或管理员进行数据分析和管理。硬件连接方面,所有传感器和模块通过洞洞板焊接的接口电路和杜邦线连接到STM32核心板,确保系统结构紧凑且可靠。整个设计注重实用性和稳定性,以实际需求为导向,无需额外功能添加。系统功能总结功能描述实现方式实时采集前方障碍物距离信息并进行危险等级判断HC-SR04超声波传感器检测距离,STM32F103C8T6处理数据并判断危险等级通过振动马达和语音模块提供多模式导航提示振动马达模块提供触觉反馈,JL-03语音模块提供语音提示行走轨迹和遇障数据上传至华为云平台ESP8266-01S Wi-Fi模块实现无线通信,将数据上传至华为云QT上位机显示行走路径地图和遇障统计信息基于华为云平台数据,QT应用程序解析并显示路径地图和统计信息设计的各个功能模块描述STM32F103C8T6最小系统核心板作为主控制器,负责整个系统的协调与控制。它实时处理来自传感器的数据,进行障碍物距离的危险等级判断,并根据结果驱动振动马达和语音模块提供反馈。同时,主控制器管理Wi-Fi模块的数据上传功能,确保系统高效运行。HC-SR04超声波传感器用于检测前方障碍物的距离信息。该传感器通过发射和接收超声波信号,计算与障碍物之间的距离,并将数据实时传输给STM32主控制器,以支持危险评估和导航决策。JL-03语音模块接收STM32的指令,提供语音导航提示。根据障碍物危险等级或系统状态,模块播放预录的语音消息,如警告音或方向指示,帮助用户感知周围环境。振动马达模块提供触觉导航反馈,通过不同的振动模式和强度来表示危险等级或导航信息。STM32控制马达的振动行为,使得用户可以通过触觉感知障碍物 proximity 或系统提示。ESP8266-01S Wi-Fi模块实现与华为云平台的通信。它将STM32处理后的行走轨迹和遇障数据通过Wi-Fi网络上传输至云平台,支持远程数据存储和监控,为上位机显示提供数据源。硬件连接方面,使用洞洞板焊接传感器接口电路,并通过杜邦线连接各模块,包括STM32、超声波传感器、语音模块、振动马达和Wi-Fi模块。这种设计确保各组件稳定互联,便于系统集成和调试。QT上位机软件基于华为云平台的数据,显示行走路径地图和遇障统计信息。它提供可视化界面,帮助用户或管理员查看历史轨迹和障碍事件,增强系统的监控和分析能力。上位机代码设计以下是基于QT框架的上位机代码设计,用于显示智能盲人辅助导航系统的行走路径地图和遇障统计信息。代码采用C++开发,包括主窗口类、网络数据获取、JSON解析和UI显示。假设数据从华为云平台通过HTTP GET请求获取,返回JSON格式的轨迹和障碍数据。项目文件结构: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 <QNetworkAccessManager> #include <QNetworkReply> #include <QGraphicsScene> #include <QGraphicsView> #include <QJsonDocument> #include <QJsonArray> #include <QJsonObject> #include <QLabel> #include <QTimer> class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void fetchData(); // 获取数据 void onTrackDataReceived(QNetworkReply *reply); // 处理轨迹数据 void onObstacleDataReceived(QNetworkReply *reply); // 处理障碍数据 void updateDisplay(); // 更新显示 private: QNetworkAccessManager *networkManager; QGraphicsScene *scene; // 用于绘制地图 QGraphicsView *view; // 显示地图 QLabel *statsLabel; // 显示统计信息 QTimer *timer; // 定时器用于定期更新数据 // 假设的数据结构 QList<QPointF> trackPoints; // 存储轨迹点 QList<QPair<QDateTime, double>> obstacles; // 存储障碍数据:时间和距离 void parseTrackData(const QByteArray &data); // 解析轨迹JSON void parseObstacleData(const QByteArray &data); // 解析障碍JSON void drawPath(); // 绘制路径 void updateStats(); // 更新统计信息 }; #endif // MAINWINDOW_H 3. mainwindow.cpp#include "mainwindow.h" #include <QVBoxLayout> #include <QHBoxLayout> #include <QGraphicsLineItem> #include <QDateTimeAxis> #include <QValueAxis> #include <QChartView> #include <QChart> #include <QDebug> // 华为云API端点(示例URL,需根据实际修改) const QString TRACK_API_URL = "http://your-huawei-cloud-api/track"; const QString OBSTACLE_API_URL = "http://your-huawei-cloud-api/obstacles"; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // 设置窗口 setWindowTitle("智能盲人辅助导航系统上位机"); setGeometry(100, 100, 800, 600); // 初始化网络管理器 networkManager = new QNetworkAccessManager(this); // 初始化UI QWidget *centralWidget = new QWidget(this); setCentralWidget(centralWidget); QVBoxLayout *layout = new QVBoxLayout(centralWidget); // 地图显示区域 scene = new QGraphicsScene(this); view = new QGraphicsView(scene); view->setRenderHint(QPainter::Antialiasing); view->setSceneRect(0, 0, 600, 400); layout->addWidget(view); // 统计信息标签 statsLabel = new QLabel("统计信息加载中...", this); layout->addWidget(statsLabel); // 定时器,每5秒更新一次数据 timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &MainWindow::fetchData); timer->start(5000); // 5秒间隔 // 初始获取数据 fetchData(); } MainWindow::~MainWindow() { // 清理资源 } void MainWindow::fetchData() { // 获取轨迹数据 QNetworkRequest trackRequest(QUrl(TRACK_API_URL)); QNetworkReply *trackReply = networkManager->get(trackRequest); connect(trackReply, &QNetworkReply::finished, this, [this, trackReply]() { onTrackDataReceived(trackReply); }); // 获取障碍数据 QNetworkRequest obstacleRequest(QUrl(OBSTACLE_API_URL)); QNetworkReply *obstacleReply = networkManager->get(obstacleRequest); connect(obstacleReply, &QNetworkReply::finished, this, [this, obstacleReply]() { onObstacleDataReceived(obstacleReply); }); } void MainWindow::onTrackDataReceived(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); parseTrackData(data); } else { qDebug() << "轨迹数据请求错误:" << reply->errorString(); } reply->deleteLater(); updateDisplay(); // 更新显示 } void MainWindow::onObstacleDataReceived(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); parseObstacleData(data); } else { qDebug() << "障碍数据请求错误:" << reply->errorString(); } reply->deleteLater(); updateDisplay(); // 更新显示 } void MainWindow::parseTrackData(const QByteArray &data) { trackPoints.clear(); QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull() || !doc.isObject()) { qDebug() << "无效的轨迹JSON数据"; return; } QJsonObject obj = doc.object(); if (obj.contains("points") && obj["points"].isArray()) { QJsonArray pointsArray = obj["points"].toArray(); for (const QJsonValue &value : pointsArray) { if (value.isObject()) { QJsonObject pointObj = value.toObject(); double x = pointObj["x"].toDouble(); double y = pointObj["y"].toDouble(); trackPoints.append(QPointF(x, y)); } } } } void MainWindow::parseObstacleData(const QByteArray &data) { obstacles.clear(); QJsonDocument doc = QJsonDocument::fromJson(data); if (doc.isNull() || !doc.isObject()) { qDebug() << "无效的障碍JSON数据"; return; } QJsonObject obj = doc.object(); if (obj.contains("obstacles") && obj["obstacles"].isArray()) { QJsonArray obstaclesArray = obj["obstacles"].toArray(); for (const QJsonValue &value : obstaclesArray) { if (value.isObject()) { QJsonObject obstacleObj = value.toObject(); double distance = obstacleObj["distance"].toDouble(); QString timeStr = obstacleObj["time"].toString(); QDateTime time = QDateTime::fromString(timeStr, "yyyy-MM-dd hh:mm:ss"); obstacles.append(qMakePair(time, distance)); } } } } void MainWindow::updateDisplay() { drawPath(); updateStats(); } void MainWindow::drawPath() { scene->clear(); // 清除之前绘制 if (trackPoints.isEmpty()) return; // 缩放和偏移以适应视图 double minX = trackPoints.first().x(), maxX = minX; double minY = trackPoints.first().y(), maxY = minY; for (const QPointF &point : trackPoints) { if (point.x() < minX) minX = point.x(); if (point.x() > maxX) maxX = point.x(); if (point.y() < minY) minY = point.y(); if (point.y() > maxY) maxY = point.y(); } double scaleX = view->width() / (maxX - minX + 1); double scaleY = view->height() / (maxY - minY + 1); double scale = qMin(scaleX, scaleY) * 0.8; // 缩放因子,留边距 // 绘制路径 QPen pen(Qt::blue); pen.setWidth(2); for (int i = 1; i < trackPoints.size(); ++i) { QPointF p1 = trackPoints[i-1]; QPointF p2 = trackPoints[i]; // 应用缩放和偏移 p1 = QPointF((p1.x() - minX) * scale, (p1.y() - minY) * scale); p2 = QPointF((p2.x() - minX) * scale, (p2.y() - minY) * scale); scene->addLine(QLineF(p1, p2), pen); } // 绘制障碍点(用红色标记) QPen obstaclePen(Qt::red); obstaclePen.setWidth(5); for (const auto &obstacle : obstacles) { // 假设障碍点对应轨迹点,这里简化处理:使用最后一个点或特定逻辑 // 实际应根据时间匹配轨迹点,这里仅示例 if (!trackPoints.isEmpty()) { QPointF point = trackPoints.last(); // 简化:用最后一个点代表障碍位置 point = QPointF((point.x() - minX) * scale, (point.y() - minY) * scale); scene->addEllipse(point.x() - 2, point.y() - 2, 4, 4, obstaclePen); } } } void MainWindow::updateStats() { int obstacleCount = obstacles.size(); double totalDistance = 0.0; if (!trackPoints.isEmpty()) { for (int i = 1; i < trackPoints.size(); ++i) { totalDistance += QLineF(trackPoints[i-1], trackPoints[i]).length(); } } QString statsText = QString("总行走距离: %1 米\n遇障次数: %2\n最后更新: %3") .arg(totalDistance, 0, 'f', 2) .arg(obstacleCount) .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); statsLabel->setText(statsText); } 使用说明:在QT Creator中创建新项目,将上述代码文件添加到项目中。在project.pro文件中确保添加网络模块:QT += network。根据实际华为云API修改TRACK_API_URL和OBSTACLE_API_URL。编译并运行程序。上位机将定期从云平台获取数据并显示路径和统计信息。此代码提供了基本功能,实际应用中可能需要根据数据格式和业务逻辑进行调整。模块代码设计#include <stdint.h> #include <string.h> // RCC registers #define RCC_BASE 0x40021000 #define RCC_APB2ENR (*((volatile uint32_t *)(RCC_BASE + 0x18))) #define RCC_APB1ENR (*((volatile uint32_t *)(RCC_BASE + 0x1C))) // GPIOA registers #define GPIOA_BASE 0x40010800 #define GPIOA_CRL (*((volatile uint32_t *)(GPIOA_BASE + 0x00))) #define GPIOA_CRH (*((volatile uint32_t *)(GPIOA_BASE + 0x04))) #define GPIOA_IDR (*((volatile uint32_t *)(GPIOA_BASE + 0x08))) #define GPIOA_ODR (*((volatile uint32_t *)(GPIOA_BASE + 0x0C))) // USART1 registers #define USART1_BASE 0x40013800 #define USART1_SR (*((volatile uint32_t *)(USART1_BASE + 0x00))) #define USART1_DR (*((volatile uint32_t *)(USART1_BASE + 0x04))) #define USART1_BRR (*((volatile uint32_t *)(USART1_BASE + 0x08))) #define USART1_CR1 (*((volatile uint32_t *)(USART1_BASE + 0x0C))) // USART2 registers #define USART2_BASE 0x40004400 #define USART2_SR (*((volatile uint32_t *)(USART2_BASE + 0x00))) #define USART2_DR (*((volatile uint32_t *)(USART2_BASE + 0x04))) #define USART2_BRR (*((volatile uint32_t *)(USART2_BASE + 0x08))) #define USART2_CR1 (*((volatile uint32_t *)(USART2_BASE + 0x0C))) // TIM2 registers #define TIM2_BASE 0x40000000 #define TIM2_CR1 (*((volatile uint32_t *)(TIM2_BASE + 0x00))) #define TIM2_CR2 (*((volatile uint32_t *)(TIM2_BASE + 0x04))) #define TIM2_SMCR (*((volatile uint32_t *)(TIM2_BASE + 0x08))) #define TIM2_DIER (*((volatile uint32_t *)(TIM2_BASE + 0x0C))) #define TIM2_SR (*((volatile uint32_t *)(TIM2_BASE + 0x10))) #define TIM2_EGR (*((volatile uint32_t *)(TIM2_BASE + 0x14))) #define TIM2_CCMR1 (*((volatile uint32_t *)(TIM2_BASE + 0x18))) #define TIM2_CCMR2 (*((volatile uint32_t *)(TIM2_BASE + 0x1C))) #define TIM2_CCER (*((volatile uint32_t *)(TIM2_BASE + 0x20))) #define TIM2_CNT (*((volatile uint32_t *)(TIM2_BASE + 0x24))) #define TIM2_PSC (*((volatile uint32_t *)(TIM2_BASE + 0x28))) #define TIM2_ARR (*((volatile uint32_t *)(TIM2_BASE + 0x2C))) #define TIM2_CCR1 (*((volatile uint32_t *)(TIM2_BASE + 0x34))) #define TIM2_CCR2 (*((volatile uint32_t *)(TIM2_BASE + 0x38))) // Bit definitions #define USART_SR_TXE (1 << 7) #define USART_SR_RXNE (1 << 5) #define RCC_APB2ENR_IOPAEN (1 << 2) #define RCC_APB2ENR_USART1EN (1 << 14) #define RCC_APB1ENR_USART2EN (1 << 17) #define RCC_APB1ENR_TIM2EN (1 << 0) // Pin definitions #define TRIG_PIN 0 // PA0 #define ECHO_PIN 1 // PA1 #define MOTOR_PIN 4 // PA4 void SystemInit(void) { RCC_APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN; RCC_APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_TIM2EN; } void GPIO_Init(void) { // PA0 as output push-pull 50MHz for Trig GPIOA_CRL &= ~(0xF << (4 * TRIG_PIN)); GPIOA_CRL |= (0x3 << (4 * TRIG_PIN)); // PA1 as input floating for Echo GPIOA_CRL &= ~(0xF << (4 * ECHO_PIN)); GPIOA_CRL |= (0x4 << (4 * ECHO_PIN)); // PA4 as output push-pull 50MHz for vibration motor GPIOA_CRL &= ~(0xF << (4 * MOTOR_PIN)); GPIOA_CRL |= (0x3 << (4 * MOTOR_PIN)); // PA9 as USART1 TX, alternate function push-pull GPIOA_CRH &= ~(0xF << 4); GPIOA_CRH |= (0xA << 4); // PA10 as USART1 RX, input floating GPIOA_CRH &= ~(0xF << 8); GPIOA_CRH |= (0x4 << 8); // PA2 as USART2 TX, alternate function push-pull GPIOA_CRL &= ~(0xF << 8); GPIOA_CRL |= (0xA << 8); // PA3 as USART2 RX, input floating GPIOA_CRL &= ~(0xF << 12); GPIOA_CRL |= (0x4 << 12); } void USART1_Init(void) { USART1_BRR = 0x3415; // 9600 baud at 8MHz USART1_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE } void USART2_Init(void) { USART2_BRR = 0x3415; // 9600 baud at 8MHz USART2_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE } void TIM2_Init(void) { TIM2_PSC = 7; // 1MHz timer clock TIM2_ARR = 0xFFFF; TIM2_CCMR1 &= ~(0x3 << 8); TIM2_CCMR1 |= (0x1 << 8); // CC2S = 01, input TIM2_CCMR1 &= ~(0xF << 12); // IC2F = 0000 TIM2_CCMR1 &= ~(0x3 << 10); // IC2PSC = 00 TIM2_CCER &= ~(1 << 5); // CC2P = 0, rising edge TIM2_CCER &= ~(1 << 7); // CC2NP = 0 TIM2_CCER |= (1 << 4); // CC2E enable TIM2_CR1 |= (1 << 0); // Enable TIM2 } void USART1_SendChar(char c) { while (!(USART1_SR & USART_SR_TXE)); USART1_DR = c; } void USART1_SendString(const char *str) { while (*str) { USART1_SendChar(*str++); } } void USART2_SendChar(char c) { while (!(USART2_SR & USART_SR_TXE)); USART2_DR = c; } void USART2_SendString(const char *str) { while (*str) { USART2_SendChar(*str++); } } char USART2_ReceiveChar(void) { while (!(USART2_SR & USART_SR_RXNE)); return (char)USART2_DR; } void Vibrate(int on) { if (on) { GPIOA_ODR |= (1 << MOTOR_PIN); } else { GPIOA_ODR &= ~(1 << MOTOR_PIN); } } void PlayVoice(int num) { char cmd[20]; // Simple command format, adjust based on JL-03 module sprintf(cmd, "PLAY:%d\r\n", num); USART1_SendString(cmd); } uint32_t GetDistance(void) { GPIOA_ODR |= (1 << TRIG_PIN); for (volatile int i = 0; i < 10; i++); // ~10us delay GPIOA_ODR &= ~(1 << TRIG_PIN); while (!(GPIOA_IDR & (1 << ECHO_PIN))); TIM2_CNT = 0; while (GPIOA_IDR & (1 << ECHO_PIN)); uint32_t time_us = TIM2_CNT; return time_us / 58; // Distance in cm } void ESP8266_SendATCommand(const char *cmd) { USART2_SendString(cmd); USART2_SendString("\r\n"); } int ESP8266_WaitForResponse(const char *response, uint32_t timeout) { uint32_t start_time = TIM2_CNT; char buffer[100] = {0}; int index = 0; while (TIM2_CNT - start_time < timeout) { if (USART2_SR & USART_SR_RXNE) { char c = USART2_ReceiveChar(); buffer[index++] = c; if (index >= 99) index = 0; if (strstr(buffer, response) != NULL) { return 1; } } } return 0; } void ESP8266_Init(void) { ESP8266_SendATCommand("AT"); ESP8266_WaitForResponse("OK", 1000000); ESP8266_SendATCommand("AT+CWMODE=1"); ESP8266_WaitForResponse("OK", 1000000); // Replace with actual Wi-Fi credentials ESP8266_SendATCommand("AT+CWJAP=\"SSID\",\"password\""); ESP8266_WaitForResponse("OK", 5000000); } void UploadToCloud(uint32_t distance, int obstacle) { ESP8266_SendATCommand("AT+CIPSTART=\"TCP\",\"cloud.huawei.com\",80"); if (ESP8266_WaitForResponse("OK", 1000000)) { char post_data[100]; sprintf(post_data, "POST /api/data HTTP/1.1\r\nHost: cloud.huawei.com\r\nContent-Type: application/json\r\nContent-Length: 50\r\n\r\n{\"distance\":%lu,\"obstacle\":%d}", distance, obstacle); ESP8266_SendATCommand("AT+CIPSEND=100"); ESP8266_WaitForResponse(">", 1000000); USART2_SendString(post_data); ESP8266_WaitForResponse("SEND OK", 1000000); ESP8266_SendATCommand("AT+CIPCLOSE"); } } int main(void) { SystemInit(); GPIO_Init(); USART1_Init(); USART2_Init(); TIM2_Init(); ESP8266_Init(); uint32_t last_upload = 0; while (1) { uint32_t dist = GetDistance(); int danger_level = 0; if (dist < 50) { danger_level = 2; Vibrate(1); PlayVoice(1); } else if (dist < 100) { danger_level = 1; Vibrate(1); PlayVoice(2); } else { Vibrate(0); } if (TIM2_CNT - last_upload > 5000000) { UploadToCloud(dist, danger_level); last_upload = TIM2_CNT; } for (volatile int i = 0; i < 100000; i++); } } 项目核心代码#include "stm32f10x.h" // 寄存器定义头文件 #include "delay.h" // 假设有延时模块,提供Delay_ms函数 #include "ultrasonic.h" // 假设有超声波模块,提供Ultrasonic_Init和Ultrasonic_GetDistance #include "voice.h" // 假设有语音模块,提供Voice_Init和Voice_Play #include "vibrate.h" // 假设有振动模块,提供Vibrate_Init和Vibrate #include "wifi.h" // 假设有Wi-Fi模块,提供WiFi_Init和WiFi_SendData // 定义危险等级常量 #define DANGER_LEVEL_SAFE 0 #define DANGER_LEVEL_WARNING 1 #define DANGER_LEVEL_DANGER 2 // 定义距离阈值(单位:厘米) #define DISTANCE_DANGER 50.0f #define DISTANCE_WARNING 100.0f // 全局变量用于存储距离和危险等级 volatile uint32_t distance_cm = 0; volatile uint8_t danger_level = DANGER_LEVEL_SAFE; /** * @brief 系统时钟初始化函数(使用寄存器方式设置时钟到72MHz) * @param 无 * @retval 无 */ void SystemClock_Init(void) { // 启用HSE(外部高速时钟) RCC->CR |= ((uint32_t)RCC_CR_HSEON); while (!(RCC->CR & RCC_CR_HSERDY)); // 配置PLL:HSE作为源,9倍频(8MHz * 9 = 72MHz) RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9; // 启用PLL RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); // 设置FLASH延迟(2等待状态,因为72MHz > 48MHz) FLASH->ACR |= FLASH_ACR_LATENCY_2; // 切换系统时钟到PLL RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 设置AHB、APB1、APB2分频(HCLK=72MHz, PCLK1=36MHz, PCLK2=72MHz) RCC->CFGR |= RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV2 | RCC_CFGR_PPRE2_DIV1; } /** * @brief 系统初始化函数 * @param 无 * @retval 无 */ void SystemInit(void) { // 设置向量表偏移(可选,通常默认) SCB->VTOR = FLASH_BASE; // 初始化系统时钟 SystemClock_Init(); // 初始化SysTick定时器用于延时(假设SysTick_Init在delay.h中实现,但这里简单处理) // 假设Delay_ms函数基于SysTick或软件延时 } int main(void) { // 系统初始化 SystemInit(); // 初始化各模块 Ultrasonic_Init(); // 初始化超声波传感器(假设已实现) Voice_Init(); // 初始化语音模块(假设已实现) Vibrate_Init(); // 初始化振动马达(假设已实现) WiFi_Init(); // 初始化Wi-Fi模块(假设已实现) // 主循环 while (1) { // 读取超声波距离(单位:厘米) distance_cm = Ultrasonic_GetDistance(); // 判断危险等级 if (distance_cm < DISTANCE_DANGER) { danger_level = DANGER_LEVEL_DANGER; } else if (distance_cm < DISTANCE_WARNING) { danger_level = DANGER_LEVEL_WARNING; } else { danger_level = DANGER_LEVEL_SAFE; } // 根据危险等级控制振动和语音提示 switch (danger_level) { case DANGER_LEVEL_DANGER: Vibrate(255); // 最大振动强度 Voice_Play(1); // 播放危险语音(例如:"前方障碍物,请停止") break; case DANGER_LEVEL_WARNING: Vibrate(128); // 中等振动强度 Voice_Play(2); // 播放警告语音(例如:"前方有障碍物,注意") break; case DANGER_LEVEL_SAFE: Vibrate(0); // 关闭振动 Voice_Play(0); // 播放安全语音或无声(例如:"道路畅通") break; } // 上传数据到华为云平台(距离和危险等级) WiFi_SendData((float)distance_cm, danger_level); // 延时100ms,控制循环频率 Delay_ms(100); } } 总结本系统基于STM32F103C8T6核心板设计,实现了智能盲人辅助导航功能,通过实时采集前方障碍物距离信息并进行危险等级判断,有效提升了盲人出行的安全性和自主性。系统整合了多模式导航提示,包括振动马达的触觉反馈和JL-03语音模块的语音提示,为用户提供直观、及时的导航辅助。硬件组成上,采用HC-SR04超声波传感器进行障碍物检测,ESP8266-01S Wi-Fi模块实现与华为云平台的数据通信,确保行走轨迹和遇障数据能够实时上传。此外,通过洞洞板焊接传感器接口电路和杜邦线连接各模块,保证了系统的稳定性和可扩展性。QT上位机则用于显示行走路径地图和遇障统计信息,方便用户或监护人员远程监控和分析。整体而言,该系统不仅实现了基本的导航和避障功能,还通过云平台和上位机集成,提供了数据记录和可视化支持,具有较高的实用性和创新性。未来可进一步优化传感器精度和通信效率,以更好地服务于盲人群体。
  • [技术干货] 基于STM32设计的智能冷链物流监控系统
    项目开发背景随着全球贸易和电子商务的快速发展,冷链物流在食品、药品等易腐物品的运输中扮演着关键角色。这些物品对温度、湿度等环境条件极为敏感,任何偏差都可能导致质量下降、腐败甚至安全风险,因此确保运输过程中的环境稳定性至关重要。传统冷链监控往往依赖人工记录或简单设备,无法实现实时、全面的数据采集和远程管理,这增加了运营成本并降低了可靠性。在实际运输中,温度波动、湿度变化以及意外震动等异常情况频繁发生,但现有系统多数缺乏高效的实时监测和自动响应机制。这导致问题发现延迟、追溯困难,以及潜在的货物损失。此外,GPS定位和轨迹记录的缺失使得物流公司难以优化路线、提高效率,并应对突发事件。为了应对这些挑战,本项目旨在开发一个基于STM32的智能冷链物流监控系统,通过集成多传感器和无线通信技术,实现全天候的环境数据采集、精确定位和云端数据传输。该系统不仅能够实时监测温度、湿度和震动,还能在异常时自动报警并记录日志,从而提升冷链运输的透明度和可控性。通过结合STM32微控制器的低成本、高性能特点,以及华为云平台的数据处理能力,本项目为冷链物流行业提供了一个高效、可靠的解决方案,有助于降低损耗、提高客户满意度,并推动物流智能化的发展。设计实现的功能(1)实时监测环境温度数据(通过DS18B20传感器)(2)实时监测湿度数据(通过DHT22传感器)(3)实时监测震动数据(通过SW-420传感器)(4)精确定位运输位置(通过ATGM336H GPS模块)(5)记录运输轨迹(STM32处理并存储GPS数据)(6)检测异常情况(如温度、湿度、震动超出阈值)(7)自动报警异常情况(通过STM32触发报警机制)(8)记录事件日志(STM32存储异常事件信息)(9)上传实时数据至华为云(通过ESP8266 Wi-Fi模块)(10)支持QT上位机显示温湿度曲线和轨迹地图(基于上传的云数据)项目硬件模块组成(1)STM32F103C8T6最小系统核心板作为主控制器(2)DS18B20防水温度传感器监测环境温度(3)DHT22温湿度传感器监测湿度数据(4)SW-420震动传感器检测运输震动(5)ATGM336H GPS模块实现精确定位(6)ESP8266-01S Wi-Fi模块上传数据至华为云设计意义基于STM32设计的智能冷链物流监控系统通过集成温度、湿度和震动传感器,结合GPS定位和无线通信技术,实现了对冷链运输环境的全方位实时监控,有效提升了物流过程的透明度和可控性,这对于保障生鲜食品、药品等易腐货物的质量安全具有重要意义。该系统能够实时采集并传输温度、湿度和震动数据,确保运输环境始终处于预设的安全范围内,一旦检测到异常如温度超标或剧烈震动,便会自动触发报警机制并记录事件日志,从而帮助运营人员及时干预,避免货物变质或损坏,减少经济损失。GPS模块提供精确定位和轨迹记录功能,配合QT上位机软件可视化显示温湿度曲线和地图轨迹,使得用户能够直观跟踪运输全程,优化物流路线和管理决策,增强了对运输过程的洞察力和响应能力。通过Wi-Fi模块将数据上传至华为云平台,系统支持数据的远程存储和访问,便于进行历史数据分析和趋势预测,这不仅提高了监控的效率和可靠性,还为冷链物流的智能化升级提供了坚实的技术基础,符合现代物联网应用的发展方向。设计思路设计思路基于STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。系统通过集成多种传感器实时采集冷链运输环境中的数据,包括使用DS18B20防水温度传感器监测环境温度,DHT22温湿度传感器监测湿度数据,以及SW-420震动传感器检测运输过程中的震动情况。同时,ATGM336H GPS模块用于精确定位运输位置并记录轨迹数据,确保全程监控。数据采集后,STM32控制器进行实时处理和分析,检查温度、湿度和震动数据是否超出预设阈值。如果检测到异常情况,如温度过高或震动过大,系统会自动触发报警机制,并通过内部日志记录事件详情,包括时间、位置和异常类型,以便后续查询和分析。为了远程监控和数据存储,系统使用ESP8266-01S Wi-Fi模块将采集到的传感器数据、GPS定位信息以及事件日志上传至华为云平台。数据传输采用定期上传和事件触发上传相结合的方式,确保数据的实时性和可靠性,同时减少功耗。在上位机端,基于QT开发的上位机软件接收华为云的数据,并实时显示运输全程的温湿度曲线和轨迹地图。用户可以通过界面直观查看历史数据和当前状态,支持数据导出和报警通知功能,从而实现对冷链物流过程的全面可视化监控。框架图智能冷链物流监控系统框架图: +-------------+ | Huawei Cloud| +-------------+ ^ | (Wi-Fi Data Upload) +-------------+ | ESP8266 | | Wi-Fi Module | +-------------+ ^ | (UART Communication) +-------------+ | STM32 | | F103C8T6 MCU | +-------------+ / | | \ / | | \ / | | \ +--------+ +--------+ +--------+ +----------+ |DS18B20| | DHT22 | | SW-420 | | ATGM336H | |Temperature|Humidity|Vibration| | GPS Module| +--------+ +--------+ +--------+ +----------+ 数据流:传感器数据(温度、湿度、震动、GPS)采集到STM32,经处理通过ESP8266上传至华为云,QT上位机从云获取数据并显示曲线和地图。异常报警由STM32触发并通过云记录。系统总体设计系统总体设计基于STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。该系统旨在实时监测冷链运输过程中的环境参数,包括温度、湿度和震动数据,并通过集成多种传感器实现数据采集。DS18B20防水温度传感器用于精确测量环境温度,DHT22温湿度传感器同时监测湿度和温度数据,SW-420震动传感器检测运输过程中的震动情况,确保货物在运输中的稳定性。GPS定位部分采用ATGM336H模块,实现精确定位并记录运输轨迹,数据通过串口通信传输至STM32主控制器进行处理和存储。异常情况如温度超标、湿度异常或震动过大时,系统会自动触发报警机制,并通过事件日志记录相关数据,便于后续分析和追溯。数据传输通过ESP8266-01S Wi-Fi模块实现,将采集到的传感器数据、GPS位置信息以及事件日志上传至华为云平台,实现远程监控和数据备份。云平台负责数据存储和管理,为上位机提供数据源。上位机部分采用QT开发,显示运输全程的温湿度曲线和轨迹地图,用户可以通过图形界面实时查看历史数据和当前状态,辅助决策和监控。整个系统设计注重实际应用,确保数据准确性和可靠性,满足冷链物流的监控需求。系统功能总结功能描述实现硬件/方式温度监测实时监测环境温度DS18B20防水温度传感器湿度监测实时监测环境湿度DHT22温湿度传感器震动监测检测运输过程中的震动SW-420震动传感器定位与轨迹记录GPS精确定位运输位置并记录轨迹ATGM336H GPS模块数据上传将传感器数据上传至云平台ESP8266-01S Wi-Fi模块上传至华为云异常报警与日志记录当温度、湿度或震动异常时自动报警,并记录事件日志STM32主控制器处理数据,判断异常,并通过Wi-Fi发送报警或本地记录上位机显示QT软件显示运输全程的温湿度曲线和轨迹地图数据通过Wi-Fi上传至云,QT软件从云获取数据并可视化设计的各个功能模块描述STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行,初始化各个传感器和模块,实时采集和处理温度、湿度、震动以及GPS数据,并根据预设阈值判断异常情况,触发报警机制,同时控制数据上传和日志记录。DS18B20防水温度传感器用于监测环境温度,通过单总线协议与STM32通信,提供高精度的温度读数,其防水设计使其适合冷链运输的潮湿环境,确保温度数据的可靠性和实时性。DHT22温湿度传感器用于监测湿度数据,同时提供辅助温度信息,通过数字信号输出,STM32定期读取其数据以补充湿度监测,并与DS18B20数据交叉验证,增强系统可靠性。SW-420震动传感器检测运输过程中的震动事件,当震动强度超过设定阈值时,会向STM32发送信号,触发异常报警,STM32记录震动事件并纳入事件日志中。ATGM336H GPS模块实现精确定位功能,STM32通过串口接收GPS数据,解析出经纬度信息,实时记录运输位置并生成轨迹数据,用于上传和后续地图显示。ESP8266-01S Wi-Fi模块负责无线通信,将STM32处理后的传感器数据、GPS轨迹和事件日志通过MQTT或HTTP协议上传至华为云平台,实现数据的远程存储和实时监控。QT上位机软件运行于PC端,从华为云平台获取数据,图形化显示运输全程的温湿度变化曲线和轨迹地图,用户可通过界面查看历史数据和报警事件,完成监控需求。上位机代码设计以下是基于QT C++开发的智能冷链物流监控系统上位机代码设计。代码包括主窗口类、MQTT客户端处理、图表显示和地图集成。假设数据通过MQTT从华为云接收,数据格式为JSON,包含温度、湿度、震动、经纬度等信息。项目文件结构:main.cpp:应用程序入口。mainwindow.h:主窗口头文件。mainwindow.cpp:主窗口实现文件。map.html:用于显示地图的HTML文件(作为资源嵌入)。ColdChainMonitor.pro:QT项目文件。代码实现:1. ColdChainMonitor.pro(QT项目文件):QT += core gui mqtt charts webenginewidgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = ColdChainMonitor TEMPLATE = app SOURCES += main.cpp \ mainwindow.cpp HEADERS += mainwindow.h RESOURCES += resources.qrc2. resources.qrc(资源文件,包含map.html):<RCC> <qresource prefix="/"> <file>map.html</file> </qresource> </RCC> 3. main.cpp:#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } 4. mainwindow.h:#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QtMqtt/QtMqtt> #include <QChartView> #include <QLineSeries> #include <QWebEngineView> #include <QTextEdit> QT_CHARTS_USE_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void onConnected(); void onMessageReceived(const QByteArray &message, const QMqttTopicName &topic); void updateChart(double temperature, double humidity); void updateMap(double lat, double lon); void addLog(const QString &log); private: QMqttClient *m_client; QChart *m_chart; QLineSeries *m_tempSeries; QLineSeries *m_humSeries; QChartView *m_chartView; QWebEngineView *m_mapView; QTextEdit *m_logEdit; void setupUI(); void setupMQTT(); }; #endif // MAINWINDOW_H 5. mainwindow.cpp:#include "mainwindow.h" #include <QVBoxLayout> #include <QHBoxLayout> #include <QWebChannel> #include <QMessageBox> #include <QDateTime> #include <QJsonDocument> #include <QJsonObject> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setupUI(); setupMQTT(); } MainWindow::~MainWindow() { if (m_client) m_client->disconnect(); } void MainWindow::setupUI() { QWidget *centralWidget = new QWidget(this); QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget); QVBoxLayout *leftLayout = new QVBoxLayout(); m_chart = new QChart(); m_tempSeries = new QLineSeries(); m_tempSeries->setName("Temperature (°C)"); m_humSeries = new QLineSeries(); m_humSeries->setName("Humidity (%)"); m_chart->addSeries(m_tempSeries); m_chart->addSeries(m_humSeries); m_chart->createDefaultAxes(); m_chart->setTitle("Temperature and Humidity Monitoring"); m_chartView = new QChartView(m_chart); leftLayout->addWidget(m_chartView); m_logEdit = new QTextEdit(); m_logEdit->setReadOnly(true); leftLayout->addWidget(m_logEdit); m_mapView = new QWebEngineView(); m_mapView->load(QUrl("qrc:/map.html")); mainLayout->addLayout(leftLayout, 2); mainLayout->addWidget(m_mapView, 3); setCentralWidget(centralWidget); setWindowTitle("Smart Cold Chain Monitoring System"); resize(1200, 600); } void MainWindow::setupMQTT() { m_client = new QMqttClient(this); m_client->setHostname("your_mqtt_broker_address"); // Replace with actual Huawei Cloud MQTT broker m_client->setPort(1883); // Default MQTT port // If authentication is required, set username and password: // m_client->setUsername("username"); // m_client->setPassword("password"); connect(m_client, &QMqttClient::connected, this, &MainWindow::onConnected); connect(m_client, &QMqttClient::messageReceived, this, [this](const QByteArray &message, const QMqttTopicName &topic) { onMessageReceived(message, topic); }); m_client->connectToHost(); } void MainWindow::onConnected() { addLog("Connected to MQTT broker"); m_client->subscribe("coldchain/data"); // Subscribe to the topic where data is published } void MainWindow::onMessageReceived(const QByteArray &message, const QMqttTopicName &topic) { QString msg = QString::fromUtf8(message); addLog("Received: " + msg); QJsonDocument doc = QJsonDocument::fromJson(message); if (doc.isNull()) { addLog("Error: Invalid JSON data"); return; } QJsonObject obj = doc.object(); double temperature = obj["temperature"].toDouble(); double humidity = obj["humidity"].toDouble(); double vibration = obj["vibration"].toDouble(); double lat = obj["latitude"].toDouble(); double lon = obj["longitude"].toDouble(); updateChart(temperature, humidity); updateMap(lat, lon); if (vibration > 100) { // Example vibration threshold addLog("Alert: High vibration detected - " + QString::number(vibration)); QMessageBox::warning(this, "Vibration Alert", "High vibration detected during transport!"); } if (temperature < 2 || temperature > 8) { // Temperature range for cold chain addLog("Alert: Temperature out of range - " + QString::number(temperature)); QMessageBox::warning(this, "Temperature Alert", "Temperature is outside safe range (2-8°C)!"); } } void MainWindow::updateChart(double temperature, double humidity) { static int xValue = 0; m_tempSeries->append(xValue, temperature); m_humSeries->append(xValue, humidity); xValue++; m_chart->axes(Qt::Horizontal).first()->setRange(0, xValue); m_chart->axes(Qt::Vertical).first()->setRange(0, 100); // Adjust based on expected range } void MainWindow::updateMap(double lat, double lon) { m_mapView->page()->runJavaScript(QString("updateMarker(%1, %2);").arg(lat).arg(lon)); } void MainWindow::addLog(const QString &log) { m_logEdit->append(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + " - " + log); } 6. map.html(保存为资源文件):<!DOCTYPE html> <html> <head> <title>Transport Map</title> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script> <style> #map { height: 100%; width: 100%; } </style> </head> <body> <div id="map"></div> <script> var map = L.map('map').setView([39.9042, 116.4074], 10); // Initial view L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '? OpenStreetMap contributors' }).addTo(map); var marker = L.marker([39.9042, 116.4074]).addTo(map); function updateMarker(lat, lon) { map.setView([lat, lon], 13); marker.setLatLng([lat, lon]); } </script> </body> </html> 使用说明:将上述代码保存到相应文件中。在QT Creator中创建新项目,并替换默认文件内容。添加resources.qrc文件,包含map.html。更新MQTT broker信息(主机名、端口、认证等)以匹配华为云配置。编译并运行项目。此代码提供了一个基本的上位机界面,实时显示温湿度曲线、GPS轨迹地图和事件日志。异常情况会弹出警报并记录日志。确保华为云MQTT服务已正确设置,设备数据发布到"coldchain/data"主题。模块代码设计#include "stm32f10x.h" // 寄存器地址定义 #define RCC_BASE 0x40021000 #define GPIOA_BASE 0x40010800 #define GPIOB_BASE 0x40010C00 #define UART1_BASE 0x40013800 #define UART2_BASE 0x40004400 #define UART3_BASE 0x40004800 #define RCC_CR (*(volatile unsigned int *)(RCC_BASE)) #define RCC_CFGR (*(volatile unsigned int *)(RCC_BASE + 0x04)) #define RCC_APB2ENR (*(volatile unsigned int *)(RCC_BASE + 0x18)) #define RCC_APB1ENR (*(volatile unsigned int *)(RCC_BASE + 0x1C)) #define GPIOA_CRL (*(volatile unsigned int *)(GPIOA_BASE + 0x00)) #define GPIOA_CRH (*(volatile unsigned int *)(GPIOA_BASE + 0x04)) #define GPIOA_IDR (*(volatile unsigned int *)(GPIOA_BASE + 0x08)) #define GPIOA_ODR (*(volatile unsigned int *)(GPIOA_BASE + 0x0C)) #define GPIOB_CRL (*(volatile unsigned int *)(GPIOB_BASE + 0x00)) #define GPIOB_CRH (*(volatile unsigned int *)(GPIOB_BASE + 0x04)) #define GPIOB_IDR (*(volatile unsigned int *)(GPIOB_BASE + 0x08)) #define GPIOB_ODR (*(volatile unsigned int *)(GPIOB_BASE + 0x0C)) #define UART1_SR (*(volatile unsigned int *)(UART1_BASE)) #define UART1_DR (*(volatile unsigned int *)(UART1_BASE + 0x04)) #define UART1_BRR (*(volatile unsigned int *)(UART1_BASE + 0x08)) #define UART1_CR1 (*(volatile unsigned int *)(UART1_BASE + 0x0C)) #define UART2_SR (*(volatile unsigned int *)(UART2_BASE)) #define UART2_DR (*(volatile unsigned int *)(UART2_BASE + 0x04)) #define UART2_BRR (*(volatile unsigned int *)(UART2_BASE + 0x08)) #define UART2_CR1 (*(volatile unsigned int *)(UART2_BASE + 0x0C)) #define UART3_SR (*(volatile unsigned int *)(UART3_BASE)) #define UART3_DR (*(volatile unsigned int *)(UART3_BASE + 0x04)) #define UART3_BRR (*(volatile unsigned int *)(UART3_BASE + 0x08)) #define UART3_CR1 (*(volatile unsigned int *)(UART3_BASE + 0x0C)) // 引脚定义 #define DS18B20_PIN 0 // PA0 #define DHT22_PIN 1 // PA1 #define SW420_PIN 2 // PA2 // 函数声明 void SystemClock_Config(void); void GPIO_Init(void); void UART1_Init(void); void UART2_Init(void); void UART3_Init(void); void DS18B20_Init(void); float DS18B20_ReadTemp(void); void DHT22_Init(void); void DHT22_Read(float *temp, float *hum); uint8_t SW420_Read(void); void GPS_Init(void); void GPS_Read(void); void WiFi_Init(void); void WiFi_SendData(float temp, float hum, uint8_t shock, const char *gps_data); void Delay_us(uint32_t us); void Delay_ms(uint32_t ms); // 全局变量 char gps_buffer[100]; volatile uint8_t uart2_rx_index = 0; volatile uint8_t uart2_rx_buffer[100]; int main(void) { SystemClock_Config(); GPIO_Init(); UART1_Init(); // For debug UART2_Init(); // For GPS UART3_Init(); // For WiFi DS18B20_Init(); DHT22_Init(); // SW420 is input, already initialized in GPIO_Init while (1) { float temperature = DS18B20_ReadTemp(); float humidity, temp_dht; DHT22_Read(&temp_dht, &humidity); // DHT22 also provides temperature, but we use DS18B20 for temp uint8_t shock = SW420_Read(); GPS_Read(); // Read GPS data into gps_buffer // Check for anomalies if (temperature > 8.0 || temperature < 2.0 || humidity > 80.0 || shock == 1) { // Trigger alarm, e.g., set a GPIO or send via WiFi // Here, we'll send via WiFi } WiFi_SendData(temperature, humidity, shock, gps_buffer); Delay_ms(5000); // Send data every 5 seconds } } void SystemClock_Config(void) { // Enable HSE RCC_CR |= (1 << 16); // HSEON while (!(RCC_CR & (1 << 17))); // Wait for HSERDY // Configure PLL: HSE as source, multiply by 9 -> 72MHz RCC_CFGR |= (1 << 16); // PLLSRC = HSE RCC_CFGR |= (7 << 18); // PLLMUL = 9 (0111) RCC_CR |= (1 << 24); // PLLON while (!(RCC_CR & (1 << 25))); // Wait for PLLRDY // Configure FLASH latency FLASH_ACR = 0x12; // Two wait states for 48 < HCLK <= 72MHz // Switch to PLL RCC_CFGR |= (2 << 0); // SW = PLL while ((RCC_CFGR & (3 << 2)) != (2 << 2)); // Wait for SWS to be PLL } void GPIO_Init(void) { // Enable GPIOA and GPIOB clocks RCC_APB2ENR |= (1 << 2) | (1 << 3); // GPIOA and GPIOB enable // DS18B20 on PA0: output open-drain GPIOA_CRL &= ~(0xF << (0 * 4)); GPIOA_CRL |= (0x4 << (0 * 4)); // Output open-drain, 50MHz // DHT22 on PA1: output open-drain GPIOA_CRL &= ~(0xF << (1 * 4)); GPIOA_CRL |= (0x4 << (1 * 4)); // Output open-drain, 50MHz // SW420 on PA2: input pull-up GPIOA_CRL &= ~(0xF << (2 * 4)); GPIOA_CRL |= (0x8 << (2 * 4)); // Input with pull-up/pull-down GPIOA_ODR |= (1 << 2); // Set pull-up // UART2 on PA2 (TX) and PA3 (RX) GPIOA_CRL &= ~(0xFF << (2 * 4)); // Clear bits for PA2 and PA3 GPIOA_CRL |= (0x4 << (2 * 4)); // PA2: Alternate function push-pull, 50MHz GPIOA_CRL |= (0x4 << (3 * 4)); // PA3: Input floating (RX is input) // UART3 on PB10 (TX) and PB11 (RX) GPIOB_CRH &= ~(0xFF << (2 * 4)); // Clear bits for PB10 and PB11 (bits 8-15 for CRH) GPIOB_CRH |= (0x4 << (10 - 8) * 4); // PB10: Alternate function push-pull, 50MHz GPIOB_CRH |= (0x4 << (11 - 8) * 4); // PB11: Input floating } void UART1_Init(void) { // Enable UART1 clock RCC_APB2ENR |= (1 << 14); // USART1 enable // Configure UART1: 9600 baud, 8 data bits, no parity, 1 stop bit UART1_BRR = 0x1D4C; // 72MHz / 9600 = 7500 -> 0x1D4C UART1_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE } void UART2_Init(void) { // Enable UART2 clock RCC_APB1ENR |= (1 << 17); // USART2 enable // Configure UART2: 9600 baud for GPS UART2_BRR = 0x1D4C; // 72MHz / 9600 = 7500 -> 0x1D4C UART2_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE // Enable RX interrupt for GPS UART2_CR1 |= (1 << 5); // RXNEIE NVIC_EnableIRQ(USART2_IRQn); } void UART3_Init(void) { // Enable UART3 clock RCC_APB1ENR |= (1 << 18); // USART3 enable // Configure UART3: 115200 baud for WiFi UART3_BRR = 0x0271; // 72MHz / 115200 ≈ 625 -> 0x0271 UART3_CR1 |= (1 << 13) | (1 << 3) | (1 << 2); // UE, TE, RE } void DS18B20_Init(void) { // Initialization is done in read function } float DS18B20_ReadTemp(void) { uint8_t temp_l, temp_h; int16_t temp; // Reset pulse GPIOA_ODR &= ~(1 << DS18B20_PIN); // Pull low Delay_us(480); GPIOA_ODR |= (1 << DS18B20_PIN); // Release Delay_us(60); if (!(GPIOA_IDR & (1 << DS18B20_PIN))) { // Presence pulse detected Delay_us(240); } // Skip ROM command DS18B20_WriteByte(0xCC); // Convert T command DS18B20_WriteByte(0x44); Delay_ms(750); // Wait for conversion // Reset again GPIOA_ODR &= ~(1 << DS18B20_PIN); Delay_us(480); GPIOA_ODR |= (1 << DS18B20_PIN); Delay_us(60); if (!(GPIOA_IDR & (1 << DS18B20_PIN))) { Delay_us(240); } // Skip ROM DS18B20_WriteByte(0xCC); // Read scratchpad DS18B20_WriteByte(0xBE); temp_l = DS18B20_ReadByte(); temp_h = DS18B20_ReadByte(); temp = (temp_h << 8) | temp_l; return temp / 16.0; } void DS18B20_WriteByte(uint8_t data) { for (int i = 0; i < 8; i++) { GPIOA_ODR &= ~(1 << DS18B20_PIN); // Pull low if (data & 0x01) { Delay_us(5); GPIOA_ODR |= (1 << DS18B20_PIN); // Release for logic 1 } else { Delay_us(60); GPIOA_ODR |= (1 << DS18B20_PIN); } data >>= 1; Delay_us(60); } } uint8_t DS18B20_ReadByte(void) { uint8 data = 0; for (int i = 0; i < 8; i++) { GPIOA_ODR &= ~(1 << DS18B20_PIN); Delay_us(1); GPIOA_ODR |= (1 << DS18B20_PIN); Delay_us(14); if (GPIOA_IDR & (1 << DS18B20_PIN)) { data |= (1 << i); } Delay_us(45); } return data; } void DHT22_Init(void) { // Initialization is done in read function } void DHT22_Read(float *temp, float *hum) { uint8_t data[5] = {0}; uint8_t checksum; // Send start signal GPIOA_ODR &= ~(1 << DHT22_PIN); Delay_ms(1); GPIOA_ODR |= (1 << DHT22_PIN); Delay_us(30); // Wait for response while (GPIOA_IDR & (1 << DHT22_PIN)); while (!(GPIOA_IDR & (1 << DHT22_PIN))); while (GPIOA_IDR & (1 << DHT22_PIN)); // Read data for (int i = 0; i < 40; i++) { while (!(GPIOA_IDR & (1 << DHT22_PIN))); // Wait for low to go high Delay_us(40); if (GPIOA_IDR & (1 << DHT22_PIN)) { data[i/8] |= (1 << (7 - (i % 8))); while (GPIOA_IDR & (1 << DHT22_PIN)); } } checksum = data[0] + data[1] + data[2] + data[3]; if (checksum == data[4]) { *hum = ((data[0] << 8) | data[1]) / 10.0; *temp = (((data[2] & 0x7F) << 8) | data[3]) / 10.0; if (data[2] & 0x80) *temp = -(*temp); } else { *hum = 0.0; *temp = 0.0; } } uint8_t SW420_Read(void) { return (GPIOA_IDR & (1 << SW420_PIN)) ? 0 : 1; // Active low? Check sensor datasheet. Assuming shock when low. } void GPS_Init(void) { // UART2 already initialized } void GPS_Read(void) { // Data is received via interrupt, so we handle it in ISR // For simplicity, we assume gps_buffer is filled in ISR } void USART2_IRQHandler(void) { if (UART2_SR & (1 << 5)) { // RXNE uint8_t data = UART2_DR; if (data == '\n') { gps_buffer[uart2_rx_index] = '\0'; uart2_rx_index = 0; } else { gps_buffer[uart2_rx_index++] = data; if (uart2_rx_index >= sizeof(gps_buffer) - 1) uart2_rx_index = 0; } } } void WiFi_Init(void) { // Send AT commands to initialize ESP8266 // This is done once at startup const char *init_cmds[] = { "AT\r\n", "AT+CWMODE=1\r\n", "AT+CWJAP=\"SSID\",\"PASSWORD\"\r\n", // Replace with your WiFi credentials "AT+CIPSTART=\"TCP\",\"华为云地址\",端口号\r\n", // Replace with Huawei cloud details NULL }; for (int i = 0; init_cmds[i] != NULL; i++) { WiFi_SendCommand(init_cmds[i]); Delay_ms(1000); } } void WiFi_SendCommand(const char *cmd) { while (*cmd) { UART3_DR = *cmd++; while (!(UART3_SR & (1 << 7))); // Wait for TXE } } void WiFi_SendData(float temp, float hum, uint8_t shock, const char *gps_data) { char buffer[100]; sprintf(buffer, "AT+CIPSEND=%d\r\n", strlen(buffer)); // First send length WiFi_SendCommand(buffer); Delay_ms(100); sprintf(buffer, "temp=%.2f,hum=%.2f,shock=%d,gps=%s\r\n", temp, hum, shock, gps_data); WiFi_SendCommand(buffer); } void Delay_us(uint32_t us) { us *= 72; // For 72MHz, approximate while (us--) { __NOP(); } } void Delay_ms(uint32_t ms) { while (ms--) { Delay_us(1000); } } 项目核心代码#include "stm32f10x.h" // 假设其他模块的头文件已存在 #include "ds18b20.h" #include "dht22.h" #include "gps.h" #include "wifi.h" // 定义传感器引脚 #define DS18B20_PIN GPIO_Pin_0 #define DS18B20_PORT GPIOA #define DHT22_PIN GPIO_Pin_1 #define DHT22_PORT GPIOA #define SW420_PIN GPIO_Pin_2 #define SW420_PORT GPIOA // 异常阈值 #define TEMP_LOW_LIMIT -20.0 #define TEMP_HIGH_LIMIT 10.0 #define HUMIDITY_LIMIT 80.0 #define SHAKE_THRESHOLD 1 // 假设震动次数阈值 // 全局变量 float temperature = 0.0; float humidity = 0.0; uint8_t shake_count = 0; char gps_data[100] = {0}; uint8_t alert_flag = 0; // 函数声明 void System_Init(void); void GPIO_Init(void); void USART_Init(void); void Timer_Init(void); void Read_Sensors(void); void Process_GPS(void); void Check_Alerts(void); void Upload_Data(void); void Delay_ms(uint32_t ms); int main(void) { System_Init(); GPIO_Init(); USART_Init(); Timer_Init(); // 初始化模块 DS18B20_Init(); DHT22_Init(); GPS_Init(); WIFI_Init(); while (1) { Read_Sensors(); Process_GPS(); Check_Alerts(); Upload_Data(); Delay_ms(5000); // 每5秒执行一次 } } void System_Init(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) { // 启用GPIOA时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // 配置DS18B20引脚为推挽输出(初始) DS18B20_PORT->CRL &= ~(0x0F << (0 * 4)); // PA0 DS18B20_PORT->CRL |= (0x03 << (0 * 4)); // Output, 50MHz // 配置DHT22引脚为推挽输出(初始) DHT22_PORT->CRL &= ~(0x0F << (1 * 4)); // PA1 DHT22_PORT->CRL |= (0x03 << (1 * 4)); // Output, 50MHz // 配置SW-420引脚为输入上拉 SW420_PORT->CRL &= ~(0x0F << (2 * 4)); // PA2 SW420_PORT->CRL |= (0x08 << (2 * 4)); // Input, pull-up SW420_PORT->ODR |= SW420_PIN; // Set pull-up } void USART_Init(void) { // 启用USART1时钟(用于Wi-Fi) RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // 启用USART2时钟(用于GPS) RCC->APB1ENR |= RCC_APB1ENR_USART2EN; // 配置USART1 (PA9 TX, PA10 RX) GPIOA->CRH &= ~(0xFF << 4); // Clear PA9 and PA10 GPIOA->CRH |= (0x0B << 4); // PA9: Output, 50MHz, alt func GPIOA->CRH |= (0x04 << 8); // PA10: Input, pull-up USART1->BRR = 72000000 / 115200; // Baud rate 115200 USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // Enable USART, TX, RX // 配置USART2 (PA2 TX, PA3 RX) for GPS GPIOA->CRL &= ~(0xFF << 8); // Clear PA2 and PA3 GPIOA->CRL |= (0x0B << 8); // PA2: Output, 50MHz, alt func GPIOA->CRL |= (0x04 << 12); // PA3: Input, pull-up USART2->BRR = 72000000 / 9600; // Baud rate 9600 for GPS USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // Enable USART, TX, RX } void Timer_Init(void) { // 启用TIM2时钟 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; TIM2->PSC = 7200 - 1; // 预分频,10kHz TIM2->ARR = 10000 - 1; // 自动重载值,1秒 TIM2->CR1 |= TIM_CR1_ARPE; // 自动重载预装载 TIM2->DIER |= TIM_DIER_UIE; // 更新中断使能 TIM2->CR1 |= TIM_CR1_CEN; // 启动定时器 NVIC_EnableIRQ(TIM2_IRQn); // 使能TIM2中断 } void TIM2_IRQHandler(void) { if (TIM2->SR & TIM_SR_UIF) { TIM2->SR &= ~TIM_SR_UIF; // 清除中断标志 // 定时任务,例如检查震动 if (SW420_PORT->IDR & SW420_PIN) { shake_count++; // 震动计数 } } } void Read_Sensors(void) { temperature = DS18B20_ReadTemp(); // 假设函数返回float humidity = DHT22_ReadHumidity(); // 假设函数返回float // 震动数据通过中断计数,这里重置或处理 } void Process_GPS(void) { // 假设GPS_ReadData函数从USART2读取并解析NMEA,存储到gps_data GPS_ReadData(gps_data); } void Check_Alerts(void) { alert_flag = 0; if (temperature < TEMP_LOW_LIMIT || temperature > TEMP_HIGH_LIMIT) { alert_flag |= 0x01; // 温度异常 } if (humidity > HUMIDITY_LIMIT) { alert_flag |= 0x02; // 湿度异常 } if (shake_count > SHAKE_THRESHOLD) { alert_flag |= 0x04; // 震动异常 shake_count = 0; // 重置计数 } if (alert_flag) { // 记录事件日志,假设有函数实现 Log_Event(alert_flag, temperature, humidity, shake_count, gps_data); } } void Upload_Data(void) { char data_str[200]; sprintf(data_str, "Temp:%.2f,Hum:%.2f,Shake:%d,GPS:%s,Alert:%d", temperature, humidity, shake_count, gps_data, alert_flag); WIFI_SendData(data_str); // 假设函数通过USART1发送到ESP8266 } void Delay_ms(uint32_t ms) { for (uint32_t i = 0; i < ms * 1000; i++) { __NOP(); } } 总结本系统基于STM32F103C8T6微控制器设计,实现了智能冷链物流监控的核心功能,能够实时监测运输过程中的温度、湿度和震动数据,确保货物在运输途中处于适宜的环境条件。通过集成多种传感器和模块,系统有效提升了冷链物流的监控精度和响应速度。硬件组成包括STM32F103C8T6最小系统核心板作为主控制器,DS18B20防水温度传感器用于环境温度监测,DHT22温湿度传感器提供湿度数据,SW-420震动传感器检测运输震动,ATGM336H GPS模块实现精确定位和轨迹记录,以及ESP8266-01S Wi-Fi模块将采集的数据上传至华为云平台,实现远程监控和数据存储。此外,系统通过QT上位机显示运输全程的温湿度曲线和轨迹地图,支持异常情况自动报警并记录事件日志,增强了冷链物流的可靠性和管理效率。整体设计紧凑、成本效益高,适用于实际物流应用,为冷链运输提供了全面的智能化解决方案。
  • [技术干货] 基于STM32设计的智能垃圾分类指导系统
    项目开发背景随着城市化进程不断加快和居民消费水平持续提升,生活垃圾产生量逐年增加,传统的垃圾分类主要依赖人工判断和分拣,不仅效率低下,还存在分类准确性不高、人力成本较高等问题。在此背景下,利用嵌入式技术和人工智能手段实现智能化的垃圾分类指导,已成为解决城市垃圾处理难题的重要方向。基于STM32的智能垃圾分类系统正是在这一需求背景下提出的创新方案。该系统通过图像传感器实时采集垃圾的视觉特征,结合本地轻量级算法进行快速识别,能够有效区分可回收物、厨余垃圾、有害垃圾和其他垃圾等常见类别。系统进一步通过语音模块提供实时分类指导,降低居民的学习成本和使用门槛,同时借助物联网技术将识别数据上传至云平台,实现垃圾分类数据的记录与分析。该系统的开发不仅有助于推动生活垃圾处理的智能化与信息化,还可为城市管理者提供数据支持,优化垃圾收运和处理流程。此外,通过可视化的QT界面展示分类结果和统计信息,进一步增强了系统的实用性和交互性,为环保宣传教育提供了有效工具。设计实现的功能(1)使用OV7670摄像头模块采集垃圾图像特征(2)基于STM32F103C8T6运行本地算法进行垃圾类型识别(3)通过JL-03语音模块播放分类指导提示(4)利用ESP8266-01S Wi-Fi模块连接华为云平台,QT上位机显示识别记录和统计数据分析项目硬件模块组成(1)STM32F103C8T6最小系统核心板作为主控制器(2)OV7670摄像头模块采集垃圾图像(3)JL-03语音模块播放分类提示(4)RGB LED灯带提供颜色分类指示(5)ESP8266-01S Wi-Fi模块连接华为云平台(6)洞洞板焊接图像处理电路,杜邦线连接各传感器设计意义基于STM32设计的智能垃圾分类指导系统具有重要的现实意义和应用价值。该系统通过集成图像传感器和本地处理算法,能够自动识别垃圾类型并提供分类指导,有效解决了人工分类效率低、错误率高的痛点。在实际应用中,这有助于提升垃圾分类的准确性和便捷性,推动环保意识的普及和垃圾资源化利用。系统的硬件组成如STM32主控制器、OV7670摄像头模块等,确保了低成本、高可靠性的实现,使得该设计易于部署在家庭、社区或公共场所。通过本地算法进行垃圾识别,减少了对网络依赖,提高了响应速度,同时语音模块和RGB LED灯带提供直观的提示,增强了用户体验。连接华为云平台 via ESP8266 Wi-Fi模块,使系统能够上传识别记录和数据,便于通过QT上位机进行统计分析和远程监控。这为垃圾分类管理提供了数据支持,有助于政府和机构制定更有效的环保政策,并实现长期趋势分析。整体上,该设计不仅促进了垃圾分类的智能化转型,还体现了嵌入式系统在物联网领域的实用创新,为可持续发展贡献了技术解决方案。设计思路系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调各模块的工作流程。首先,OV7670摄像头模块采集垃圾图像,通过洞洞板焊接的图像处理电路进行初步信号调理,STM32读取图像数据并提取关键特征,如颜色、形状或纹理,为后续识别做准备。本地垃圾类型识别算法在STM32上运行,利用提取的图像特征进行简单分类,例如通过阈值分割或模板匹配区分可回收物、厨余垃圾、有害垃圾和其他垃圾。算法设计注重轻量化和实时性,以适应STM32的处理能力限制,确保识别过程快速且准确。识别结果通过JL-03语音模块播放分类指导提示,例如语音播报垃圾类型和投放建议,同时RGB LED灯带根据垃圾类别显示不同颜色,提供直观的视觉指示,增强用户交互体验。ESP8266-01S Wi-Fi模块将识别记录和数据上传至华为云平台,实现远程数据存储和管理。QT上位机软件从云端获取数据,显示历史识别记录、分类统计和趋势分析,支持用户查看和导出报告,便于垃圾分类管理的监督和优化。硬件连接通过洞洞板焊接图像处理电路,并使用杜邦线灵活连接各传感器模块,确保信号传输稳定。整个系统设计注重实用性和可靠性,各模块协同工作,完成从图像采集到数据展示的全流程。框架图+----------------+ +-----------------+ | OV7670摄像头 |----->| STM32F103C8T6 | | (图像采集) | | (主控制器) | +----------------+ | - 图像处理 | | - 垃圾识别算法 | | - 模块控制 | +-----------------+ | | | +--------------------+ | +---------------------+ | | | +-----------------+ +-----------------+ +-----------------------+ | JL-03语音模块 | | RGB LED灯带 | | ESP8266-01S Wi-Fi模块 | | (语音提示) | | (颜色指示) | | (华为云连接) | +-----------------+ +-----------------+ +-----------------------+ | +-----------------+ | 华为云平台 | | - 数据存储 | | - 数据分析 | +-----------------+ | +-----------------+ | QT上位机 | | - 识别记录显示 | | - 统计数据分析 | +-----------------+ 系统总体设计系统总体设计基于STM32F103C8T6最小系统核心板作为主控制器,负责协调整个垃圾分类指导系统的运行。该系统通过OV7670摄像头模块采集垃圾图像特征,图像数据经由洞洞板焊接的图像处理电路进行初步处理,然后通过杜邦线连接至STM32核心板。STM32运行本地算法对图像进行垃圾类型识别,识别结果通过JL-03语音模块播放分类指导提示,同时RGB LED灯带根据垃圾类型提供颜色指示,以增强用户交互。硬件组件包括OV7670摄像头用于图像采集,JL-03语音模块用于音频输出,RGB LED灯带用于视觉反馈,ESP8266-01S Wi-Fi模块用于连接华为云平台,实现数据上传。所有传感器和模块通过杜邦线连接到STM32核心板,确保灵活性和可扩展性。系统设计注重实际应用,图像处理电路在洞洞板上焊接,以降低成本和提高可靠性。工作流程始于图像采集,OV7670摄像头捕获垃圾图像后,数据被传输到STM32进行本地算法处理,识别垃圾类型如可回收、有害、厨余或其他。识别结果触发语音模块播放相应提示,并控制RGB LED灯带显示对应颜色。同时,ESP8266模块将识别记录上传至华为云平台,QT上位机从云平台获取数据,显示识别历史和统计数据分析,支持用户监控和优化分类行为。系统集成通过STM32的GPIO、UART和SPI接口实现各模块通信,确保数据流畅传输。本地算法优化了处理效率,减少对云端的依赖,而Wi-Fi模块保障了数据的远程同步。整体设计注重实用性和稳定性,适用于家庭或公共场所的垃圾分类指导场景。系统功能总结功能名称功能描述使用硬件/模块图像采集采集垃圾图像特征OV7670摄像头模块图像处理本地算法进行垃圾类型识别STM32F103C8T6核心板分类指示提供颜色分类指示RGB LED灯带语音提示播放分类指导提示JL-03语音模块数据上传连接华为云平台上传数据ESP8266-01S Wi-Fi模块数据显示显示识别记录和统计数据分析QT上位机软件设计的各个功能模块描述STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行,包括控制图像采集、执行本地垃圾识别算法、驱动语音模块和LED指示,以及管理Wi-Fi通信,确保各模块协同工作。OV7670摄像头模块用于采集垃圾的图像数据,通过图像传感器获取视觉特征,为后续的垃圾类型识别提供原始输入,支持系统进行实时图像处理。JL-03语音模块接收主控制器的指令,播放预录制的分类指导提示语音,例如告知用户垃圾属于可回收、有害或其他类别,增强系统的交互性和实用性。RGB LED灯带根据识别结果显示不同颜色的光,例如用红色表示有害垃圾、绿色表示可回收垃圾,提供直观的视觉分类指示,辅助用户进行正确投放。ESP8266-01S Wi-Fi模块实现系统与华为云平台的连接,用于上传识别记录和数据,支持远程监控和统计分析,同时可能接收云端的指令或更新。洞洞板焊接的图像处理电路包括必要的组件如电阻和电容,用于稳定和预处理OV7670摄像头的图像信号,通过杜邦线可靠连接各传感器,确保数据采集的准确性。本地算法运行在STM32控制器上,基于采集的图像特征进行垃圾类型识别,采用简单的图像处理或机器学习方法实现分类,无需外部依赖,保证实时性。QT上位机软件运行在计算机端,通过Wi-Fi接收系统上传的数据,显示识别历史记录和进行统计数据分析,如图表展示分类趋势,提供用户友好的界面。上位机代码设计GarbageClassifierUI.proQT += core gui charts network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = GarbageClassifierUI TEMPLATE = app SOURCES += \ main.cpp \ mainwindow.cpp HEADERS += \ mainwindow.hmain.cpp#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } mainwindow.h#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QTcpServer> #include <QTcpSocket> #include <QTableWidget> #include <QtCharts> #include <QChartView> #include <QPieSeries> #include <QVector> #include <QString> struct Record { QString timestamp; QString type; double confidence; }; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void newConnection(); void readData(); void updateChart(); private: QTcpServer *server; QTableWidget *table; QChart *chart; QChartView *chartView; QPieSeries *series; QVector<Record> records; void setupUI(); void setupServer(); }; #endif // MAINWINDOW_H mainwindow.cpp#include "mainwindow.h" #include <QHeaderView> #include <QVBoxLayout> #include <QHBoxLayout> #include <QLabel> #include <QMessageBox> #include <QDateTime> #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), server(nullptr), table(nullptr), chart(nullptr), chartView(nullptr), series(nullptr) { setupUI(); setupServer(); } MainWindow::~MainWindow() { if (server) server->close(); } void MainWindow::setupUI() { QWidget *centralWidget = new QWidget(this); QHBoxLayout *layout = new QHBoxLayout(centralWidget); table = new QTableWidget(0, 3, this); table->setHorizontalHeaderLabels(QStringList() << "Timestamp" << "Type" << "Confidence"); table->horizontalHeader()->setStretchLastSection(true); chart = new QChart(); chart->setTitle("Garbage Classification Statistics"); series = new QPieSeries(); chart->addSeries(series); chartView = new QChartView(chart); chartView->setRenderHint(QPainter::Antialiasing); layout->addWidget(table, 2); layout->addWidget(chartView, 1); setCentralWidget(centralWidget); resize(800, 600); } void MainWindow::setupServer() { server = new QTcpServer(this); if (!server->listen(QHostAddress::Any, 1234)) { QMessageBox::critical(this, "Error", "Could not start server: " + server->errorString()); return; } connect(server, &QTcpServer::newConnection, this, &MainWindow::newConnection); } void MainWindow::newConnection() { QTcpSocket *socket = server->nextPendingConnection(); connect(socket, &QTcpSocket::readyRead, this, &MainWindow::readData); connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); } void MainWindow::readData() { QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender()); if (!socket) return; while (socket->canReadLine()) { QByteArray data = socket->readLine().trimmed(); QString line = QString::fromUtf8(data); QStringList parts = line.split(','); if (parts.size() != 3) { qDebug() << "Invalid data format:" << line; continue; } QString timestamp = parts[0]; QString type = parts[1]; double confidence = parts[2].toDouble(); Record record; record.timestamp = timestamp; record.type = type; record.confidence = confidence; records.append(record); int row = table->rowCount(); table->insertRow(row); table->setItem(row, 0, new QTableWidgetItem(timestamp)); table->setItem(row, 1, new QTableWidgetItem(type)); table->setItem(row, 2, new QTableWidgetItem(QString::number(confidence))); updateChart(); } } void MainWindow::updateChart() { QMap<QString, int> countMap; for (const Record &record : records) { countMap[record.type]++; } series->clear(); for (auto it = countMap.begin(); it != countMap.end(); ++it) { series->append(it.key(), it.value()); } } 模块代码设计#include "stm32f10x.h" // 引脚定义 #define OV7670_SCCB_SCL_PIN GPIO_Pin_6 #define OV7670_SCCB_SDA_PIN GPIO_Pin_7 #define OV7670_SCCB_PORT GPIOB #define OV7670_VSYNC_PIN GPIO_Pin_8 #define OV7670_HREF_PIN GPIO_Pin_9 #define OV7670_PCLK_PIN GPIO_Pin_10 #define OV7670_DATA_PORT GPIOC #define OV7670_DATA_PIN_START GPIO_Pin_0 #define OV7670_DATA_PIN_END GPIO_Pin_7 #define RGB_LED_R_PIN GPIO_Pin_0 #define RGB_LED_G_PIN GPIO_Pin_1 #define RGB_LED_B_PIN GPIO_Pin_2 #define RGB_LED_PORT GPIOA #define JL03_TX_PIN GPIO_Pin_2 #define JL03_RX_PIN GPIO_Pin_3 #define JL03_USART USART2 #define JL03_USART_PORT GPIOA #define ESP8266_TX_PIN GPIO_Pin_9 #define ESP8266_RX_PIN GPIO_Pin_10 #define ESP8266_USART USART1 #define ESP8266_USART_PORT GPIOA // 函数声明 void SystemClock_Config(void); void GPIO_Config(void); void I2C_Config(void); void USART_Config(void); void OV7670_Init(void); void OV7670_WriteReg(uint8_t reg, uint8_t data); uint8_t OV7670_ReadReg(uint8_t reg); void OV7670_CaptureImage(void); void Image_Process(void); void JL03_PlayVoice(uint8_t type); void RGB_LED_SetColor(uint8_t red, uint8_t green, uint8_t blue); void ESP8266_SendData(const char* data); void Delay_ms(uint32_t ms); // 全局变量 uint8_t image_data[176 * 144]; // QCIF分辨率:176x144 volatile uint8_t capture_done = 0; volatile uint32_t frame_count = 0; int main(void) { SystemClock_Config(); GPIO_Config(); I2C_Config(); USART_Config(); OV7670_Init(); while (1) { OV7670_CaptureImage(); if (capture_done) { Image_Process(); // 图像处理并识别类型 capture_done = 0; } Delay_ms(1000); // 每秒采集一次 } } void SystemClock_Config(void) { // 启用HSE并设置系统时钟为72MHz RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9; RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); } void GPIO_Config(void) { // 启用GPIO时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN; // 配置OV7670 SCCB (I2C)引脚: 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 | GPIO_CRL_MODE6 | GPIO_CRL_MODE7; // 开漏输出,50MHz // 配置OV7670数据和控制引脚: PC0-PC7为输入, PC8(VSYNC), PC9(HREF), PC10(PCLK)为输入 GPIOC->CRL &= ~0xFFFFFFFF; GPIOC->CRL |= GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0 | GPIO_CRL_CNF2_0 | GPIO_CRL_CNF3_0 | GPIO_CRL_CNF4_0 | GPIO_CRL_CNF5_0 | GPIO_CRL_CNF6_0 | GPIO_CRL_CNF7_0; // 上拉/下拉输入 GPIOC->CRH &= ~(GPIO_CRH_CNF8 | GPIO_CRH_CNF9 | GPIO_CRH_CNF10); GPIOC->CRH |= GPIO_CRH_CNF8_0 | GPIO_CRH_CNF9_0 | GPIO_CRH_CNF10_0; // 上拉/下拉输入 GPIOC->ODR |= 0x07FF; // 上拉所有引脚 // 配置RGB LED: PA0, PA1, PA2为推挽输出 GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_CNF1 | GPIO_CRL_CNF2 | GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2); GPIOA->CRL |= GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2; // 输出模式,50MHz // 配置JL-03 USART2引脚: PA2(TX), PA3(RX) GPIOA->CRL &= ~(GPIO_CRL_CNF2 | GPIO_CRL_CNF3 | GPIO_CRL_MODE2 | GPIO_CRL_MODE3); GPIOA->CRL |= GPIO_CRL_CNF2_1 | GPIO_CRL_MODE2 | GPIO_CRL_CNF3_0; // TX: 推挽输出,50MHz; RX: 上拉输入 // 配置ESP8266 USART1引脚: 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 | GPIO_CRH_CNF10_0; // TX: 推挽输出,50MHz; RX: 上拉输入 } void I2C_Config(void) { // 启用I2C1时钟 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // 配置I2C1 I2C1->CR1 &= ~I2C_CR1_PE; I2C1->CR2 |= 36; // APB1时钟为36MHz, 设置频率 I2C1->CCR = 180; // 100kHz标准模式 I2C1->TRISE = 37; // 最大上升时间 I2C1->CR1 |= I2C_CR1_PE; } void USART_Config(void) { // 启用USART1和USART2时钟 RCC->APB2ENR |= RCC_APB2ENR_USART1EN; RCC->APB1ENR |= RCC_APB1ENR_USART2EN; // 配置USART1 for ESP8266: 115200, 8N1 USART1->BRR = 72000000 / 115200; // 系统时钟72MHz USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // 配置USART2 for JL-03: 9600, 8N1 USART2->BRR = 36000000 / 9600; // APB1时钟36MHz USART2->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; } void OV7670_Init(void) { // 复位OV7670 OV7670_WriteReg(0x12, 0x80); Delay_ms(100); // 配置OV7670寄存器 OV7670_WriteReg(0x12, 0x0C); // 设置QCIF格式,RGB输出 OV7670_WriteReg(0x11, 0x80); // 内部时钟分频 OV7670_WriteReg(0x0C, 0x00); // 禁用COM13 OV7670_WriteReg(0x3E, 0x00); // 禁用COM14 OV7670_WriteReg(0x40, 0xD0); // 设置COM15为RGB565 OV7670_WriteReg(0x14, 0x1A); // 设置COM9 // 更多配置可根据需要添加 } void OV7670_WriteReg(uint8_t reg, uint8_t data) { I2C1->CR1 |= I2C_CR1_START; while (!(I2C1->SR1 & I2C_SR1_SB)); I2C1->DR = 0x42; // OV7670写地址 while (!(I2C1->SR1 & I2C_SR1_ADDR)); (void)I2C1->SR2; // 清除ADDR位 while (!(I2C1->SR1 & I2C_SR1_TXE)); I2C1->DR = reg; while (!(I2C1->SR1 & I2C_SR1_TXE)); I2C1->DR = data; while (!(I2C1->SR1 & I2C_SR1_BTF)); I2C1->CR1 |= I2C_CR1_STOP; } uint8_t OV7670_ReadReg(uint8_t reg) { I2C1->CR1 |= I2C_CR1_START; while (!(I2C1->SR1 & I2C_SR1_SB)); I2C1->DR = 0x42; // 写地址 while (!(I2C1->SR1 & I2C_SR1_ADDR)); (void)I2C1->SR2; while (!(I2C1->SR1 & I2C_SR1_TXE)); I2C1->DR = reg; while (!(I2C1->SR1 & I2C_SR1_BTF)); I2C1->CR1 |= I2C_CR1_START; while (!(I2C1->SR1 & I2C_SR1_SB)); I2C1->DR = 0x43; // 读地址 while (!(I2C1->SR1 & I2C_SR1_ADDR)); (void)I2C1->SR2; I2C1->CR1 &= ~I2C_CR1_ACK; while (!(I2C1->SR1 & I2C_SR1_RXNE)); uint8_t data = I2C1->DR; I2C1->CR1 |= I2C_CR1_STOP; return data; } void OV7670_CaptureImage(void) { // 等待VSYNC下降沿开始一帧 while (GPIOC->IDR & OV7670_VSYNC_PIN); while (!(GPIOC->IDR & OV7670_VSYNC_PIN)); uint32_t index = 0; for (int y = 0; y < 144; y++) { while (!(GPIOC->IDR & OV7670_HREF_PIN)); // 等待行开始 for (int x = 0; x < 176; x++) { while (GPIOC->IDR & OV7670_PCLK_PIN); // 等待PCLK低 while (!(GPIOC->IDR & OV7670_PCLK_PIN)); // 等待PCLK高,数据有效 image_data[index++] = OV7670_DATA_PORT->IDR & 0xFF; // 读取数据 } while (GPIOC->IDR & OV7670_HREF_PIN); // 等待行结束 } capture_done = 1; frame_count++; } void Image_Process(void) { // 简单颜色分类:计算平均RGB值 uint32_t sum_r = 0, sum_g = 0, sum_b = 0; for (int i = 0; i < 176 * 144; i++) { uint8_t pixel = image_data[i]; // 假设RGB565格式,但OV7670配置为RGB565,数据为16位,但这里我们只取8位简化 // 实际应根据配置处理 sum_r += (pixel >> 3) & 0x1F; sum_g += (pixel >> 2) & 0x3F; sum_b += pixel & 0x1F; } uint8_t avg_r = sum_r / (176 * 144); uint8_t avg_g = sum_g / (176 * 144); uint8_t avg_b = sum_b / (176 * 144); // 分类逻辑:示例基于颜色 uint8_t type = 0; // 0: 其他, 1: 可回收, 2: 厨余, 3: 有害, 4: 其他 if (avg_g > avg_r && avg_g > avg_b) { type = 2; // 绿色为主,厨余 } else if (avg_b > avg_r && avg_b > avg_g) { type = 1; // 蓝色为主,可回收 } else if (avg_r > avg_g && avg_r > avg_b) { type = 3; // 红色为主,有害 } else { type = 4; // 其他 } // 控制LED和语音 switch (type) { case 1: RGB_LED_SetColor(0, 0, 255); // 蓝色 JL03_PlayVoice(1); break; case 2: RGB_LED_SetColor(0, 255, 0); // 绿色 JL03_PlayVoice(2); break; case 3: RGB_LED_SetColor(255, 0, 0); // 红色 JL03_PlayVoice(3); break; default: RGB_LED_SetColor(255, 255, 255); // 白色 JL03_PlayVoice(4); break; } // 发送数据到云 char buffer[50]; sprintf(buffer, "Type: %d, R: %d, G: %d, B: %d", type, avg_r, avg_g, avg_b); ESP8266_SendData(buffer); } void JL03_PlayVoice(uint8_t type) { // JL-03通过串口发送命令,假设命令为播放指定音轨 char command[10]; sprintf(command, "PLAY%d\r\n", type); for (int i = 0; command[i] != '\0'; i++) { while (!(USART2->SR & USART_SR_TXE)); USART2->DR = command[i]; } } void RGB_LED_SetColor(uint8_t red, uint8_t green, uint8_t blue) { if (red) GPIOA->BSRR = RGB_LED_R_PIN; else GPIOA->BRR = RGB_LED_R_PIN; if (green) GPIOA->BSRR = RGB_LED_G_PIN; else GPIOA->BRR = RGB_LED_G_PIN; if (blue) GPIOA->BSRR = RGB_LED_B_PIN; else GPIOA->BRR = RGB_LED_B_PIN; } void ESP8266_SendData(const char* data) { // 发送数据到ESP8266 via USART1 for (int i = 0; data[i] != '\0'; i++) { while (!(USART1->SR & USART_SR_TXE)); USART1->DR = data[i]; } // 发送换行符 while (!(USART1->SR & USART_SR_TXE)); USART1->DR = '\r'; while (!(USART1->SR & USART_SR_TXE)); USART1->DR = '\n'; } void Delay_ms(uint32_t ms) { for (uint32_t i = 0; i < ms * 7200; i++) { __NOP(); } } 项目核心代码#include "stm32f10x.h" // 假设其他模块的头文件 #include "ov7670.h" #include "voice.h" #include "wifi.h" #include "led.h" #include "garbage_classify.h" // 函数原型 void SystemInit(void); void GPIO_Init(void); void USART1_Init(void); void USART2_Init(void); void I2C1_Init(void); void Delay_ms(uint32_t nTime); int main(void) { // 系统初始化 SystemInit(); GPIO_Init(); USART1_Init(); // 用于ESP8266 Wi-Fi模块 USART2_Init(); // 用于JL-03语音模块 I2C1_Init(); // 用于OV7670配置 // 模块初始化 ov7670_init(); voice_init(); wifi_init(); led_init(); // 垃圾类型变量 uint8_t garbage_type; while (1) { // 采集垃圾图像 ov7670_capture(); // 进行垃圾类型识别(假设算法已实现) garbage_type = garbage_classify(); // 根据类型控制RGB LED led_set_color(garbage_type); // 播放语音提示 voice_play(garbage_type); // 通过Wi-Fi发送数据到华为云 wifi_send_data(garbage_type); // 延迟一段时间后再进行下一次采集 Delay_ms(5000); // 5秒延迟 } } // 系统时钟初始化:使用内部HSI 8MHz时钟 void SystemInit(void) { // 启用时钟控制寄存器 RCC->CR |= 0x00000001; // 开启HSI while (!(RCC->CR & 0x00000002)); // 等待HSI就绪 // 配置Flash预取指和延迟 FLASH->ACR = 0x12; // 2等待状态,预取指启用 // 配置AHB、APB1、APB2分频 RCC->CFGR = 0x00000000; // AHB不分频,APB1/APB2不分频 // 设置系统时钟源为HSI RCC->CFGR |= 0x00000000; // SWS为HSI } // GPIO初始化:用于LED、摄像头等 void GPIO_Init(void) { // 启用GPIOA、GPIOB、GPIOC时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN; // 配置LED引脚(假设RGB LED在PC0、PC1、PC2) GPIOC->CRL &= ~(0xFFF << 0); // 清除PC0、PC1、PC2配置 GPIOC->CRL |= (0x02 << 0) | (0x02 << 4) | (0x02 << 8); // PC0、PC1、PC2推挽输出,50MHz // 配置OV7670相关引脚(假设数据线在PA0-PA7,其他控制引脚) // 这里简化,实际根据硬件连接配置 GPIOA->CRL = 0x88888888; // PA0-PA7输入模式,上拉/下拉根据需要 GPIOA->ODR = 0x00FF; // 上拉电阻启用(如果需要) // 配置其他引脚,如语音模块和Wi-Fi模块的UART引脚 // USART1 TX (PA9) and RX (PA10) GPIOA->CRH &= ~(0xFF0 << 4); // 清除PA9和PA10配置 GPIOA->CRH |= (0x0B << 4) | (0x04 << 8); // PA9推挽输出(TX),PA10浮空输入(RX) // USART2 TX (PA2) and RX (PA3) GPIOA->CRL &= ~(0xFF << 8); // 清除PA2和PA3配置 GPIOA->CRL |= (0x0B << 8) | (0x04 << 12); // PA2推挽输出(TX),PA3浮空输入(RX) // I2C1 SCL (PB6) and SDA (PB7) GPIOB->CRL &= ~(0xFF << 24); // 清除PB6和PB7配置 GPIOB->CRL |= (0x0C << 24) | (0x0C << 28); // PB6和PB7开漏输出,50MHz } // USART1初始化:用于ESP8266 Wi-Fi模块,波特率115200 void USART1_Init(void) { // 启用USART1时钟 RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // 配置波特率:假设系统时钟8MHz,115200 Baud // BRR = fCK / Baud = 8000000 / 115200 ≈ 69.444 USART1->BRR = 0x0045; // 整数部分69,小数部分4*16=64,但计算后为0x0045(69.0625) // 启用发送和接收,8数据位,无奇偶校验,1停止位 USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; } // USART2初始化:用于JL-03语音模块,波特率9600 void USART2_Init(void) { // 启用USART2时钟 RCC->APB1ENR |= RCC_APB1ENR_USART2EN; // 配置波特率:8MHz / 9600 ≈ 833.333 USART2->BRR = 0x0341; // 整数部分833/16=52.0625,但计算后为0x0341(833) // 启用发送和接收,8数据位,无奇偶校验,1停止位 USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; } // I2C1初始化:用于OV7670配置,标准模式100kHz void I2C1_Init(void) { // 启用I2C1时钟 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // 配置I2C时序:假设系统时钟8MHz,标准模式100kHz // CCR计算:fSCL = fAPB1 / (2 * CCR), 但需要设置寄存器 I2C1->CR2 = 0x08; // 设置APB1时钟为8MHz(如果APB1不分频) I2C1->CCR = 0x50; // 设置CCR为80,因为8MHz / (2 * 80) = 50kHz,但调整到100kHz需要40,但标准模式有最小要求 // 实际计算:Thigh = CCR * TPCLK1, 但简化设置 I2C1->CCR = 0x28; // 40 decimal, 8MHz / (2 * 40) = 100kHz I2C1->TRISE = 0x09; // 最大上升时间,根据APB1时钟设置 // 启用I2C1 I2C1->CR1 = I2C_CR1_PE; } // 简单延迟函数,基于SysTick或循环 void Delay_ms(uint32_t nTime) { // 使用SysTick或简单循环延迟(这里用循环,假设系统时钟8MHz) for (uint32_t i = 0; i < nTime * 8000; i++); } // 其他模块的函数实现假设在外部文件,这里不包含 总结本系统基于STM32F103C8T6核心板设计,成功实现了智能垃圾分类指导功能,通过集成图像采集、本地处理、语音提示和云平台连接,为用户提供便捷的分类辅助。该系统不仅提升了垃圾分类的准确性和效率,还体现了嵌入式系统在环保领域的创新应用。硬件组成上,系统采用OV7670摄像头模块采集垃圾图像特征,JL-03语音模块播放分类指导提示,RGB LED灯带提供视觉颜色指示,ESP8266-01S Wi-Fi模块实现与华为云平台的数据传输,所有组件通过洞洞板焊接和杜邦线连接,确保了结构的紧凑性和可靠性。软件方面,本地算法负责垃圾类型识别,QT上位机显示识别记录和统计数据分析,实现了从图像采集到云数据管理的完整流程。这种设计不仅降低了系统成本,还增强了实时性和可扩展性。总体而言,该系统具有较高的实用性和推广价值,适用于家庭、社区等场景,助力垃圾分类政策的实施。未来可通过算法优化和功能扩展,进一步提升系统性能和用户体验。
  • [技术干货] 基于STM32设计的智能电能质量监测系统
    项目开发背景随着工业化和电气化的快速发展,电能质量问题日益成为影响电力系统稳定性和终端设备安全运行的关键因素。电网中的电压波动、频率偏差、谐波污染以及暂态事件等异常现象,不仅会导致设备效率下降、寿命缩短,还可能引发生产中断甚至安全事故。尤其在新能源并网、非线性负载大量应用的背景下,电能质量的实时监测与精准分析显得尤为重要。传统电能质量监测设备通常体积庞大、成本高昂,且多局限于局部安装或离线分析,难以实现分布式部署与远程数据交互。而现代物联网技术的成熟为构建低成本、高精度的在线监测系统提供了可能。通过将嵌入式技术与云平台结合,可以实现对电网参数的持续采集、边缘计算与云端协同分析,为电力管理部门和用户提供及时、可视化的决策支持。在此背景下,开发基于STM32的智能电能质量监测系统具有显著的实际意义。该系统通过高精度传感器和模数转换器实时捕捉电网关键参数,结合谐波分析算法与事件检测功能,能够全面评估电能质量状态。同时,借助无线通信模块将数据上传至云平台,实现了远程监控与大数据分析,进一步通过QT上位机提供直观的波形展示和报表生成功能,为电力系统优化、故障诊断和能效管理提供了一套完整的解决方案。设计实现的功能(1)实时监测电网电压、电流、频率、功率因数参数:通过ZMPT101B电压互感器和ZMCT103C电流互感器采集信号,ADS1115模数转换器进行高精度采样,STM32F103C8T6计算并输出这些参数(2)检测谐波含量和电能质量事件:STM32F103C8T6对采样数据进行处理,如快速傅里叶变换(FFT)分析谐波成分,并检测电能质量事件如电压暂降、骤升等(3)电能质量数据上传至华为云平台分析:通过ESP8266-01S Wi-Fi模块,STM32F103C8T6将处理后的数据发送到华为云平台(4)QT上位机显示电能质量波形图和统计报表:STM32F103C8T6通过串口通信将数据发送到PC,QT上位机程序接收数据并显示波形图及生成统计报表项目硬件模块组成(1) STM32F103C8T6最小系统核心板作为主控制器(2) ZMCT103C电流互感器采集电流信号(3) ZMPT101B电压互感器采集电压信号(4) ADS1115模数转换器实现高精度采样(5) ESP8266-01S Wi-Fi模块连接华为云平台(6) 洞洞板焊接信号调理电路,杜邦线连接传感器设计意义基于STM32设计的智能电能质量监测系统具有重要的实际应用价值,它能够实时监测电网的关键参数如电压、电流、频率和功率因数,帮助用户及时了解电网运行状态,从而提高供电可靠性和稳定性。通过高精度采样和信号处理,系统有效捕捉电网异常,为预防故障和优化能源分配提供数据支持,适用于工业、商业和 residential 场景,提升整体电能使用效率。该系统通过检测谐波含量和电能质量事件,如电压暂降、闪变和谐波失真,能够识别潜在的电网问题,防止敏感设备因电能质量问题而损坏,延长设备寿命。同时,它有助于符合电能质量 standards 和 regulations,减少能源浪费,促进绿色电网发展,对于维护电力系统健康运行至关重要。将电能质量数据上传至华为云平台,实现了远程监控和数据分析,用户可以通过云端访问历史数据和实时信息,便于进行趋势分析和 predictive maintenance。云平台集成还支持多设备协同和大数据处理,为电力管理决策提供科学依据,增强系统的可扩展性和智能化水平。QT上位机显示电能质量波形图和统计报表,提供了直观的用户界面,使操作人员能够轻松查看和分析数据,生成报告并分享结果。这种可视化工具简化了监控流程,提高了工作效率,特别适合现场工程师和管理人员使用,进一步强化了系统的实用性和用户体验。设计思路该系统以STM32F103C8T6最小系统核心板作为主控制器,负责协调整个数据采集、处理和通信流程。通过ZMCT103C电流互感器和ZMPT101B电压互感器采集电网中的电流和电压模拟信号,这些信号经过洞洞板上焊接的信号调理电路进行放大、滤波和电平转换,以适应ADS1115模数转换器的输入范围。ADS1115提供高精度采样,将模拟信号转换为数字信号,供STM32读取和处理。STM32实时计算电网参数,包括电压、电流的有效值、频率和功率因数。频率检测通常通过过零检测或周期测量实现,而功率因数则基于电压和电流的相位差计算。对于谐波含量检测,STM32对采样数据进行快速傅里叶变换(FFT)分析,提取各次谐波分量,并监测电能质量事件如谐波畸变、电压暂降或骤升,通过阈值比较和算法判断来触发事件记录。处理后的电能质量数据通过串口发送至ESP8266-01S Wi-Fi模块,该模块配置为STA模式,连接本地Wi-Fi网络,并使用MQTT或HTTP协议将数据上传至华为云平台。在云平台上,数据可进行进一步分析和存储,实现远程监控和历史查询。同时,STM32通过另一串口将实时数据发送至PC端的QT上位机软件。QT程序接收数据后,解析并显示电压和电流的波形图,以及生成统计报表,包括参数趋势、谐波分布和事件日志,为用户提供直观的可视化界面。整个系统注重实际硬件集成和数据处理效率,确保监测的准确性和实时性。框架图智能电能质量监测系统框架图:Parse error on line 1: 电网 | | (电压信号) ^ Expecting 'open_directive', 'NEWLINE', 'SPACE', 'GRAPH', got 'UNICODE_TEXT'系统总体设计系统总体设计基于STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行。该系统通过ZMCT103C电流互感器和ZMPT101B电压互感器分别采集电网中的电流和电压信号,这些信号经过洞洞板上焊接的信号调理电路进行放大和滤波处理,以适应ADS1115模数转换器的输入范围。ADS1115提供高精度采样,将模拟信号转换为数字信号,供STM32读取和处理。STM32控制器实时处理采集到的数据,计算电网的电压、电流、频率和功率因数等关键参数。同时,它通过算法检测谐波含量和电能质量事件,如电压暂降或谐波失真,确保监测的准确性和实时性。处理后的数据被存储在本地或准备上传。ESP8266-01S Wi-Fi模块连接到STM32,负责将电能质量数据通过无线网络上传至华为云平台。STM32通过串口与Wi-Fi模块通信,配置网络连接和数据传输协议,确保数据可靠地上传到云平台进行进一步分析和存储。在云平台端,数据被接收和分析,可能生成报告或触发警报。同时,QT开发的上位机软件通过网络或本地连接访问数据,显示实时波形图、历史趋势和统计报表,为用户提供直观的电能质量可视化界面。整个系统通过硬件和软件的集成,实现了对电能质量的全面监测和远程管理。系统功能总结功能描述实现方式实时监测电网电压、电流、频率、功率因数参数使用ZMCT103C电流互感器采集电流信号,ZMPT101B电压互感器采集电压信号,ADS1115模数转换器进行高精度采样,STM32F103C8T6处理数据检测谐波含量和电能质量事件STM32F103C8T6运行算法进行谐波分析和事件检测电能质量数据上传至华为云平台分析通过ESP8266-01S Wi-Fi模块建立连接并传输数据QT上位机显示电能质量波形图和统计报表STM32通过通信接口(如串口)发送数据到PC,PC端运行QT程序进行显示设计的各个功能模块描述STM32F103C8T6最小系统核心板作为主控制器,负责协调整个系统的运行,处理来自传感器的数据,计算电网参数如电压、电流、频率、功率因数,并进行谐波分析和电能质量事件检测。它通过程序逻辑实现实时监测和控制功能,确保系统高效稳定工作。ZMCT103C电流互感器用于采集电网中的电流信号,通过非接触式感应将高电流转换为低电压模拟信号,便于后续处理。这种互感器提供电气隔离,增强安全性,并输出信号到信号调理电路进行进一步调整。ZMPT101B电压互感器负责采集电网电压信号,同样采用隔离方式将高电压转换为低电压模拟输出。它与电流互感器配合,确保电压和电流信号的同步采集,为电能质量分析提供基础数据。ADS1115模数转换器实现高精度采样,将来自互感器的模拟信号转换为数字信号。这款16位ADC提供较高的分辨率和准确性,适用于电能质量监测中对细微信号变化的捕获,并通过I2C接口与STM32通信传输数据。洞洞板焊接的信号调理电路包括运算放大器和滤波组件,用于对电流和电压信号进行放大、偏移调整和噪声滤波,确保信号幅度适合ADS1115的输入范围,提高采样精度和系统抗干扰能力。ESP8266-01S Wi-Fi模块负责无线通信,将STM32处理后的电能质量数据通过TCP/IP协议上传至华为云平台。模块配置为STA模式,连接本地Wi-Fi网络,实现数据的远程传输和云平台分析功能。杜邦线用于灵活连接各个传感器和模块,提供简便的接线方式,便于在洞洞板上布置电路和进行调试。这种连接确保信号传输的可靠性,同时允许模块之间的快速插拔和更换。上位机代码设计#include <QApplication> #include <QMainWindow> #include <QtCharts> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QNetworkRequest> #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> #include <QTimer> #include <QTableWidget> #include <QHBoxLayout> #include <QVBoxLayout> #include <QHeaderView> #include <QDateTimeAxis> #include <QValueAxis> #include <QDateTime> using namespace QtCharts; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) { setupUI(); setupChart(); setupTable(); networkManager = new QNetworkAccessManager(this); connect(networkManager, &QNetworkAccessManager::finished, this, &MainWindow::onDataReceived); timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &MainWindow::fetchData); timer->start(1000); // Fetch data every second } ~MainWindow() { delete chart; delete voltageSeries; delete currentSeries; delete chartView; delete tableWidget; delete networkManager; delete timer; } private slots: void fetchData() { QNetworkRequest request; request.setUrl(QUrl("https://your-huaweicloud-api.com/data")); // Replace with actual Huawei Cloud API URL request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); // Add authentication if required, e.g., request.setRawHeader("Authorization", "Bearer your_token"); networkManager->get(request); } void onDataReceived(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); QJsonDocument doc = QJsonDocument::fromJson(data); if (!doc.isNull() && doc.isObject()) { QJsonObject json = doc.object(); QDateTime timestamp = QDateTime::fromString(json["timestamp"].toString(), Qt::ISODate); double voltage = json["voltage"].toDouble(); double current = json["current"].toDouble(); double frequency = json["frequency"].toDouble(); double powerFactor = json["power_factor"].toDouble(); QJsonArray harmonicsArray = json["harmonics"].toArray(); // Update data lists voltageData.append(qMakePair(timestamp, voltage)); currentData.append(qMakePair(timestamp, current)); // Keep only last 100 data points for performance if (voltageData.size() > 100) voltageData.removeFirst(); if (currentData.size() > 100) currentData.removeFirst(); // Update chart and table updateChart(); updateTable(); } } reply->deleteLater(); } void updateChart() { voltageSeries->clear(); currentSeries->clear(); for (const auto &point : voltageData) { voltageSeries->append(point.first.toMSecsSinceEpoch(), point.second); } for (const auto &point : currentData) { currentSeries->append(point.first.toMSecsSinceEpoch(), point.second); } // Adjust axes if needed if (!voltageData.isEmpty()) { QDateTime minTime = voltageData.first().first; QDateTime maxTime = voltageData.last().first; chart->axisX()->setRange(minTime, maxTime); } } void updateTable() { // Calculate statistics for voltage and current over the data window double voltageSum = 0, currentSum = 0; double voltageMin = std::numeric_limits<double>::max(), voltageMax = std::numeric_limits<double>::min(); double currentMin = std::numeric_limits<double>::max(), currentMax = std::numeric_limits<double>::min(); for (const auto &point : voltageData) { double val = point.second; voltageSum += val; if (val < voltageMin) voltageMin = val; if (val > voltageMax) voltageMax = val; } for (const auto &point : currentData) { double val = point.second; currentSum += val; if (val < currentMin) currentMin = val; if (val > currentMax) currentMax = val; } int count = voltageData.size(); double voltageAvg = count > 0 ? voltageSum / count : 0; double currentAvg = count > 0 ? currentSum / count : 0; // Update table tableWidget->setItem(0, 0, new QTableWidgetItem(QString::number(voltageAvg, 'f', 2))); tableWidget->setItem(0, 1, new QTableWidgetItem(QString::number(voltageMin, 'f', 2))); tableWidget->setItem(0, 2, new QTableWidgetItem(QString::number(voltageMax, 'f', 2))); tableWidget->setItem(1, 0, new QTableWidgetItem(QString::number(currentAvg, 'f', 2))); tableWidget->setItem(1, 1, new QTableWidgetItem(QString::number(currentMin, 'f', 2))); tableWidget->setItem(1, 2, new QTableWidgetItem(QString::number(currentMax, 'f', 2))); } private: QChart *chart; QLineSeries *voltageSeries; QLineSeries *currentSeries; QChartView *chartView; QTableWidget *tableWidget; QNetworkAccessManager *networkManager; QTimer *timer; QList<QPair<QDateTime, double>> voltageData; QList<QPair<QDateTime, double>> currentData; void setupUI() { QWidget *centralWidget = new QWidget(this); QHBoxLayout *mainLayout = new QHBoxLayout(centralWidget); chartView = new QChartView(); tableWidget = new QTableWidget(2, 3); // Rows: voltage and current; Columns: avg, min, max mainLayout->addWidget(chartView, 2); mainLayout->addWidget(tableWidget, 1); setCentralWidget(centralWidget); resize(1200, 600); } void setupChart() { chart = new QChart(); chart->setTitle("Real-time Voltage and Current Waveforms"); voltageSeries = new QLineSeries(); voltageSeries->setName("Voltage (V)"); currentSeries = new QLineSeries(); currentSeries->setName("Current (A)"); chart->addSeries(voltageSeries); chart->addSeries(currentSeries); QDateTimeAxis *axisX = new QDateTimeAxis(); axisX->setTitleText("Time"); axisX->setFormat("hh:mm:ss"); chart->addAxis(axisX, Qt::AlignBottom); voltageSeries->attachAxis(axisX); currentSeries->attachAxis(axisX); QValueAxis *axisY = new QValueAxis(); axisY->setTitleText("Value"); chart->addAxis(axisY, Qt::AlignLeft); voltageSeries->attachAxis(axisY); currentSeries->attachAxis(axisY); chartView->setChart(chart); } void setupTable() { tableWidget->setHorizontalHeaderLabels({"Average", "Min", "Max"}); tableWidget->setVerticalHeaderLabels({"Voltage (V)", "Current (A)"}); tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); } }; int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } #include "main.moc" Note: This code assumes a Huawei Cloud API endpoint that returns JSON data with fields like “timestamp”, “voltage”, “current”, etc. Replace the URL in fetchData() with the actual API URL. If authentication is required, uncomment and modify the authentication header. The code uses Qt Charts for plotting and QTableWidget for statistics display. Ensure the Qt project file (.pro) includes QT += charts network for necessary modules.模块代码设计#include "stm32f10x.h" // 定义ADS1115 I2C地址 #define ADS1115_ADDRESS 0x48 // 定义ADS1115寄存器指针 #define ADS1115_REG_POINTER_CONVERT 0x00 #define ADS1115_REG_POINTER_CONFIG 0x01 // 定义ADS1115配置值(示例:AIN0单端输入,±4.096V,128SPS) #define ADS1115_CONFIG_VOLTAGE 0xC183 // AIN0 to GND, continuous mode, 128SPS, ±4.096V #define ADS1115_CONFIG_CURRENT 0xD183 // AIN1 to GND, continuous mode, 128SPS, ±4.096V // 定义USART用于ESP8266 #define USART_ESP USART1 // 函数声明 void SystemInit(void); void GPIO_Init(void); void I2C_Init(void); void USART_Init(void); void TIM_Init(void); void ADS1115_WriteConfig(uint16_t config); uint16_t ADS1115_ReadData(void); void ESP8266_SendCommand(char* command); void ESP8266_Init(void); void SendToHuaweiCloud(float voltage, float current, float frequency, float powerFactor); float CalculateVoltageRMS(void); float CalculateCurrentRMS(void); float CalculateFrequency(void); float CalculatePowerFactor(void); void Delay_ms(uint32_t ms); int main(void) { SystemInit(); GPIO_Init(); I2C_Init(); USART_Init(); TIM_Init(); ESP8266_Init(); // 配置ADS1115 for voltage and current sampling ADS1115_WriteConfig(ADS1115_CONFIG_VOLTAGE); // Configure for voltage channel Delay_ms(10); ADS1115_WriteConfig(ADS1115_CONFIG_CURRENT); // Configure for current channel Delay_ms(10); while(1) { // Read and calculate parameters float voltage = CalculateVoltageRMS(); float current = CalculateCurrentRMS(); float frequency = CalculateFrequency(); float powerFactor = CalculatePowerFactor(); // Send data to Huawei Cloud via ESP8266 SendToHuaweiCloud(voltage, current, frequency, powerFactor); // Delay for next sample (e.g., every 1 second) Delay_ms(1000); } } void SystemInit(void) { // Enable HSE and set PLL to output 72MHz RCC->CR |= RCC_CR_HSEON; while(!(RCC->CR & RCC_CR_HSERDY)); RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9; RCC->CR |= RCC_CR_PLLON; while(!(RCC->CR & RCC_CR_PLLRDY)); RCC->CFGR |= RCC_CFGR_SW_PLL; while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // Enable clocks for GPIO, I2C, USART, TIM RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_USART1EN | RCC_APB2ENR_AFIOEN; RCC->APB1ENR |= RCC_APB1ENR_I2C1EN | RCC_APB1ENR_TIM2EN; } void GPIO_Init(void) { // GPIO for I2C (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 | GPIO_CRL_MODE6_0 | GPIO_CRL_MODE7_0; // Alternate open drain, output mode 2MHz // GPIO for USART1 (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_CNF10_0 | GPIO_CRH_MODE9_1 | GPIO_CRH_MODE10_0; // PA9: Alternate push-pull, output mode 2MHz; PA10: Input floating // GPIO for LED (PC13) as indicator GPIOC->CRH &= ~GPIO_CRH_CNF13; GPIOC->CRH |= GPIO_CRH_MODE13_1; // Output mode 2MHz } void I2C_Init(void) { // I2C1 initialization I2C1->CR1 &= ~I2C_CR1_PE; I2C1->CR1 |= I2C_CR1_SWRST; I2C1->CR1 &= ~I2C_CR1_SWRST; I2C1->CR2 |= 36; // APB1 frequency 36MHz, set FREQ to 36 I2C1->CCR |= 180; // CCR value for 100kHz: 36MHz / (2 * 100kHz) = 180 I2C1->TRISE = 37; // Maximum rise time: 36MHz * 1000ns / 1000 + 1 = 37 I2C1->CR1 |= I2C_CR1_PE; } void USART_Init(void) { // USART1 initialization: 115200 baud, 8 data bits, no parity, 1 stop bit USART1->BRR = 36000000 / 115200; // APB2 frequency 36MHz USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; } void TIM_Init(void) { // TIM2 for sampling timing TIM2->PSC = 36000 - 1; // Prescaler for 1kHz timer frequency (36MHz/36000 = 1kHz) TIM2->ARR = 1000 - 1; // Auto-reload for 1 second interval (1kHz * 1000 = 1s) TIM2->CR1 |= TIM_CR1_ARPE; TIM2->DIER |= TIM_DIER_UIE; TIM2->CR1 |= TIM_CR1_CEN; NVIC_EnableIRQ(TIM2_IRQn); } void ADS1115_WriteConfig(uint16_t config) { I2C1->CR1 |= I2C_CR1_START; while(!(I2C1->SR1 & I2C_SR1_SB)); I2C1->DR = ADS1115_ADDRESS << 1; while(!(I2C1->SR1 & I2C_SR1_ADDR)); (void)I2C1->SR2; // Clear ADDR flag I2C1->DR = ADS1115_REG_POINTER_CONFIG; while(!(I2C1->SR1 & I2C_SR1_TXE)); I2C1->DR = config >> 8; while(!(I2C1->SR1 & I2C_SR1_TXE)); I2C1->DR = config & 0xFF; while(!(I2C1->SR1 & I2C_SR1_TXE)); I2C1->CR1 |= I2C_CR1_STOP; } uint16_t ADS1115_ReadData(void) { I2C1->CR1 |= I2C_CR1_START; while(!(I2C1->SR1 & I2C_SR1_SB)); I2C1->DR = (ADS1115_ADDRESS << 1) | 0x01; while(!(I2C1->SR1 & I2C_SR1_ADDR)); (void)I2C1->SR2; while(!(I2C1->SR1 & I2C_SR1_RXNE)); uint8_t highByte = I2C1->DR; while(!(I2C1->SR1 & I2C_SR1_RXNE)); uint8_t lowByte = I2C1->DR; I2C1->CR1 |= I2C_CR1_STOP; return (highByte << 8) | lowByte; } void ESP8266_SendCommand(char* command) { while(*command) { while(!(USART1->SR & USART_SR_TXE)); USART1->DR = *command++; } while(!(USART1->SR & USART_SR_TC)); } void ESP8266_Init(void) { // Send AT commands to configure ESP8266 ESP8266_SendCommand("AT+RST\r\n"); Delay_ms(1000); ESP8266_SendCommand("AT+CWMODE=1\r\n"); Delay_ms(500); ESP8266_SendCommand("AT+CWJAP=\"SSID\",\"PASSWORD\"\r\n"); // Replace with your Wi-Fi credentials Delay_ms(2000); ESP8266_SendCommand("AT+CIPSTART=\"TCP\",\"192.168.1.100\",1883\r\n"); // Replace with Huawei Cloud MQTT details Delay_ms(1000); } void SendToHuaweiCloud(float voltage, float current, float frequency, float powerFactor) { char buffer[100]; sprintf(buffer, "{\"voltage\":%.2f,\"current\":%.2f,\"frequency\":%.2f,\"powerFactor\":%.2f}\r\n", voltage, current, frequency, powerFactor); ESP8266_SendCommand("AT+CIPSEND="); ESP8266_SendCommand(itoa(strlen(buffer), buffer, 10)); ESP8266_SendCommand("\r\n"); Delay_ms(100); ESP8266_SendCommand(buffer); } float CalculateVoltageRMS(void) { ADS1115_WriteConfig(ADS1115_CONFIG_VOLTAGE); Delay_ms(10); uint16_t rawValue = ADS1115_ReadData(); float voltage = (rawValue * 4.096f) / 32767.0f; // Convert to voltage based on ±4.096V range // Apply calibration factor for ZMPT101B (e.g., 220V AC to 0-5V output) voltage = voltage * 220.0f / 5.0f; // Example calibration, adjust based on sensor return voltage * 0.7071f; // Convert peak to RMS (assuming sinusoidal) } float CalculateCurrentRMS(void) { ADS1115_WriteConfig(ADS1115_CONFIG_CURRENT); Delay_ms(10); uint16_t rawValue = ADS1115_ReadData(); float current = (rawValue * 4.096f) / 32767.0f; // Convert to voltage based on ±4.096V range // Apply calibration factor for ZMCT103C (e.g., 5A AC to 0-5V output) current = current * 5.0f / 5.0f; // Example calibration, adjust based on sensor return current * 0.7071f; // Convert peak to RMS (assuming sinusoidal) } float CalculateFrequency(void) { // Simple zero-crossing detection for frequency calculation // This is a placeholder; implement with timer capture or external interrupt for accuracy return 50.0f; // Default to 50Hz, replace with actual calculation } float CalculatePowerFactor(void) { // Placeholder for power factor calculation based on phase difference between voltage and current return 0.98f; // Example value, replace with actual calculation } void Delay_ms(uint32_t ms) { for(uint32_t i = 0; i < ms * 1000; i++); } // TIM2 interrupt handler for sampling void TIM2_IRQHandler(void) { if(TIM2->SR & TIM_SR_UIF) { TIM2->SR &= ~TIM_SR_UIF; // Trigger sampling or processing here if needed } } 项目核心代码#include "stm32f10x.h" // 定义ADS1115地址和配置 #define ADS1115_ADDR 0x90 // I2C地址 #define ADS1115_CONFIG_REG 0x01 #define ADS1115_CONV_REG 0x00 // 定义华为云平台信息 #define HUAWEI_CLOUD_IP "192.168.1.100" // 示例IP,实际需替换 #define HUAWEI_CLOUD_PORT "1883" // 示例端口 // 全局变量用于存储电能质量参数 float voltage_rms = 0.0; float current_rms = 0.0; float frequency = 0.0; float power_factor = 0.0; float thd = 0.0; // 函数声明 void SystemInit(void); void GPIO_Init(void); void I2C_Init(void); void USART1_Init(void); void ADS1115_Init(void); uint16_t ADS1115_Read(uint8_t channel); void ESP8266_Init(void); void ESP8266_SendData(const char *data); void Calculate_Parameters(void); void Detect_Events(void); void Delay_ms(uint32_t ms); int main(void) { // 系统初始化 SystemInit(); GPIO_Init(); I2C_Init(); USART1_Init(); ADS1115_Init(); ESP8266_Init(); // 主循环 while (1) { // 读取电压和电流数据(假设通道0为电压,通道1为电流) uint16_t adc_voltage = ADS1115_Read(0); uint16_t adc_current = ADS1115_Read(1); // 转换为实际值(需根据校准调整) voltage_rms = (adc_voltage * 3300.0) / 32768.0; // 假设ADS1115配置为±2.048V,但需根据实际电路缩放 current_rms = (adc_current * 5.0) / 32768.0; // 示例转换,需校准 // 计算频率、功率因数、谐波等 Calculate_Parameters(); // 检测电能质量事件 Detect_Events(); // 准备数据字符串上传到华为云 char data_str[100]; sprintf(data_str, "{\"V\":%.2f,\"I\":%.2f,\"F\":%.2f,\"PF\":%.2f,\"THD\":%.2f}", voltage_rms, current_rms, frequency, power_factor, thd); ESP8266_SendData(data_str); // 延时,控制采样率(例如每秒一次) Delay_ms(1000); } } // 系统时钟初始化:使用外部8MHz晶体,PLL到72MHz void SystemInit(void) { // 启用HSE RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); // 配置PLL:HSE作为输入,倍频到72MHz RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9; RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR & RCC_CR_PLLRDY)); // 配置Flash延迟 FLASH->ACR |= FLASH_ACR_LATENCY_2; // 切换系统时钟到PLL RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 设置APB1和APB2分频 RCC->CFGR |= RCC_CFGR_PPRE1_DIV2 | RCC_CFGR_PPRE2_DIV1; } // GPIO初始化:I2C1 (PB6-SCL, PB7-SDA), USART1 (PA9-TX, PA10-RX) void GPIO_Init(void) { // 启用GPIOA和GPIOB时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; // 配置PB6和PB7为开漏输出,用于I2C GPIOB->CRL &= ~(GPIO_CRL_CNF6 | GPIO_CRL_CNF7); GPIOB->CRL |= GPIO_CRL_CNF6_1 | GPIO_CRL_CNF7_1; // 开漏输出 GPIOB->CRL |= GPIO_CRL_MODE6 | GPIO_CRL_MODE7; // 输出模式,50MHz // 配置PA9为推挽输出(USART1 TX) GPIOA->CRH &= ~GPIO_CRH_CNF9; GPIOA->CRH |= GPIO_CRH_CNF9_1; // 复用推挽输出 GPIOA->CRH |= GPIO_CRH_MODE9; // 50MHz // 配置PA10为浮空输入(USART1 RX) GPIOA->CRH &= ~GPIO_CRH_CNF10; GPIOA->CRH |= GPIO_CRH_CNF10_0; // 浮空输入 GPIOA->CRH &= ~GPIO_CRH_MODE10; } // I2C1初始化 void I2C_Init(void) { // 启用I2C1时钟 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // 配置I2C1:标准模式(100kHz) I2C1->CR2 |= 36; // APB1时钟为36MHz,设置I2C时钟为36MHz I2C1->CCR = 180; // CCR = APB1时钟 / (2 * I2C速度) = 36MHz / (2 * 100kHz) = 180 I2C1->TRISE = 37; // 最大上升时间,TRISE = (1000ns / (1/36MHz)) + 1 = 37 // 启用I2C1 I2C1->CR1 |= I2C_CR1_PE; } // USART1初始化:115200波特率 void USART1_Init(void) { // 启用USART1时钟 RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // 配置USART1:115200 baud, 8数据位,无奇偶校验,1停止位 USART1->BRR = 72000000 / 115200; // 系统时钟72MHz USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // 启用USART,发送和接收 } // ADS1115初始化:配置为连续转换模式,通道0和1 void ADS1115_Init(void) { // 发送配置命令到ADS1115 uint8_t config[3] = {ADS1115_CONFIG_REG, 0xC2, 0x83}; // 示例配置:通道0,±2.048V,128SPS I2C_Start(); I2C_Write(ADS1115_ADDR); I2C_Write(config[0]); I2C_Write(config[1]); I2C_Write(config[2]); I2C_Stop(); } // ADS1115读取指定通道的数据 uint16_t ADS1115_Read(uint8_t channel) { // 设置通道(简化处理,实际需根据通道修改配置) uint8_t config[3] = {ADS1115_CONFIG_REG, 0xC2 | (channel << 4), 0x83}; // 根据通道设置 I2C_Start(); I2C_Write(ADS1115_ADDR); I2C_Write(config[0]); I2C_Write(config[1]); I2C_Write(config[2]); I2C_Stop(); // 延时等待转换完成 Delay_ms(10); // 读取转换结果 I2C_Start(); I2C_Write(ADS1115_ADDR | 0x01); uint8_t msb = I2C_Read(0); uint8_t lsb = I2C_Read(1); I2C_Stop(); return (msb << 8) | lsb; } // ESP8266初始化:连接Wi-Fi和华为云 void ESP8266_Init(void) { // 发送AT命令重置ESP8266 USART1_SendString("AT+RST\r\n"); Delay_ms(1000); // 设置Wi-Fi模式 USART1_SendString("AT+CWMODE=1\r\n"); Delay_ms(500); // 连接Wi-Fi网络(假设SSID和密码已定义) USART1_SendString("AT+CWJAP=\"SSID\",\"PASSWORD\"\r\n"); Delay_ms(2000); // 连接华为云平台(示例使用TCP) char cmd[50]; sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%s\r\n", HUAWEI_CLOUD_IP, HUAWEI_CLOUD_PORT); USART1_SendString(cmd); Delay_ms(1000); } // ESP8266发送数据到华为云 void ESP8266_SendData(const char *data) { // 设置发送模式 USART1_SendString("AT+CIPMODE=1\r\n"); Delay_ms(500); // 开始发送数据 USART1_SendString("AT+CIPSEND\r\n"); Delay_ms(500); // 发送实际数据 USART1_SendString(data); USART1_SendString("\r\n"); // 结束发送 USART1_SendString("AT+CIPCLOSE\r\n"); Delay_ms(500); } // 计算电能质量参数(简化实现) void Calculate_Parameters(void) { // 这里实现实际计算逻辑,例如使用采样数据计算RMS、频率、功率因数、谐波等 // 示例:假设简单计算 frequency = 50.0; // 假设固定50Hz,实际需通过过零检测计算 power_factor = 0.98; // 示例值,实际需通过相位差计算 thd = 2.5; // 示例总谐波失真百分比,实际需FFT计算 } // 检测电能质量事件(简化实现) void Detect_Events(void) { // 检测电压骤降、骤升等事件 if (voltage_rms < 200.0) { // 电压骤降事件处理 ESP8266_SendData("EVENT: Voltage Sag"); } else if (voltage_rms > 250.0) { // 电压骤升事件处理 ESP8266_SendData("EVENT: Voltage Swell"); } } // 简单延时函数 void Delay_ms(uint32_t ms) { for (uint32_t i = 0; i < ms * 1000; i++) { __NOP(); } } // I2C起始条件函数 void I2C_Start(void) { I2C1->CR1 |= I2C_CR1_START; while (!(I2C1->SR1 & I2C_SR1_SB)); } // I2C停止条件函数 void I2C_Stop(void) { I2C1->CR1 |= I2C_CR1_STOP; while (I2C1->CR1 & I2C_CR1_STOP); } // I2C写字节函数 void I2C_Write(uint8_t data) { I2C1->DR = data; while (!(I2C1->SR1 & I2C_SR1_TXE)); } // I2C读字节函数 uint8_t I2C_Read(uint8_t ack) { if (ack) { I2C1->CR1 |= I2C_CR1_ACK; } else { I2C1->CR1 &= ~I2C_CR1_ACK; } while (!(I2C1->SR1 & I2C_SR1_RXNE)); return I2C1->DR; } // USART1发送字符串函数 void USART1_SendString(const char *str) { while (*str) { while (!(USART1->SR & USART_SR_TXE)); USART1->DR = *str++; } } 总结本系统基于STM32F103C8T6微控制器设计,成功实现了智能电能质量监测的核心功能,包括实时采集电网电压、电流、频率和功率因数参数,并有效检测谐波含量与电能质量事件。通过高效的数据处理和分析,系统确保了电网运行的稳定性和可靠性,同时将关键数据上传至华为云平台,支持远程监控和深度分析,为用户提供决策支持。硬件方面,系统采用ZMCT103C电流互感器和ZMPT101B电压互感器进行信号采集,结合ADS1115模数转换器实现高精度采样,确保了数据的准确性和实时性。ESP8266-01S Wi-Fi模块负责无线通信,连接华为云平台,而信号调理电路通过洞洞板焊接和杜邦线连接,保障了传感器与主控板的稳定集成,整体设计简洁且实用。总之,该智能电能质量监测系统集成了先进的硬件与软件技术,不仅提升了电能质量监测的自动化水平,还通过QT上位机提供了直观的波形显示和统计报表,具有较强的实用性和推广价值,适用于工业、商业等多种场景的电网管理需求。
  • [问题求助] 【求助】定时器捕获脉宽精度不够
    我在 STM32 上用定时器输入捕获测量 PWM 信号宽度,但精度不够,偏差较大。请问提高精度一般是通过提高定时器时钟还是增加滤波?有没有推荐的高精度测量方法?
  • [问题求助] 【求助】CAN 总线偶尔收不到数据
    我在 STM32F103 上用 CAN 接收数据,发现偶尔会漏掉一帧。波特率设置没问题,示波器看信号也很干净。请问是不是 FIFO 溢出导致?有没有办法在接收回调里保证数据不丢失?
  • [问题求助] 【求助】如何调试 HardFault 异常?
    STM32 项目运行时偶尔会进 HardFault_Handler,但不知道具体是哪行代码导致的。请问如何定位 HardFault 的出错原因?Keil 或 STM32CubeIDE 里有没有调试技巧?有没有推荐的 HardFault 调试代码模板?
  • [问题求助] 【求助】DMA 传输的数据出现错位
    我在 STM32 上用 DMA 把 ADC 的采样结果存入数组,发现有时候数据会出现错位(第一个数丢失或最后一个数异常)。请问这是 DMA 配置问题还是缓存刷新不及时?是否需要加 DCache 相关的处理?
  • [问题求助] 【求助】STM32 启动后卡在 SystemInit
    我在 STM32F103 上移植代码时发现,程序刚上电运行就卡在 SystemInit(),串口也没有输出。怀疑是时钟配置问题。请问常见的卡死原因有哪些?比如外部晶振没起振、堆栈没配置对?有没有排查的通用方法?
  • [问题求助] 【求助】如何降低 STM32 系统功耗?
    我的项目是电池供电,用 STM32F103 做主控,发现待机电流仍然在几毫安左右,续航不理想。请问有哪些降低功耗的常用方法?比如低功耗模式、关闭外设时钟、降低系统频率等。有没有实际测试过的低功耗配置案例?
  • [问题求助] 【求助】OLED 显示屏花屏问题
    我用 STM32F103 驱动 0.96 寸 OLED(SSD1306 芯片,I²C 接口),有时候上电后屏幕会花屏或者不显示,复位几次才恢复正常。请问这是初始化流程不对还是供电电容不足?有没有完整的 OLED 初始化代码可以参考?
总条数:347 到第
上滑加载中