基于STM32的智能药箱监控系统设计

举报
DS小龙哥 发表于 2025/06/22 18:44:04 2025/06/22
【摘要】 项目开发背景 项目开发背景随着社会老龄化进程加速和慢性病患者数量的持续增长,长期、规律服药成为大量人群维持健康的必要手段。然而,患者(尤其是老年人和独居人群)常因记忆力减退、生活节奏混乱或药物方案复杂而出现漏服、错服、多服等问题,严重影响治疗效果,甚至引发严重后果。传统药箱功能单一,无法主动干预服药行为,也缺乏有效的服药依从性记录手段,使得患者自身及照护者难以精准掌握实际服药情况。现代物联...

项目开发背景

项目开发背景

随着社会老龄化进程加速和慢性病患者数量的持续增长,长期、规律服药成为大量人群维持健康的必要手段。然而,患者(尤其是老年人和独居人群)常因记忆力减退、生活节奏混乱或药物方案复杂而出现漏服、错服、多服等问题,严重影响治疗效果,甚至引发严重后果。传统药箱功能单一,无法主动干预服药行为,也缺乏有效的服药依从性记录手段,使得患者自身及照护者难以精准掌握实际服药情况。

现代物联网技术和移动互联网的快速发展为解决这一难题提供了新的可能。通过将嵌入式系统、传感器、无线通信和云平台技术整合到药箱设计中,可以构建一个具备智能提醒、状态监控和远程管理功能的系统。智能药箱监控系统应运而生,旨在利用智能化手段提升服药依从性,保障用药安全。

该系统以高性能、低功耗的STM32微控制器为核心,结合高精度实时时钟确保提醒的准时性,利用磁感应开关精确无接触地记录药箱开启事件。通过OLED屏幕直观展示关键时间信息,辅以蜂鸣器进行主动提醒。借助WiFi模块和华为云物联网平台,系统实现了服药记录、设备状态的实时上传与云端存储,打破了地域限制。配套的移动APP则为用户或其家属、医护人员提供了便捷的远程监控和管理窗口,能及时查看提醒、服药历史记录及设备状态,实现对患者服药行为的有效监督和关怀。

该设计不仅着眼于提升个体用户的用药体验和安全性,减轻家庭照护负担,同时也为医疗机构获取真实服药数据、评估疗效、优化治疗方案提供了技术支撑,是智慧医疗在家庭健康管理领域的重要应用探索。

设计实现的功能

(1)定时提醒用户服药并蜂鸣报警:通过DS3231实时时钟模块精确计时,到达预设服药时间时,STM32触发有源蜂鸣器发出警报声。
(2)OLED显示当前时间和下次服药时间:通过SPI接口驱动OLED屏,实时显示DS3231提供的当前时间及预设的下次服药时间。
(3)药箱开启记录自动统计与上传:磁感应干簧管检测药箱开关状态,STM32记录开启时间并存储,同时统计开启次数。
(4)远程APP查看提醒与记录数据:Qt开发的Android APP通过华为云物联网平台同步数据,用户可实时查看服药提醒、药箱开启记录及统计信息。
(5)所有数据上传至华为云物联网平台:STM32通过ESP8266 WiFi模块(MQTT协议)上传实时时间、服药提醒状态、药箱开启记录等数据至华为云平台。

项目硬件模块组成

(1)主控芯片:STM32F103C8T6
(2)实时时钟模块:DS3231(I2C)
(3)药箱开启检测:磁感应干簧管
(4)蜂鸣报警模块:高电平触发有源蜂鸣器
(5)显示模块:0.96寸SPI OLED
(6)联网模块:ESP8266 WiFi模块(MQTT协议)
(7)APP端:Qt编写Android APP
(8)供电模块:DC 5V电源 + 锂电池 + TP4056充电模块

设计意义

该智能药箱监控系统的设计具有以下实际意义:

提升用药依从性与安全性。系统通过精准的定时蜂鸣报警和OLED显示,主动提醒用户按时服药,有效降低了因遗忘或疏忽导致的漏服、错服风险,尤其对于需要长期规律服药的慢性病患者和健忘的老年群体至关重要。这种自动化提醒机制显著提升了用户的用药依从性,是保障治疗效果和健康安全的核心环节。

实现用药行为的数字化管理。利用磁感应干簧管自动检测并记录药箱的每次开启动作,结合实时时钟标记时间点,系统客观地统计了用户的实际服药行为。这些关键数据自动通过ESP8266模块上传至华为云物联网平台,形成了可追溯的电子用药日志,为自我管理或医护人员评估用药规律性提供了可靠的数据支撑,弥补了传统人工记录的不足。

构建远程监护与信息同步能力。基于华为云平台的数据存储和MQTT协议通信,系统实现了服药记录、提醒状态等信息的云端同步。配合Qt开发的Android APP,患者家属或护理人员能够远程、实时地查看用户的服药提醒状态和开箱记录历史。这种远程监控能力极大地方便了看护者对独居老人或特殊患者的用药情况进行关注和必要的干预,增强了照护的及时性。

提供本地化直观信息交互。OLED屏幕持续显示清晰的当前时间和下次服药时间,使用户无需依赖手机或复杂操作即可快速获取关键信息。这种简洁、常显的本地化交互设计,尤其符合老年用户的操作习惯,提升了系统的易用性和信息获取效率,是确保提醒有效触达用户的重要补充手段。

平衡成本、可靠性与实用性。系统硬件选型聚焦成熟可靠且成本可控的模块(如STM32F103C8T6、DS3231、ESP8266),结合锂电池与TP4056充电模块实现稳定供电。整体设计在满足核心功能需求(精准提醒、状态记录、数据上传、远程查看)的前提下,注重了系统的稳定性、功耗控制及后续的可维护性与推广成本,体现了较高的工程实用价值。

设计思路

设计思路

系统以STM32F103C8T6为核心控制器,通过模块化设计实现功能集成。DS3231实时时钟模块通过I2C接口与STM32连接,提供精准时间基准,用于设定服药提醒周期。系统初始化时,从DS3231读取当前时间,并在OLED屏(SPI接口驱动)动态显示当前时间及预设的下次服药时间。OLED显示采用分页刷新机制,降低功耗。

当到达预设服药时间时,STM32通过GPIO输出高电平触发有源蜂鸣器报警,持续提醒直至用户手动关闭或超时停止。药箱状态监测通过干簧管传感器实现:干簧管安装在箱盖与箱体接合处,箱盖闭合时干簧管处于断开状态;开启时磁铁远离导致干簧管闭合,STM32检测到GPIO电平变化即记录开启时间戳,并通过内部Flash存储事件记录。

数据上传通过ESP8266模块实现,STM32通过UART串口与ESP8266通信,采用AT指令集配置WiFi连接。药箱开启记录及设备状态数据封装为JSON格式,通过MQTT协议发布到华为云物联网平台指定主题。云端数据存储后,由Qt开发的Android APP订阅云端主题,实现服药提醒推送、历史记录查询及设备状态同步功能。

供电系统采用TP4056充电管理模块,外接锂电池作为备用电源。主电源为5V DC输入时,TP4056为锂电池充电;外部断电时自动切换至锂电池供电,确保系统持续运行。STM32通过ADC监测电池电压,低电量时在OLED显示警告信息。

软件层面采用分层架构:硬件驱动层管理各外设初始化,业务逻辑层处理服药提醒判断、记录统计,网络层负责MQTT数据封装与重传机制。关键数据(如服药时间配置)存储于STM32 Flash,防止断电丢失。采用事件驱动与定时器中断结合的方式优化系统响应效率。

框架图

智能药箱监控系统框架图

+-----------------------------------------------------------------------+  
|                             智能药箱监控系统                           |  
+----------------------------+---------------------+--------------------+  
|         STM32F103C8T6      |       ESP8266       |      华为云物联网平台 |  
|   (主控核心)               |   (WiFi联网模块)     |  (数据存储与分析)    |  
| +------------------------+ | +-----------------+ | +-----------------+ |  
| |  实时时钟模块 DS3231   | | |  MQTT协议通信    | | | 药箱开启记录    | |  
| | (I2C接口,精确计时)    | | | (UART串口通信)   | | | 服药提醒状态    | |  
| +-----------+------------+ | +--------+--------+ | +--------+--------+ |  
|             | I2C          |          | UART     |          | MQTT      |  
| +-----------v------------+ | +--------v--------+ |          |          |  
| |     OLED显示模块       | | |  数据上传/接收   | <--------->+          |  
| | (SPI接口,显示时间/提醒)| | +-----------------+ |          +----------+ |  
| +-----------+------------+ |                     |                     |  
|             |              +---------------------+                     |  
| +-----------v------------+                                            |  
| | 蜂鸣器报警模块         |                                            |  
| | (GPIO高电平触发)       |                                            |  
| +-----------+------------+                                            |  
|             |                                                         |  
| +-----------v------------+ +---------------------+                    |  
| | 干簧管传感器           | | 供电模块             |                    |  
| | (药箱开启检测)         | | ? DC 5V电源输入     |                    |  
| | (GPIO输入检测)         | | ? TP4056充电管理    |                    |  
| +------------------------+ | ? 锂电池备用电源     |                    |  
|                            +---------------------+                    |  
+-----------------------------------------------------------------------+  
|                              Qt开发Android APP                        |  
| 功能:                                                                |  
| ? 实时接收服药提醒通知                                                |  
| ? 查看历史药箱开启记录                                                |  
| ? 远程同步设备状态                                                    |  
+-----------------------------------------------------------------------+  

硬件连接说明:

  1. DS3231 → STM32 (I2C: PB6-SCL, PB7-SDA)
  2. OLED → STM32 (SPI: PA5-SCK, PA7-SDA)
  3. 干簧管 → STM32 (GPIO输入: PA0, 磁吸触发低电平)
  4. 蜂鸣器 → STM32 (GPIO输出: PA1, 高电平鸣响)
  5. ESP8266 → STM32 (UART: PA9-TX, PA10-RX, AT+MQTT指令)
  6. TP4056 → 锂电池 (充放电管理) → STM32 (5V供电)

数据流方向:

  • 药箱开启记录/服药状态 → STM32 → ESP8266 (MQTT) → 华为云平台 → Qt APP
  • 服药时间配置 → Qt APP → 华为云平台 → ESP8266 → STM32 → DS3231/OELD/蜂鸣器

系统总体设计

系统以STM32F103C8T6为主控芯片,协调各模块实现药箱智能化管理。主控通过I2C接口连接DS3231高精度实时时钟模块,为系统提供可靠的时间基准,确保服药提醒的准确性。药箱开启状态由磁感应干簧管检测,当箱盖开启时产生电平变化信号,主控记录事件时间并统计开启次数。

SPI接口的0.96寸OLED显示屏实时显示当前时间及预设的下次服药时间,提供直观的人机交互界面。到达服药时间时,主控通过GPIO输出高电平触发有源蜂鸣器报警,持续提醒用户服药。

ESP8266 WiFi模块通过串口与主控通信,采用MQTT协议将药箱开启记录、系统状态等数据实时上传至华为云物联网平台。Qt开发的Android APP同步云端数据,实现服药提醒推送、历史记录查询及药箱状态远程监控功能。

供电系统由5V DC电源适配器供电,TP4056充电模块管理锂电池充放电,确保市电中断时备用电源持续工作。主控程序采用中断与轮询结合的方式,高效处理实时时钟中断、干簧管状态检测、数据显示更新及云端数据交互等任务。

系统功能总结

功能需求 实现方式 硬件模块
定时提醒用户服药并蜂鸣报警 DS3231实时时钟触发定时事件,STM32控制蜂鸣器报警 DS3231实时时钟模块、有源蜂鸣器
OLED显示当前时间和下次服药时间 STM32读取DS3231时间数据,通过SPI驱动OLED显示时间信息 DS3231实时时钟模块、SPI OLED
药箱开启记录自动统计与上传 干簧管检测药箱门状态变化,STM32记录开启次数和时间,通过ESP8266上传 磁感应干簧管、ESP8266 WiFi模块
远程APP查看提醒与记录数据 ESP8266通过MQTT协议与华为云同步数据,Qt开发的Android APP实时获取服药提醒、药箱开启记录等状态 ESP8266 WiFi模块
所有数据上传至华为云物联网平台 STM32整合时间、药箱状态、报警记录等数据,通过ESP8266以MQTT协议上传至华为云 ESP8266 WiFi模块

设计的各个功能模块描述

STM32F103C8T6主控芯片作为系统的核心处理器,负责整体协调和控制。它通过I2C接口与DS3231实时时钟模块通信,获取精确时间信息,用于实现定时服药提醒和记录功能。同时,主控处理药箱开启检测信号、驱动蜂鸣器报警、更新OLED显示内容,并通过串口与ESP8266模块交互实现数据上传。

DS3231实时时钟模块提供高精度时间参考,确保系统能准确读取当前时间和设置下次服药时间。主控通过I2C协议定期读取该模块数据,用于触发定时提醒和记录事件时间戳,保证报警和显示功能的可靠性。

磁感应干簧管作为药箱开启检测传感器,安装在药箱门上。当药箱开启时,干簧管状态变化(如闭合或断开),触发主控的中断或轮询检测。主控自动记录每次开启的时间戳和次数,并统计数据用于后续上传,实现开启记录的自动化管理。

高电平触发有源蜂鸣器模块用于服药提醒报警。在预设服药时间到达时,主控输出高电平信号驱动蜂鸣器发出声音警报,提醒用户服药。报警持续一定时间或直到用户干预,确保提醒功能有效执行。

0.96寸SPI OLED显示模块实时展示系统信息。主控通过SPI接口发送指令,在OLED屏幕上显示当前时间和下次服药时间,便于用户直观查看。显示内容根据实时时钟数据动态更新,提供清晰的时间参考。

ESP8266 WiFi模块实现网络连接功能。主控通过串口发送药箱开启记录、服药提醒状态等数据,ESP8266使用MQTT协议将数据上传至华为云物联网平台。同时,该模块接收云平台指令或APP同步请求,支持远程数据交互。

Qt编写的Android APP端提供远程监控界面。用户通过APP连接到华为云平台,查看服药提醒、药箱开启记录等同步数据,实现状态实时追踪和提醒管理。APP与云平台交互,确保数据一致性和远程访问。

DC 5V电源、锂电池和TP4056充电模块组成供电系统。DC 5V电源为主控和外设供电,锂电池作为备用电源在断电时维持系统运行,TP4056模块管理锂电池充电过程,确保系统稳定供电和续航能力。

上位机代码设计

上位机代码设计(基于Qt的Android APP)

// main.cpp - 应用入口
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "cloudconnector.h"
#include "datamanager.h"

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    
    // 注册QML类型
    qmlRegisterType<DataManager>("MedBox.Data", 1, 0, "DataManager");
    qmlRegisterType<CloudConnector>("MedBox.Cloud", 1, 0, "CloudConnector");
    
    QQmlApplicationEngine engine;
    DataManager dataManager;
    CloudConnector cloudConnector;
    
    // 注入上下文对象
    engine.rootContext()->setContextProperty("dataManager", &dataManager);
    engine.rootContext()->setContextProperty("cloudConnector", &cloudConnector);
    
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    
    return app.exec();
}
// cloudconnector.h - 华为云连接
#include <QMqttClient>
#include <QObject>

class CloudConnector : public QObject {
    Q_OBJECT
public:
    explicit CloudConnector(QObject *parent = nullptr);
    
    Q_INVOKABLE void connectToCloud(const QString &deviceId, const QString &secret);
    Q_INVOKABLE void publishData(const QString &topic, const QString &message);
    
signals:
    void cloudConnected();
    void newMedicationAlert(QString time, QString medName);
    void newOpenRecord(QString timestamp);
    
private slots:
    void onMessageReceived(const QByteArray &message, const QString &topic);
    
private:
    QMqttClient *m_client;
    QString m_deviceId;
};
// cloudconnector.cpp
#include "cloudconnector.h"
#include <QDebug>

CloudConnector::CloudConnector(QObject *parent) : QObject(parent) {
    m_client = new QMqttClient(this);
    m_client->setHostname("iotda.cn-north-4.myhuaweicloud.com");
    m_client->setPort(1883);
    
    connect(m_client, &QMqttClient::connected, this, [this]() {
        m_client->subscribe("medbox/" + m_deviceId + "/alerts");
        m_client->subscribe("medbox/" + m_deviceId + "/records");
        emit cloudConnected();
    });
    
    connect(m_client, &QMqttClient::messageReceived, 
            this, &CloudConnector::onMessageReceived);
}

void CloudConnector::connectToCloud(const QString &deviceId, const QString &secret) {
    m_deviceId = deviceId;
    m_client->setUsername(deviceId);
    m_client->setPassword(secret);
    m_client->connectToHost();
}

void CloudConnector::publishData(const QString &topic, const QString &message) {
    auto pub = m_client->publish("medbox/" + m_deviceId + "/" + topic, 
                                message.toUtf8());
    if (!pub) {
        qWarning() << "Publish failed";
    }
}

void CloudConnector::onMessageReceived(const QByteArray &msg, const QString &topic) {
    const QString data = QString::fromUtf8(msg);
    
    if (topic.contains("alerts")) {
        QStringList parts = data.split('|');
        if (parts.size() == 2) 
            emit newMedicationAlert(parts[0], parts[1]);
    }
    else if (topic.contains("records")) {
        emit newOpenRecord(data);
    }
}
// datamanager.h - 数据管理
#include <QObject>
#include <QSqlDatabase>
#include <QSqlQueryModel>

class DataManager : public QObject {
    Q_OBJECT
public:
    explicit DataManager(QObject *parent = nullptr);
    
    Q_INVOKABLE void addRecord(const QString &timestamp);
    Q_INVOKABLE QSqlQueryModel* getHistoryModel();
    
signals:
    void historyUpdated();
    
private:
    void initDatabase();
    QSqlDatabase m_db;
};
// datamanager.cpp
#include "datamanager.h"
#include <QStandardPaths>
#include <QSqlError>
#include <QDebug>

DataManager::DataManager(QObject *parent) : QObject(parent) {
    initDatabase();
}

void DataManager::initDatabase() {
    const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
                           + "/medbox.db";
    m_db = QSqlDatabase::addDatabase("QSQLITE");
    m_db.setDatabaseName(dbPath);
    
    if (!m_db.open()) {
        qCritical() << "Database error:" << m_db.lastError();
        return;
    }
    
    QSqlQuery query;
    query.exec("CREATE TABLE IF NOT EXISTS OpenRecords ("
               "id INTEGER PRIMARY KEY AUTOINCREMENT, "
               "timestamp DATETIME NOT NULL)");
}

void DataManager::addRecord(const QString &timestamp) {
    QSqlQuery query;
    query.prepare("INSERT INTO OpenRecords (timestamp) VALUES (?)");
    query.addBindValue(timestamp);
    if (!query.exec()) {
        qWarning() << "Insert error:" << query.lastError();
    } else {
        emit historyUpdated();
    }
}

QSqlQueryModel* DataManager::getHistoryModel() {
    auto *model = new QSqlQueryModel();
    model->setQuery("SELECT timestamp FROM OpenRecords ORDER BY id DESC");
    model->setHeaderData(0, Qt::Horizontal, tr("开箱时间"));
    return model;
}
// main.qml - 主界面
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import MedBox.Data 1.0
import MedBox.Cloud 1.0

ApplicationWindow {
    id: appWindow
    visible: true
    width: 360
    height: 640
    title: "智能药箱监控"
    
    // 华为云设备凭证(需用户配置)
    property string deviceId: ""
    property string deviceSecret: ""
    
    CloudConnector {
        id: cloud
        onNewMedicationAlert: {
            alertPopup.time = time
            alertPopup.medName = medName
            alertPopup.open()
        }
        onNewOpenRecord: dataManager.addRecord(timestamp)
    }
    
    DataManager {
        id: dataManager
    }
    
    StackView {
        id: stack
        anchors.fill: parent
        initialItem: loginPage
    }
    
    // 登录页面
    Component {
        id: loginPage
        Page {
            ColumnLayout {
                anchors.centerIn: parent
                spacing: 20
                
                TextField {
                    id: idField
                    placeholderText: "设备ID"
                    Layout.fillWidth: true
                }
                
                TextField {
                    id: secretField
                    placeholderText: "设备密钥"
                    echoMode: TextInput.Password
                    Layout.fillWidth: true
                }
                
                Button {
                    text: "连接设备"
                    Layout.fillWidth: true
                    onClicked: {
                        deviceId = idField.text
                        deviceSecret = secretField.text
                        cloud.connectToCloud(deviceId, deviceSecret)
                    }
                }
            }
            
            Connections {
                target: cloud
                onCloudConnected: stack.replace(mainPage)
            }
        }
    }
    
    // 主功能页面
    Component {
        id: mainPage
        Page {
            header: ToolBar {
                Label { text: "智能药箱监控"; font.bold: true; anchors.centerIn: parent }
            }
            
            ColumnLayout {
                anchors.fill: parent
                spacing: 15
                anchors.margins: 20
                
                GroupBox {
                    title: "服药提醒"
                    Layout.fillWidth: true
                    
                    Label {
                        id: nextMedTime
                        text: "加载中..."
                        font.pointSize: 16
                    }
                    
                    Label {
                        id: medName
                        text: ""
                    }
                }
                
                GroupBox {
                    title: "今日开箱记录"
                    Layout.fillWidth: true
                    Layout.fillHeight: true
                    
                    ListView {
                        anchors.fill: parent
                        model: ListModel { id: todayRecords }
                        delegate: Label { text: model.timestamp }
                    }
                }
                
                Button {
                    text: "历史记录"
                    Layout.fillWidth: true
                    onClicked: stack.push(historyPage)
                }
            }
            
            Component.onCompleted: {
                // 模拟数据更新
                nextMedTime.text = "14:30"
                medName.text = "降压药 × 2"
                todayRecords.append({"timestamp": "08:45"})
                todayRecords.append({"timestamp": "12:30"})
            }
        }
    }
    
    // 历史记录页面
    Component {
        id: historyPage
        Page {
            header: ToolBar {
                ToolButton {
                    text: "返回"
                    onClicked: stack.pop()
                }
            }
            
            TableView {
                anchors.fill: parent
                model: dataManager.getHistoryModel()
                ScrollBar.vertical: ScrollBar {}
            }
        }
    }
    
    // 服药提醒弹窗
    Popup {
        id: alertPopup
        modal: true
        property string time
        property string medName
        
        ColumnLayout {
            anchors.centerIn: parent
            spacing: 15
            
            Label {
                text: "服药时间到!"
                font.bold: true
                font.pointSize: 18
            }
            
            Label {
                text: `时间: ${alertPopup.time}`
            }
            
            Label {
                text: `药品: ${alertPopup.medName}`
            }
            
            Button {
                text: "确认"
                onClicked: alertPopup.close()
            }
        }
    }
}

文件结构

SmartMedBoxApp/
├── main.cpp
├── cloudconnector.h
├── cloudconnector.cpp
├── datamanager.h
├── datamanager.cpp
├── main.qml
├── SmartMedBoxApp.pro
└── resources.qrc

使用说明

  1. 功能模块

    • 华为云连接:通过MQTT协议实现数据双向通信
    • 本地数据库:SQLite存储开箱历史记录
    • 服药提醒:实时接收云端推送的提醒通知
    • 历史查询:表格展示所有开箱记录
  2. 配置要求

    • Qt 5.15+ 配置Android编译套件
    • 添加模块:QT += mqtt sql
    • 华为云平台需提前创建设备并获取设备ID/密钥
  3. 界面流程
    设备登录 → 主监控界面(显示提醒/开箱记录)→ 历史记录查询

  4. 数据协议

    • 服药提醒主题:medbox/{deviceId}/alerts
      • 数据格式:时间|药品名 (例:14:30|降压药 × 2)
    • 开箱记录主题:medbox/{deviceId}/records
      • 数据格式:ISO时间戳 (例:2024-06-15T08:45:00Z)

此APP实现了药箱状态监控、服药提醒推送、历史记录查询等核心功能,通过华为云平台与STM32设备进行数据同步。

模块代码设计

STM32F103C8T6 智能药箱监控系统代码设计

#include "stm32f10x.h"
#include <string.h>
#include <stdio.h>

// OLED 引脚定义
#define OLED_CS_PORT    GPIOA
#define OLED_CS_PIN     GPIO_Pin_4
#define OLED_DC_PORT    GPIOA
#define OLED_DC_PIN     GPIO_Pin_2
#define OLED_RES_PORT   GPIOA
#define OLED_RES_PIN    GPIO_Pin_3

// 蜂鸣器控制引脚
#define BUZZER_PORT     GPIOA
#define BUZZER_PIN      GPIO_Pin_1

// 干簧管检测引脚
#define REED_PORT       GPIOA
#define REED_PIN        GPIO_Pin_0

// ESP8266 串口定义
#define ESP8266_USART   USART1

// DS3231 I2C 地址
#define DS3231_ADDR     0xD0

// 系统状态结构体
typedef struct {
    uint8_t hour;
    uint8_t minute;
    uint8_t next_hour;
    uint8_t next_minute;
    uint8_t box_opened;
    uint8_t alarm_active;
} SystemState;

SystemState sys_state;

// 函数声明
void RCC_Configuration(void);
void GPIO_Configuration(void);
void SPI_Configuration(void);
void I2C_Configuration(void);
void USART_Configuration(void);
void DS3231_Init(void);
void DS3231_GetTime(uint8_t *hour, uint8_t *minute);
void OLED_Init(void);
void OLED_WriteCommand(uint8_t cmd);
void OLED_WriteData(uint8_t data);
void OLED_DisplayTime(uint8_t hour, uint8_t minute);
void Buzzer_Control(uint8_t state);
uint8_t ReedSwitch_Read(void);
void ESP8266_SendCmd(char *cmd);
void SendToHuaweiCloud(void);
void Delay_ms(uint32_t ms);

int main(void) {
    // 系统初始化
    RCC_Configuration();
    GPIO_Configuration();
    SPI_Configuration();
    I2C_Configuration();
    USART_Configuration();
    
    // 外设初始化
    DS3231_Init();
    OLED_Init();
    Buzzer_Control(0);  // 关闭蜂鸣器
    
    // 设置初始服药时间
    sys_state.next_hour = 8;
    sys_state.next_minute = 0;
    
    // 连接华为云
    ESP8266_SendCmd("AT+CWMODE=1\r\n");
    Delay_ms(1000);
    ESP8266_SendCmd("AT+CWJAP=\"YourSSID\",\"YourPassword\"\r\n");
    Delay_ms(3000);
    ESP8266_SendCmd("AT+MQTTUSERCFG=0,1,\"DeviceID\",\"DeviceSecret\",0,0,\"\"\r\n");
    ESP8266_SendCmd("AT+MQTTCONN=0,\"iotda.cn-north-4.myhuaweicloud.com\",1883,1\r\n");
    
    while(1) {
        // 获取当前时间
        DS3231_GetTime(&sys_state.hour, &sys_state.minute);
        
        // 显示当前时间
        OLED_DisplayTime(sys_state.hour, sys_state.minute);
        
        // 检查服药时间
        if(sys_state.hour == sys_state.next_hour && 
           sys_state.minute == sys_state.next_minute) {
            if(!sys_state.alarm_active) {
                Buzzer_Control(1);  // 开启蜂鸣器
                sys_state.alarm_active = 1;
                // 发送提醒到云端
                SendToHuaweiCloud();
            }
        } else {
            if(sys_state.alarm_active) {
                Buzzer_Control(0);  // 关闭蜂鸣器
                sys_state.alarm_active = 0;
            }
        }
        
        // 检测药箱开启状态
        if(ReedSwitch_Read() == 0) {  // 干簧管闭合
            if(!sys_state.box_opened) {
                sys_state.box_opened = 1;
                // 记录开启事件并上传
                SendToHuaweiCloud();
            }
        } else {
            sys_state.box_opened = 0;
        }
        
        Delay_ms(500);  // 主循环延时
    }
}

// 系统时钟配置
void RCC_Configuration(void) {
    // 开启外设时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN |
                    RCC_APB2ENR_AFIOEN | RCC_APB2ENR_USART1EN |
                    RCC_APB2ENR_SPI1EN | RCC_APB2ENR_IOPCEN;
    
    // 开启I2C时钟
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
}

// GPIO配置
void GPIO_Configuration(void) {
    // 蜂鸣器 (PA1 推挽输出)
    GPIOA->CRL &= ~(0xF << 4);
    GPIOA->CRL |= (0x01 << 4);
    
    // 干簧管 (PA0 浮空输入)
    GPIOA->CRL &= ~(0xF << 0);
    
    // OLED控制引脚 (PA2/PA3/PA4 推挽输出)
    GPIOA->CRL &= ~(0xFFF << 8);
    GPIOA->CRL |= (0x111 << 8);
    
    // I2C (PB6-SCL, PB7-SDA)
    GPIOB->CRL &= ~(0xFF << 24);
    GPIOB->CRL |= (0x88 << 24);  // 复用开漏
    
    // SPI (PA5-SCK, PA7-MOSI)
    GPIOA->CRL &= ~(0xFF << 20);
    GPIOA->CRL |= (0xB4 << 20);  // PA5复用推挽, PA7复用推挽
    
    // USART1 (PA9-TX, PA10-RX)
    GPIOA->CRH &= ~(0xFF << 4);
    GPIOA->CRH |= (0x0B << 4);  // PA9复用推挽, PA10浮空输入
}

// SPI配置
void SPI_Configuration(void) {
    SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_1 | SPI_CR1_SPE;
    SPI1->CR2 = 0;
}

// I2C配置
void I2C_Configuration(void) {
    I2C1->CR1 = 0;
    I2C1->CR2 = 0x08;        // 8MHz时钟
    I2C1->CCR = 0x28;        // 100kHz模式
    I2C1->TRISE = 0x09;      // 最大上升时间
    I2C1->CR1 = I2C_CR1_PE;  // 使能I2C
}

// USART配置
void USART_Configuration(void) {
    USART1->BRR = 0x1D4C;    // 72MHz/9600=0x1D4C
    USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}

// DS3231初始化
void DS3231_Init(void) {
    uint8_t data[2] = {0x0E, 0x00}; // 控制寄存器地址
    // 启动I2C传输
    I2C1->CR1 |= I2C_CR1_START;
    while(!(I2C1->SR1 & I2C_SR1_SB));
    I2C1->DR = DS3231_ADDR;
    while(!(I2C1->SR1 & I2C_SR1_ADDR));
    (void)I2C1->SR2; // 清除ADDR标志
    
    // 发送寄存器地址
    I2C1->DR = data[0];
    while(!(I2C1->SR1 & I2C_SR1_TXE));
    
    // 发送数据
    I2C1->DR = data[1];
    while(!(I2C1->SR1 & I2C_SR1_BTF));
    
    // 停止传输
    I2C1->CR1 |= I2C_CR1_STOP;
}

// 获取DS3231时间
void DS3231_GetTime(uint8_t *hour, uint8_t *minute) {
    uint8_t reg = 0x02; // 小时寄存器地址
    
    // 启动I2C传输
    I2C1->CR1 |= I2C_CR1_START;
    while(!(I2C1->SR1 & I2C_SR1_SB));
    I2C1->DR = DS3231_ADDR | 0x01; // 读模式
    while(!(I2C1->SR1 & I2C_SR1_ADDR));
    (void)I2C1->SR2; // 清除ADDR标志
    
    // 读取分钟
    while(!(I2C1->SR1 & I2C_SR1_RXNE));
    *minute = I2C1->DR;
    
    // 读取小时
    while(!(I2C1->SR1 & I2C_SR1_RXNE));
    *hour = I2C1->DR;
    
    // 停止传输
    I2C1->CR1 |= I2C_CR1_STOP;
}

// OLED初始化
void OLED_Init(void) {
    // 复位OLED
    GPIO_ResetBits(OLED_RES_PORT, OLED_RES_PIN);
    Delay_ms(100);
    GPIO_SetBits(OLED_RES_PORT, OLED_RES_PIN);
    Delay_ms(100);
    
    // 初始化序列
    OLED_WriteCommand(0xAE); // 关闭显示
    OLED_WriteCommand(0xD5); // 设置显示时钟分频
    OLED_WriteCommand(0x80);
    OLED_WriteCommand(0xA8); // 设置复用率
    OLED_WriteCommand(0x3F);
    OLED_WriteCommand(0xD3); // 设置显示偏移
    OLED_WriteCommand(0x00);
    OLED_WriteCommand(0x40); // 设置起始行
    OLED_WriteCommand(0x8D); // 电荷泵设置
    OLED_WriteCommand(0x14);
    OLED_WriteCommand(0x20); // 内存模式
    OLED_WriteCommand(0x00);
    OLED_WriteCommand(0xA1); // 段重定向
    OLED_WriteCommand(0xC8); // 行重定向
    OLED_WriteCommand(0xDA); // COM引脚配置
    OLED_WriteCommand(0x12);
    OLED_WriteCommand(0x81); // 对比度设置
    OLED_WriteCommand(0xCF);
    OLED_WriteCommand(0xD9); // 预充电周期
    OLED_WriteCommand(0xF1);
    OLED_WriteCommand(0xDB); // VCOMH设置
    OLED_WriteCommand(0x40);
    OLED_WriteCommand(0xA4); // 整体显示开启
    OLED_WriteCommand(0xA6); // 正常显示
    OLED_WriteCommand(0xAF); // 开启显示
}

// OLED写命令
void OLED_WriteCommand(uint8_t cmd) {
    GPIO_ResetBits(OLED_DC_PORT, OLED_DC_PIN); // 命令模式
    GPIO_ResetBits(OLED_CS_PORT, OLED_CS_PIN); // 片选
    
    while(!(SPI1->SR & SPI_SR_TXE));
    SPI1->DR = cmd;
    while(!(SPI1->SR & SPI_SR_TXE));
    while(SPI1->SR & SPI_SR_BSY);
    
    GPIO_SetBits(OLED_CS_PORT, OLED_CS_PIN); // 取消片选
}

// OLED写数据
void OLED_WriteData(uint8_t data) {
    GPIO_SetBits(OLED_DC_PORT, OLED_DC_PIN); // 数据模式
    GPIO_ResetBits(OLED_CS_PORT, OLED_CS_PIN); // 片选
    
    while(!(SPI1->SR & SPI_SR_TXE));
    SPI1->DR = data;
    while(!(SPI1->SR & SPI_SR_TXE));
    while(SPI1->SR & SPI_SR_BSY);
    
    GPIO_SetBits(OLED_CS_PORT, OLED_CS_PIN); // 取消片选
}

// OLED显示时间
void OLED_DisplayTime(uint8_t hour, uint8_t minute) {
    char timeStr[16];
    sprintf(timeStr, "Time:%02d:%02d", hour, minute);
    
    // 设置显示位置
    OLED_WriteCommand(0x21); // 列地址
    OLED_WriteCommand(0);
    OLED_WriteCommand(127);
    OLED_WriteCommand(0x22); // 页地址
    OLED_WriteCommand(0);
    OLED_WriteCommand(3);
    
    // 显示字符串
    for(int i=0; i<strlen(timeStr); i++) {
        OLED_WriteData(timeStr[i]);
    }
}

// 蜂鸣器控制
void Buzzer_Control(uint8_t state) {
    if(state) {
        GPIO_SetBits(BUZZER_PORT, BUZZER_PIN);
    } else {
        GPIO_ResetBits(BUZZER_PORT, BUZZER_PIN);
    }
}

// 干簧管状态读取
uint8_t ReedSwitch_Read(void) {
    return GPIO_ReadInputDataBit(REED_PORT, REED_PIN);
}

// ESP8266发送命令
void ESP8266_SendCmd(char *cmd) {
    while(*cmd) {
        while(!(USART1->SR & USART_SR_TXE));
        USART1->DR = *cmd++;
    }
}

// 发送数据到华为云
void SendToHuaweiCloud(void) {
    char payload[128];
    if(sys_state.alarm_active) {
        sprintf(payload, "AT+MQTTPUB=0,\"alarm\",\"Medication Time!\",0,0\r\n");
    } else if(sys_state.box_opened) {
        sprintf(payload, "AT+MQTTPUB=0,\"box_status\",\"Box Opened\",0,0\r\n");
    }
    ESP8266_SendCmd(payload);
    Delay_ms(500);
}

// 简单延时函数
void Delay_ms(uint32_t ms) {
    for(uint32_t i=0; i<ms*8000; i++);
}

关键模块说明

  1. DS3231 实时时钟模块

    • 通过I2C接口通信(100kHz速率)
    • 初始化时配置控制寄存器
    • 提供精确时间读取功能(小时/分钟)
  2. 干簧管药箱检测

    • PA0配置为浮空输入模式
    • 磁铁靠近时返回0(闭合状态)
    • 检测到状态变化时上传开启事件
  3. 蜂鸣器报警模块

    • PA1配置为推挽输出
    • 服药时间到达时激活蜂鸣器
    • 用户操作或超时后关闭
  4. SPI OLED显示

    • 使用SPI1接口(PA5-SCK, PA7-MOSI)
    • 支持标准128x64分辨率
    • 实时显示当前时间和下次服药时间
  5. ESP8266联网模块

    • 通过USART1通信(9600bps)
    • 支持MQTT协议连接华为云
    • 实现数据上传和报警推送
  6. 华为云物联网平台对接

    • 使用MQTT标准协议
    • 上传药箱开启记录
    • 推送服药提醒到APP端

项目核心代码

以下是基于STM32F103C8T6的智能药箱监控系统main.c完整代码,采用寄存器方式开发:

#include "stm32f10x.h"
#include "ds3231.h"
#include "oled_spi.h"
#include "esp8266_mqtt.h"

// 硬件定义
#define BUZZER_PIN    GPIO_Pin_12   // PB12控制蜂鸣器
#define REED_PIN      GPIO_Pin_13   // PC13检测干簧管
#define BUZZER_PORT   GPIOB
#define REED_PORT     GPIOC

// 系统状态变量
volatile uint8_t alarm_flag = 0;        // 服药提醒标志
volatile uint32_t box_open_count = 0;   // 药箱开启计数
RTC_TimeTypeDef current_time, next_med_time = {8, 0, 0}; // 默认下次服药时间08:00

// 初始化函数声明
void RCC_Configuration(void);
void GPIO_Configuration(void);
void NVIC_Configuration(void);
void SysTick_Handler(void);
void Check_Medication_Time(void);
void Process_Box_Open(void);
void Update_Display(void);

int main(void) {
    // 系统初始化
    RCC_Configuration();
    GPIO_Configuration();
    NVIC_Configuration();
    DS3231_Init();
    OLED_Init();
    ESP8266_Init();
    
    // 从DS3231读取初始时间
    DS3231_GetTime(&current_time);
    
    // 连接华为云
    ESP8266_Connect_Cloud();
    
    // 主循环
    while (1) {
        // 1. 更新时间
        DS3231_GetTime(&current_time);
        
        // 2. 检查服药时间
        Check_Medication_Time();
        
        // 3. 处理药箱开启事件
        if (GPIO_ReadInputDataBit(REED_PORT, REED_PIN) == 0) {
            Process_Box_Open();
            while (GPIO_ReadInputDataBit(REED_PORT, REED_PIN) == 0); // 防抖
        }
        
        // 4. 更新OLED显示
        Update_Display();
        
        // 5. 处理云平台数据
        ESP8266_Process();
    }
}

// 系统时钟配置
void RCC_Configuration(void) {
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | 
                    RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN;
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_I2C1EN;
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
}

// GPIO配置
void GPIO_Configuration(void) {
    // 蜂鸣器输出 (PB12)
    GPIOB->CRH &= ~(0x0F << 16);
    GPIOB->CRH |= (0x03 << 16);  // 推挽输出50MHz
    
    // 干簧管输入 (PC13)
    GPIOC->CRH &= ~(0x0F << 20);
    GPIOC->CRH |= (0x08 << 20);  // 上拉输入
    
    // OLED SPI引脚在oled_spi.c中配置
    // ESP8266 USART2引脚在esp8266_mqtt.c中配置
}

// 中断配置
void NVIC_Configuration(void) {
    SysTick_Config(SystemCoreClock / 1000); // 1ms中断
    NVIC_EnableIRQ(SysTick_IRQn);
}

// SysTick中断处理
void SysTick_Handler(void) {
    static uint32_t alarm_timer = 0;
    
    if (alarm_flag) {
        if (++alarm_timer >= 500) {  // 0.5s间隔
            alarm_timer = 0;
            GPIOB->ODR ^= BUZZER_PIN; // 蜂鸣器鸣响
        }
    }
}

// 检查服药时间
void Check_Medication_Time(void) {
    if (current_time.hours == next_med_time.hours && 
        current_time.minutes == next_med_time.minutes) {
        alarm_flag = 1;  // 触发报警
    }
}

// 处理药箱开启
void Process_Box_Open(void) {
    box_open_count++;
    
    // 如果是服药时间开启药箱
    if (alarm_flag) {
        alarm_flag = 0;
        GPIOB->ODR &= ~BUZZER_PIN; // 关闭蜂鸣器
        
        // 更新下次服药时间 (示例: +4小时)
        next_med_time.hours = (current_time.hours + 4) % 24;
        next_med_time.minutes = current_time.minutes;
    }
    
    // 上传开启记录到云平台
    char data[50];
    sprintf(data, "{\"open_count\":%lu}", box_open_count);
    ESP8266_SendData(data);
}

// 更新OLED显示
void Update_Display(void) {
    OLED_Clear();
    
    // 显示当前时间
    char time_str[20];
    sprintf(time_str, "Time: %02d:%02d:%02d", 
            current_time.hours, 
            current_time.minutes, 
            current_time.seconds);
    OLED_ShowString(0, 0, (uint8_t*)time_str);
    
    // 显示下次服药时间
    char med_str[20];
    sprintf(med_str, "Next: %02d:%02d", 
            next_med_time.hours, 
            next_med_time.minutes);
    OLED_ShowString(0, 2, (uint8_t*)med_str);
    
    // 显示开启次数
    char count_str[20];
    sprintf(count_str, "Opened: %lu", box_open_count);
    OLED_ShowString(0, 4, (uint8_t*)count_str);
}

代码说明:

  1. 硬件接口

    • 蜂鸣器:PB12(高电平触发)
    • 干簧管:PC13(低电平表示药箱开启)
    • DS3231:I2C1接口
    • OLED:SPI1接口
    • ESP8266:USART2接口
  2. 核心功能

    • 实时时钟同步:每秒从DS3231获取当前时间
    • 定时提醒:到达设定时间触发蜂鸣器(0.5s间隔鸣响)
    • 药箱检测:干簧管低电平时记录开启次数
    • 服药确认:报警状态下开箱自动关闭蜂鸣器并更新下次服药时间
    • 数据显示:OLED实时显示时间/下次服药/开启次数
    • 云上传:通过ESP8266以JSON格式上传开启记录
  3. 数据处理

    • 使用sprintf构建JSON数据包:{"open_count":123}
    • OLED显示格式:三行分别显示当前时间/下次服药/开启次数
  4. 模块依赖

    • ds3231.h:DS3231驱动(提供时间读取)
    • oled_spi.h:OLED显示驱动
    • esp8266_mqtt.h:ESP8266通信及华为云对接

总结

本系统设计基于STM32F103C8T6主控芯片,构建了一套完整的智能药箱监控解决方案,核心功能包括定时服药提醒、时间显示、药箱开启记录统计、远程数据查看以及云端数据上传。该系统通过硬件模块协同工作,实现高效可靠的健康管理,有效提升用户服药依从性,减少漏服风险。

硬件模块采用模块化设计,实时时钟DS3231提供精确时间基准,确保定时提醒的准确性;磁感应干簧管检测药箱开启状态,结合蜂鸣报警模块实现及时提醒;SPI OLED显示屏直观展示当前时间和下次服药信息。联网方面,ESP8266 WiFi模块通过MQTT协议实现数据传输,保障所有记录和状态实时上传至华为云物联网平台,为远程监控提供基础支持。

远程交互通过Qt开发的Android APP实现,用户可随时查看服药提醒、药箱开启记录,并进行状态同步。供电模块集成DC 5V电源、锂电池和TP4056充电管理,确保系统在多种场景下稳定运行。整体设计融合本地与云端功能,形成闭环监控体系,兼顾实用性与可扩展性。

该系统在医疗健康领域具有广泛应用前景,通过智能化手段简化服药流程,增强数据追溯能力。其低功耗、高可靠性的特点,为用户提供便捷体验,同时为健康数据分析奠定坚实基础。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。