欢迎光临
我们一直在努力

场景实战:基于STM32的智能药盒定时提醒与蓝牙通信解决方案解析

文章目录

    • 一、方案整体设计与原理说明
      • 1.1 核心原理
      • 1.2 整体架构流程图
    • 二、硬件选型与接线
      • 2.1 核心硬件清单
      • 2.2 详细接线说明
        • (1)STM32与HC-05蓝牙模块接线
        • (2)STM32与提醒模块接线
        • (3)STM32与本地按键接线
        • (4)RTC备用电源接线(可选)
    • 三、软件开发环境搭建
      • 3.1 环境准备
      • 3.2 STM32CubeMX初始化配置步骤
        • 步骤1:新建工程
        • 步骤2:配置RCC时钟
        • 步骤3:配置RTC外设
        • 步骤4:配置USART1(蓝牙通信)
        • 步骤5:配置GPIO引脚(提醒+按键)
        • 步骤6:生成工程代码
    • 四、核心代码编写
      • 4.1 头文件与全局变量定义(main.h)
      • 4.2 外设初始化代码(main.c)
      • 4.3 RTC时间操作函数(main.c续)
      • 4.4 提醒控制函数(main.c续)
      • 4.5 蓝牙通信函数(main.c续)
      • 4.6 按键处理函数(main.c续)
      • 4.7 RTC中断与主函数(main.c续)
    • 五、代码烧录与调试
      • 5.1 代码编译与烧录
      • 5.2 硬件调试步骤
        • 步骤1:基础功能调试
        • 步骤2:时间功能调试
        • 步骤3:定时提醒调试
        • 步骤4:异常场景调试
      • 5.3 常见问题与解决
    • 六、功能扩展建议
    • 总结

一、方案整体设计与原理说明

需要实现的是基于STM32的智能药盒系统,核心包含精准定时服药提醒和蓝牙无线通信两大功能。本方案以STM32F103C8T6最小系统板为核心控制器,利用STM32内置RTC(实时时钟)模块实现毫秒级精准计时,预设多组服药时间后,当系统时间匹配预设时间时触发蜂鸣器+LED声光提醒;同时通过HC-05蓝牙模块实现STM32与手机/电脑的双向通信,支持蓝牙设置服药时间、查看当前时间、确认提醒、查询药盒状态等操作。整个方案采用模块化编程思路,步骤拆解至零基础小白可复刻,所有代码可直接复制落地。

1.1 核心原理

  • RTC实时时钟原理:STM32F103内置RTC外设,基于32.768kHz低速外部晶振(LSE)实现精准计时,掉电后可通过纽扣电池供电保持时间不丢失,能独立读取/设置年、月、日、时、分、秒等时间参数。
  • 定时提醒原理:系统实时读取RTC当前时间,与预设的1-5组服药时间逐一对比,当时间匹配(精确到分钟)时,触发GPIO口输出控制蜂鸣器发声、LED闪烁;用户可通过按键或蓝牙指令“确认”,停止提醒。
  • 蓝牙通信原理:HC-05蓝牙模块基于串口(UART)通信,与STM32的USART引脚对接,STM32通过串口收发蓝牙数据,解析手机/电脑发送的指令(如“SET 08:00”设置服药时间、“CONFIRM”确认提醒),并将当前时间、提醒状态等信息回传给终端。
  • 交互原理:通过物理按键实现本地时间调整、提醒确认;通过蓝牙实现远程参数配置和状态查询,双重交互方式提升易用性。

1.2 整体架构流程图

以下是智能药盒系统完整工作流程的Mermaid流程图,采用深色背景、白色字体,样式美观且逻辑层级清晰:

#mermaid-svg-jOqZgvDHpY8UzXyR{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jOqZgvDHpY8UzXyR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jOqZgvDHpY8UzXyR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jOqZgvDHpY8UzXyR .error-icon{fill:#552222;}#mermaid-svg-jOqZgvDHpY8UzXyR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jOqZgvDHpY8UzXyR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jOqZgvDHpY8UzXyR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jOqZgvDHpY8UzXyR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jOqZgvDHpY8UzXyR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jOqZgvDHpY8UzXyR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jOqZgvDHpY8UzXyR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jOqZgvDHpY8UzXyR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jOqZgvDHpY8UzXyR .marker.cross{stroke:#333333;}#mermaid-svg-jOqZgvDHpY8UzXyR svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jOqZgvDHpY8UzXyR p{margin:0;}#mermaid-svg-jOqZgvDHpY8UzXyR .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-jOqZgvDHpY8UzXyR .cluster-label text{fill:#333;}#mermaid-svg-jOqZgvDHpY8UzXyR .cluster-label span{color:#333;}#mermaid-svg-jOqZgvDHpY8UzXyR .cluster-label span p{background-color:transparent;}#mermaid-svg-jOqZgvDHpY8UzXyR .label text,#mermaid-svg-jOqZgvDHpY8UzXyR span{fill:#333;color:#333;}#mermaid-svg-jOqZgvDHpY8UzXyR .node rect,#mermaid-svg-jOqZgvDHpY8UzXyR .node circle,#mermaid-svg-jOqZgvDHpY8UzXyR .node ellipse,#mermaid-svg-jOqZgvDHpY8UzXyR .node polygon,#mermaid-svg-jOqZgvDHpY8UzXyR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jOqZgvDHpY8UzXyR .rough-node .label text,#mermaid-svg-jOqZgvDHpY8UzXyR .node .label text,#mermaid-svg-jOqZgvDHpY8UzXyR .image-shape .label,#mermaid-svg-jOqZgvDHpY8UzXyR .icon-shape .label{text-anchor:middle;}#mermaid-svg-jOqZgvDHpY8UzXyR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jOqZgvDHpY8UzXyR .rough-node .label,#mermaid-svg-jOqZgvDHpY8UzXyR .node .label,#mermaid-svg-jOqZgvDHpY8UzXyR .image-shape .label,#mermaid-svg-jOqZgvDHpY8UzXyR .icon-shape .label{text-align:center;}#mermaid-svg-jOqZgvDHpY8UzXyR .node.clickable{cursor:pointer;}#mermaid-svg-jOqZgvDHpY8UzXyR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jOqZgvDHpY8UzXyR .arrowheadPath{fill:#333333;}#mermaid-svg-jOqZgvDHpY8UzXyR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jOqZgvDHpY8UzXyR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jOqZgvDHpY8UzXyR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jOqZgvDHpY8UzXyR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jOqZgvDHpY8UzXyR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jOqZgvDHpY8UzXyR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jOqZgvDHpY8UzXyR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jOqZgvDHpY8UzXyR .cluster text{fill:#333;}#mermaid-svg-jOqZgvDHpY8UzXyR .cluster span{color:#333;}#mermaid-svg-jOqZgvDHpY8UzXyR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jOqZgvDHpY8UzXyR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jOqZgvDHpY8UzXyR rect.text{fill:none;stroke-width:0;}#mermaid-svg-jOqZgvDHpY8UzXyR .icon-shape,#mermaid-svg-jOqZgvDHpY8UzXyR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jOqZgvDHpY8UzXyR .icon-shape p,#mermaid-svg-jOqZgvDHpY8UzXyR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jOqZgvDHpY8UzXyR .icon-shape rect,#mermaid-svg-jOqZgvDHpY8UzXyR .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jOqZgvDHpY8UzXyR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jOqZgvDHpY8UzXyR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jOqZgvDHpY8UzXyR :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-jOqZgvDHpY8UzXyR .darkStyle>*{fill:#2c3e50!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .darkStyle span{fill:#2c3e50!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .darkStyle tspan{fill:#ffffff!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .startEnd>*{fill:#e74c3c!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .startEnd span{fill:#e74c3c!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .startEnd tspan{fill:#ffffff!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .decision>*{fill:#3498db!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .decision span{fill:#3498db!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .decision tspan{fill:#ffffff!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .process>*{fill:#27ae60!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .process span{fill:#27ae60!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .process tspan{fill:#ffffff!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .bluetooth>*{fill:#9b59b6!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .bluetooth span{fill:#9b59b6!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .bluetooth tspan{fill:#ffffff!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .alarm>*{fill:#f39c12!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .alarm span{fill:#f39c12!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-jOqZgvDHpY8UzXyR .alarm tspan{fill:#ffffff!important;}

设置服药时间

确认提醒

查询状态

调整时间

确认提醒

系统上电初始化

RCC/RTC/USART/GPIO配置

读取RTC当前时间

读取预设服药时间列表

当前时间匹配服药时间?

触发声光提醒(蜂鸣器+LED闪烁)

检测蓝牙指令?

解析蓝牙指令(设置时间/确认提醒等)

检测本地按键?

处理按键指令(调整时间/确认提醒)

指令类型?

更新预设时间列表

停止声光提醒

蓝牙回传当前时间/提醒状态

按键类型?

更新RTC系统时间

收到确认指令?

二、硬件选型与接线

2.1 核心硬件清单

硬件名称型号/规格数量作用
STM32控制器 STM32F103C8T6最小系统板 1 核心控制与数据处理
蓝牙模块 HC-05(串口蓝牙,带底板) 1 无线通信(与手机/电脑)
有源蜂鸣器模块 低电平触发型 1 提醒发声
LED指示灯 绿色/红色(含220Ω限流电阻) 各1 状态指示(绿=正常,红=提醒)
轻触按键 6*6mm贴片/直插 3 本地操作(调时/确认)
32.768kHz晶振 无源晶振 1 RTC精准计时(若板载无则需外接)
纽扣电池+电池座 CR2032 1套 RTC掉电续航
电源模块 5V USB供电模块 1 系统供电
杜邦线 公对公/公对母 若干 硬件接线
ST-Link下载器 V2版 1 代码烧录与调试

2.2 详细接线说明

(1)STM32与HC-05蓝牙模块接线

HC-05采用UART通信,默认波特率9600bps,接线如下:

STM32引脚HC-05引脚说明
PA9 TXD STM32_USART1_RX(接收蓝牙数据)
PA10 RXD STM32_USART1_TX(发送蓝牙数据)
5V VCC 蓝牙模块供电(底板版支持5V)
GND GND 共地(必须共地,否则通信乱码)
(2)STM32与提醒模块接线
STM32引脚外设引脚说明
PB0 有源蜂鸣器IN端 低电平触发蜂鸣器
PB1 绿色LED正极 正常状态常亮(串220Ω电阻)
PB2 红色LED正极 提醒状态闪烁(串220Ω电阻)
GND 蜂鸣器/LED负极 共地
(3)STM32与本地按键接线
STM32引脚按键引脚说明
PA0 按键1一端 确认提醒(上拉输入,按下接地)
PA1 按键2一端 时间+1(上拉输入)
PA2 按键3一端 时间切换(时分/组号,上拉输入)
GND 按键另一端 所有按键共地
(4)RTC备用电源接线(可选)

若STM32最小系统板无RTC备用电池座,需外接CR2032电池:

STM32引脚电池座引脚说明
VBAT 电池正极 RTC掉电供电
GND 电池负极 共地

三、软件开发环境搭建

3.1 环境准备

  • STM32CubeMX:下载V6.0及以上版本(官网:https://www.st.com/en/development-tools/stm32cubemx.html),用于图形化配置STM32外设,自动生成初始化代码。
  • Keil MDK-ARM:下载V5.36及以上版本,安装STM32F103C8T6对应的Device Pack(在Keil中通过“Pack Installer”安装),用于代码编写、编译和烧录。
  • ST-Link驱动:安装ST-Link V2驱动,确保电脑能识别下载器;若用USB转串口调试蓝牙,需安装CH340/PL2303驱动。
  • 蓝牙调试工具:手机端安装“蓝牙串口助手”APP,电脑端安装“串口调试助手”(用于测试蓝牙通信)。
  • 辅助工具:万用表(检查接线和供电)、杜邦线压线钳(可选)。
  • 3.2 STM32CubeMX初始化配置步骤

    步骤1:新建工程
    • 打开STM32CubeMX,点击“New Project”,在搜索框输入“STM32F103C8T6”,选择对应型号(STM32F103C8Tx)后点击“Start Project”。
    • 弹出“Project Manager”提示框,点击“OK”跳过(默认配置即可)。
    步骤2:配置RCC时钟
    • 左侧菜单栏选择“RCC”:
      • High Speed Clock (HSE):选择“Crystal/Ceramic Resonator”(外部8MHz晶振),启用HSE;
      • Low Speed Clock (LSE):选择“Crystal/Ceramic Resonator”(外部32.768kHz晶振),启用LSE(RTC必须);
    • 点击顶部“Clock Configuration”,配置系统时钟为72MHz:
      • HSE = 8MHz → PLLMUL = 9 → SYSCLK = 72MHz;
      • AHB Prescaler = 1(HCLK=72MHz);
      • APB1 Prescaler = 2(PCLK1=36MHz);
      • APB2 Prescaler = 1(PCLK2=72MHz);
      • RTC时钟源选择“LSE”(32.768kHz)。
    步骤3:配置RTC外设
    • 左侧菜单栏选择“RTC”:
      • 模式选择“Calendar”(日历模式);
      • 启用“RTC Hour Format”为“24 Hours”(24小时制);
      • 启用“NVIC Settings”中的“RTC global interrupt”(RTC中断),优先级设为2;
    • 无需修改默认参数(分频系数:同步分频=0x7F,异步分频=0xFF,对应32.768kHz/32768=1Hz,秒级计时)。
    步骤4:配置USART1(蓝牙通信)
    • 左侧菜单栏选择“USART1”:
      • 模式选择“Asynchronous”(异步模式);
      • 配置参数:
        • Baud Rate(波特率):9600bps(匹配HC-05默认波特率);
        • Word Length:8 Bits;
        • Stop Bits:1 Bit;
        • Parity:None;
        • Hardware Flow Control:Disabled;
    • 启用“NVIC Settings”中的“USART1 global interrupt”(串口中断),优先级设为1;
    • 引脚映射:PA9(USART1_TX)、PA10(USART1_RX)(默认映射,无需修改)。
    步骤5:配置GPIO引脚(提醒+按键)
    • 左侧菜单栏选择“GPIO”:
      • 提醒模块引脚:
        • PB0:设置为“Output Push Pull”(推挽输出),命名为“BUZZER”,初始电平“High”(蜂鸣器默认不响);
        • PB1:设置为“Output Push Pull”,命名为“LED_GREEN”,初始电平“Low”(默认熄灭,正常后常亮);
        • PB2:设置为“Output Push Pull”,命名为“LED_RED”,初始电平“Low”(默认熄灭,提醒时闪烁);
      • 按键模块引脚:
        • PA0:设置为“Input Pull-Up”(上拉输入),命名为“KEY_CONFIRM”(确认提醒);
        • PA1:设置为“Input Pull-Up”,命名为“KEY_ADD”(时间+1);
        • PA2:设置为“Input Pull-Up”,命名为“KEY_SWITCH”(时间切换);
      • 所有GPIO引脚速度设为“High”。
    步骤6:生成工程代码
    • 点击顶部“Project Manager”:
      • Project Name:设置为“Smart_Pill_Box”;
      • Project Path:选择非中文路径(如D:\\STM32_Projects\\Smart_Pill_Box);
      • Toolchain/IDE:选择“MDK-ARM”,版本“V5”;
    • 点击“Code Generator”:
      • 勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”(按外设分文件生成代码);
      • 勾选“Copy all used libraries into the project folder”(复制库文件到工程);
    • 点击“Generate Code”,生成完成后点击“Open Project”直接打开Keil工程。

    四、核心代码编写

    4.1 头文件与全局变量定义(main.h)

    #ifndef __MAIN_H
    #define __MAIN_H

    #include "stm32f1xx_hal.h"

    /* 引脚宏定义 */
    // 提醒模块
    #define BUZZER_PIN GPIO_PIN_0
    #define BUZZER_GPIO_PORT GPIOB
    #define LED_GREEN_PIN GPIO_PIN_1
    #define LED_GREEN_GPIO_PORT GPIOB
    #define LED_RED_PIN GPIO_PIN_2
    #define LED_RED_GPIO_PORT GPIOB

    // 按键模块
    #define KEY_CONFIRM_PIN GPIO_PIN_0
    #define KEY_CONFIRM_GPIO_PORT GPIOA
    #define KEY_ADD_PIN GPIO_PIN_1
    #define KEY_ADD_GPIO_PORT GPIOA
    #define KEY_SWITCH_PIN GPIO_PIN_2
    #define KEY_SWITCH_GPIO_PORT GPIOA

    /* 功能宏定义 */
    #define MAX_PILL_TIME 5 // 最大预设服药时间组数
    #define ALARM_DURATION 300 // 提醒持续时间(秒),0为无限提醒
    #define BLUETOOTH_BUF_LEN 32 // 蓝牙接收缓冲区长度

    /* 时间结构体定义(匹配RTC日历格式) */
    typedef struct
    {
    uint8_t hour; // 小时(0-23)
    uint8_t min; // 分钟(0-59)
    uint8_t enable; // 启用标志:1=启用,0=禁用
    } PillTime_TypeDef;

    /* 全局变量 */
    extern RTC_TimeTypeDef RTC_CurrentTime; // RTC当前时间
    extern RTC_DateTypeDef RTC_CurrentDate; // RTC当前日期
    extern PillTime_TypeDef PillTimeList[MAX_PILL_TIME]; // 服药时间列表
    extern uint8_t Alarm_Flag; // 提醒标志:0=无提醒,1=提醒中
    extern uint32_t Alarm_Count; // 提醒计时(秒)
    extern uint8_t Bluetooth_Rx_Buf[BLUETOOTH_BUF_LEN]; // 蓝牙接收缓冲区
    extern uint8_t Bluetooth_Rx_Len; // 蓝牙接收数据长度
    extern uint8_t Key_Operate_Flag; // 按键操作标志

    /* 函数声明 */
    void SystemClock_Config(void);
    static void MX_GPIO_Init(void);
    static void MX_USART1_UART_Init(void);
    static void MX_RTC_Init(void);

    // RTC相关函数
    void RTC_Set_Time(uint8_t hour, uint8_t min, uint8_t sec);
    void RTC_Set_Date(uint8_t year, uint8_t month, uint8_t date, uint8_t weekday);
    void RTC_Read_Time(void);
    uint8_t RTC_Check_PillTime(void);

    // 提醒控制函数
    void Alarm_Start(void);
    void Alarm_Stop(void);
    void Alarm_Process(void);

    // 蓝牙通信函数
    void Bluetooth_Send_Data(uint8_t *data, uint8_t len);
    void Bluetooth_Parse_Command(void);

    // 按键处理函数
    void Key_Scan(void);
    void Key_Process(void);

    // 辅助函数
    uint8_t Str_to_Num(uint8_t *str, uint8_t len);
    void Num_to_Str(uint8_t num, uint8_t *str);

    #endif /* __MAIN_H */

    4.2 外设初始化代码(main.c)

    #include "main.h"

    /* 全局变量定义 */
    RTC_TimeTypeDef RTC_CurrentTime = {0};
    RTC_DateTypeDef RTC_CurrentDate = {0};
    PillTime_TypeDef PillTimeList[MAX_PILL_TIME] = {0}; // 初始化为0
    uint8_t Alarm_Flag = 0;
    uint32_t Alarm_Count = 0;
    uint8_t Bluetooth_Rx_Buf[BLUETOOTH_BUF_LEN] = {0};
    uint8_t Bluetooth_Rx_Len = 0;
    uint8_t Key_Operate_Flag = 0;

    /* 外设句柄 */
    UART_HandleTypeDef huart1;
    RTC_HandleTypeDef hrtc;

    /* 系统时钟配置函数 */
    void SystemClock_Config(void)
    {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

    /* 配置HSE和LSE */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.LSEState = RCC_LSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    Error_Handler();
    }

    /* 配置系统时钟总线 */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
    |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
    Error_Handler();
    }

    /* 配置RTC时钟源为LSE */
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /* RTC初始化函数 */
    static void MX_RTC_Init(void)
    {
    hrtc.Instance = RTC;
    hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND; // 自动配置分频,1秒更新一次
    hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
    if (HAL_RTC_Init(&hrtc) != HAL_OK)
    {
    Error_Handler();
    }

    /* 若首次初始化,设置默认时间:2025年1月1日 00:00:00 周三 */
    if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x5050)
    {
    RTC_DateTypeDef DateToSet = {0};
    RTC_TimeTypeDef TimeToSet = {0};

    // 设置日期
    DateToSet.WeekDay = RTC_WEEKDAY_WEDNESDAY;
    DateToSet.Month = RTC_MONTH_JANUARY;
    DateToSet.Date = 1;
    DateToSet.Year = 25; // 2025年
    if (HAL_RTC_SetDate(&hrtc, &DateToSet, RTC_FORMAT_BIN) != HAL_OK)
    {
    Error_Handler();
    }

    // 设置时间
    TimeToSet.Hours = 0;
    TimeToSet.Minutes = 0;
    TimeToSet.Seconds = 0;
    TimeToSet.TimeFormat = RTC_HOURFORMAT24;
    TimeToSet.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    TimeToSet.StoreOperation = RTC_STOREOPERATION_RESET;
    if (HAL_RTC_SetTime(&hrtc, &TimeToSet, RTC_FORMAT_BIN) != HAL_OK)
    {
    Error_Handler();
    }

    // 写入备份寄存器,标记已初始化
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x5050);
    }

    // 启用RTC秒中断(1秒触发一次)
    HAL_RTC_EnableIT(&hrtc, RTC_IT_SEC);
    }

    /* USART1初始化函数(蓝牙通信) */
    static void MX_USART1_UART_Init(void)
    {
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 9600;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
    Error_Handler();
    }

    // 启用串口接收中断(空闲中断+字节中断)
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
    HAL_UART_Receive_IT(&huart1, Bluetooth_Rx_Buf, 1);
    }

    /* GPIO初始化函数 */
    static void MX_GPIO_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* 使能GPIO时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    /* 配置提醒模块引脚(输出) */
    GPIO_InitStruct.Pin = BUZZER_PIN|LED_GREEN_PIN|LED_RED_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* 配置按键模块引脚(上拉输入) */
    GPIO_InitStruct.Pin = KEY_CONFIRM_PIN|KEY_ADD_PIN|KEY_SWITCH_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* 初始状态:蜂鸣器关闭,LED熄灭 */
    HAL_GPIO_WritePin(BUZZER_GPIO_PORT, BUZZER_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED_GREEN_GPIO_PORT, LED_GREEN_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_RED_GPIO_PORT, LED_RED_PIN, GPIO_PIN_RESET);
    }

    /* 错误处理函数(死循环+红灯闪烁) */
    void Error_Handler(void)
    {
    __disable_irq();
    while (1)
    {
    HAL_GPIO_TogglePin(LED_RED_GPIO_PORT, LED_RED_PIN);
    HAL_Delay(200);
    }
    }

    #ifdef USE_FULL_ASSERT
    void assert_failed(uint8_t *file, uint32_t line)
    {
    // 断言失败处理(可选添加串口打印)
    }
    #endif /* USE_FULL_ASSERT */

    4.3 RTC时间操作函数(main.c续)

    /* 设置RTC时间(时:分:秒) */
    void RTC_Set_Time(uint8_t hour, uint8_t min, uint8_t sec)
    {
    RTC_TimeTypeDef TimeToSet = {0};

    TimeToSet.Hours = hour;
    TimeToSet.Minutes = min;
    TimeToSet.Seconds = sec;
    TimeToSet.TimeFormat = RTC_HOURFORMAT24;
    TimeToSet.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    TimeToSet.StoreOperation = RTC_STOREOPERATION_RESET;

    if (HAL_RTC_SetTime(&hrtc, &TimeToSet, RTC_FORMAT_BIN) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /* 设置RTC日期(年:月:日:星期) */
    void RTC_Set_Date(uint8_t year, uint8_t month, uint8_t date, uint8_t weekday)
    {
    RTC_DateTypeDef DateToSet = {0};

    DateToSet.Year = year;
    DateToSet.Month = month;
    DateToSet.Date = date;
    DateToSet.WeekDay = weekday;

    if (HAL_RTC_SetDate(&hrtc, &DateToSet, RTC_FORMAT_BIN) != HAL_OK)
    {
    Error_Handler();
    }
    }

    /* 读取RTC当前时间(更新全局变量) */
    void RTC_Read_Time(void)
    {
    HAL_RTC_GetTime(&hrtc, &RTC_CurrentTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &RTC_CurrentDate, RTC_FORMAT_BIN);
    }

    /* 检查当前时间是否匹配预设服药时间 */
    uint8_t RTC_Check_PillTime(void)
    {
    for (uint8_t i=0; i<MAX_PILL_TIME; i++)
    {
    // 仅检查启用的时间组,精确到分钟
    if (PillTimeList[i].enable == 1)
    {
    if (RTC_CurrentTime.Hours == PillTimeList[i].hour &&
    RTC_CurrentTime.Minutes == PillTimeList[i].min &&
    RTC_CurrentTime.Seconds == 0) // 整分钟触发
    {
    return 1; // 匹配成功
    }
    }
    }
    return 0; // 无匹配
    }

    4.4 提醒控制函数(main.c续)

    /* 启动提醒(蜂鸣器+红灯闪烁) */
    void Alarm_Start(void)
    {
    Alarm_Flag = 1;
    Alarm_Count = 0;
    HAL_GPIO_WritePin(LED_GREEN_GPIO_PORT, LED_GREEN_PIN, GPIO_PIN_RESET); // 绿灯灭
    HAL_GPIO_WritePin(BUZZER_GPIO_PORT, BUZZER_PIN, GPIO_PIN_RESET); // 蜂鸣器响
    // 红灯闪烁由RTC秒中断控制
    }

    /* 停止提醒(恢复正常状态) */
    void Alarm_Stop(void)
    {
    Alarm_Flag = 0;
    Alarm_Count = 0;
    HAL_GPIO_WritePin(BUZZER_GPIO_PORT, BUZZER_PIN, GPIO_PIN_SET); // 蜂鸣器关
    HAL_GPIO_WritePin(LED_RED_GPIO_PORT, LED_RED_PIN, GPIO_PIN_RESET); // 红灯灭
    HAL_GPIO_WritePin(LED_GREEN_GPIO_PORT, LED_GREEN_PIN, GPIO_PIN_SET); // 绿灯常亮

    // 蓝牙回传提醒已确认
    uint8_t confirm_msg[] = "Alarm confirmed!\\r\\n";
    Bluetooth_Send_Data(confirm_msg, sizeof(confirm_msg)1);
    }

    /* 提醒处理(控制持续时间+红灯闪烁) */
    void Alarm_Process(void)
    {
    static uint8_t led_blink_flag = 0;

    if (Alarm_Flag == 1)
    {
    // 红灯1秒闪烁一次(由秒中断触发)
    led_blink_flag = !led_blink_flag;
    HAL_GPIO_WritePin(LED_RED_GPIO_PORT, LED_RED_PIN, led_blink_flag);

    // 累计提醒时间
    Alarm_Count++;

    // 达到预设持续时间后自动停止
    if (ALARM_DURATION > 0 && Alarm_Count >= ALARM_DURATION)
    {
    Alarm_Stop();
    }
    }
    }

    4.5 蓝牙通信函数(main.c续)

    /* 蓝牙发送数据函数 */
    void Bluetooth_Send_Data(uint8_t *data, uint8_t len)
    {
    HAL_UART_Transmit(&huart1, data, len, 100); // 超时100ms
    }

    /* 蓝牙指令解析函数(支持多指令) */
    void Bluetooth_Parse_Command(void)
    {
    uint8_t cmd_buf[16] = {0};
    uint8_t param_buf[8] = {0};
    uint8_t cmd_len = 0;
    uint8_t param_len = 0;

    // 清空无效数据,提取有效指令(以\\r\\n结尾)
    for (uint8_t i=0; i<Bluetooth_Rx_Len; i++)
    {
    if (Bluetooth_Rx_Buf[i] == '\\r' || Bluetooth_Rx_Buf[i] == '\\n')
    {
    break;
    }
    cmd_buf[cmd_len++] = Bluetooth_Rx_Buf[i];
    }

    if (cmd_len == 0)
    {
    goto cmd_parse_end; // 无有效指令
    }

    // 指令1:查询当前时间 – "GET_TIME"
    if (memcmp(cmd_buf, "GET_TIME", 7) == 0)
    {
    uint8_t time_msg[32] = {0};
    RTC_Read_Time();
    // 格式化输出:2025-01-01 12:30:00
    sprintf((char*)time_msg, "Current time: 20%02d-%02d-%02d %02d:%02d:%02d\\r\\n",
    RTC_CurrentDate.Year, RTC_CurrentDate.Month, RTC_CurrentDate.Date,
    RTC_CurrentTime.Hours, RTC_CurrentTime.Minutes, RTC_CurrentTime.Seconds);
    Bluetooth_Send_Data(time_msg, strlen((char*)time_msg));
    }

    // 指令2:设置服药时间 – "SET_PILLTIME X HH:MM"(X=组号0-4,HH:MM=时间)
    else if (memcmp(cmd_buf, "SET_PILLTIME", 11) == 0)
    {
    if (cmd_len < 14) // 至少长度:SET_PILLTIME 0 08:00
    {
    uint8_t err_msg[] = "Param error! Format: SET_PILLTIME X HH:MM\\r\\n";
    Bluetooth_Send_Data(err_msg, sizeof(err_msg)1);
    goto cmd_parse_end;
    }

    // 提取组号(第12位)
    uint8_t group = cmd_buf[12] '0';
    if (group >= MAX_PILL_TIME)
    {
    uint8_t err_msg[] = "Group error! Group: 0-4\\r\\n";
    Bluetooth_Send_Data(err_msg, sizeof(err_msg)1);
    goto cmd_parse_end;
    }

    // 提取小时(第14-15位)
    uint8_t hour = Str_to_Num(&cmd_buf[14], 2);
    // 提取分钟(第17-18位)
    uint8_t min = Str_to_Num(&cmd_buf[17], 2);

    if (hour > 23 || min > 59)
    {
    uint8_t err_msg[] = "Time error! Hour:0-23, Min:0-59\\r\\n";
    Bluetooth_Send_Data(err_msg, sizeof(err_msg)1);
    goto cmd_parse_end;
    }

    // 更新服药时间列表
    PillTimeList[group].hour = hour;
    PillTimeList[group].min = min;
    PillTimeList[group].enable = 1;

    // 回传成功信息
    uint8_t ok_msg[32] = {0};
    sprintf((char*)ok_msg, "PillTime %d set to %02d:%02d\\r\\n", group, hour, min);
    Bluetooth_Send_Data(ok_msg, strlen((char*)ok_msg));
    }

    // 指令3:确认提醒 – "CONFIRM_ALARM"
    else if (memcmp(cmd_buf, "CONFIRM_ALARM", 12) == 0)
    {
    if (Alarm_Flag == 1)
    {
    Alarm_Stop();
    }
    else
    {
    uint8_t msg[] = "No alarm running!\\r\\n";
    Bluetooth_Send_Data(msg, sizeof(msg)1);
    }
    }

    // 指令4:查询服药时间列表 – "GET_PILLTIME"
    else if (memcmp(cmd_buf, "GET_PILLTIME", 11) == 0)
    {
    uint8_t list_msg[64] = "PillTime List:\\r\\n";
    uint8_t msg_len = strlen((char*)list_msg);

    for (uint8_t i=0; i<MAX_PILL_TIME; i++)
    {
    if (PillTimeList[i].enable == 1)
    {
    msg_len += sprintf((char*)&list_msg[msg_len], "Group %d: %02d:%02d (Enabled)\\r\\n",
    i, PillTimeList[i].hour, PillTimeList[i].min);
    }
    else
    {
    msg_len += sprintf((char*)&list_msg[msg_len], "Group %d: Disabled\\r\\n", i);
    }
    }

    Bluetooth_Send_Data(list_msg, msg_len);
    }

    // 未知指令
    else
    {
    uint8_t unk_msg[] = "Unknown command! Support: GET_TIME/SET_PILLTIME/CONFIRM_ALARM/GET_PILLTIME\\r\\n";
    Bluetooth_Send_Data(unk_msg, sizeof(unk_msg)1);
    }

    cmd_parse_end:
    // 清空接收缓冲区
    memset(Bluetooth_Rx_Buf, 0, BLUETOOTH_BUF_LEN);
    Bluetooth_Rx_Len = 0;
    }

    /* 串口接收中断回调函数 */
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    if (huart->Instance == USART1)
    {
    // 数据存入缓冲区,防止溢出
    if (Bluetooth_Rx_Len < BLUETOOTH_BUF_LEN 1)
    {
    Bluetooth_Rx_Len++;
    }

    // 重新启用接收中断
    HAL_UART_Receive_IT(&huart1, &Bluetooth_Rx_Buf[Bluetooth_Rx_Len], 1);
    }
    }

    /* 字符串转数字(如"08"→8) */
    uint8_t Str_to_Num(uint8_t *str, uint8_t len)
    {
    uint8_t num = 0;
    for (uint8_t i=0; i<len; i++)
    {
    if (str[i] >= '0' && str[i] <= '9')
    {
    num = num * 10 + (str[i] '0');
    }
    }
    return num;
    }

    /* 数字转字符串(补零) */
    void Num_to_Str(uint8_t num, uint8_t *str)
    {
    str[0] = (num / 10) + '0';
    str[1] = (num % 10) + '0';
    }

    4.6 按键处理函数(main.c续)

    /* 按键扫描函数(消抖) */
    void Key_Scan(void)
    {
    static uint8_t key_confirm_state = 1;
    static uint8_t key_add_state = 1;
    static uint8_t key_switch_state = 1;
    static uint32_t key_confirm_time = 0;
    static uint32_t key_add_time = 0;
    static uint32_t key_switch_time = 0;

    // 确认键(PA0)
    if (HAL_GPIO_ReadPin(KEY_CONFIRM_GPIO_PORT, KEY_CONFIRM_PIN) == GPIO_PIN_RESET)
    {
    HAL_Delay(20); // 消抖
    if (HAL_GPIO_ReadPin(KEY_CONFIRM_GPIO_PORT, KEY_CONFIRM_PIN) == GPIO_PIN_RESET)
    {
    key_confirm_state = 0;
    key_confirm_time = HAL_GetTick();
    }
    }
    else if (key_confirm_state == 0 && (HAL_GetTick() key_confirm_time) > 200)
    {
    key_confirm_state = 1;
    Key_Operate_Flag = 1; // 确认键按下
    }

    // 加一键(PA1)
    if (HAL_GPIO_ReadPin(KEY_ADD_GPIO_PORT, KEY_ADD_PIN) == GPIO_PIN_RESET)
    {
    HAL_Delay(20);
    if (HAL_GPIO_ReadPin(KEY_ADD_GPIO_PORT, KEY_ADD_PIN) == GPIO_PIN_RESET)
    {
    key_add_state = 0;
    key_add_time = HAL_GetTick();
    }
    }
    else if (key_add_state == 0 && (HAL_GetTick() key_add_time) > 200)
    {
    key_add_state = 1;
    Key_Operate_Flag = 2; // 加一键按下
    }

    // 切换键(PA2)
    if (HAL_GPIO_ReadPin(KEY_SWITCH_GPIO_PORT, KEY_SWITCH_PIN) == GPIO_PIN_RESET)
    {
    HAL_Delay(20);
    if (HAL_GPIO_ReadPin(KEY_SWITCH_GPIO_PORT, KEY_SWITCH_PIN) == GPIO_PIN_RESET)
    {
    key_switch_state = 0;
    key_switch_time = HAL_GetTick();
    }
    }
    else if (key_switch_state == 0 && (HAL_GetTick() key_switch_time) > 200)
    {
    key_switch_state = 1;
    Key_Operate_Flag = 3; // 切换键按下
    }
    }

    /* 按键处理函数 */
    void Key_Process(void)
    {
    static uint8_t key_mode = 0; // 0=正常模式,1=调时模式(小时),2=调时模式(分钟)
    static uint8_t current_group = 0; // 当前选中的服药时间组

    if (Key_Operate_Flag == 0)
    {
    return;
    }

    switch(Key_Operate_Flag)
    {
    case 1: // 确认键
    if (Alarm_Flag == 1)
    {
    // 提醒中:确认停止提醒
    Alarm_Stop();
    }
    else if (key_mode != 0)
    {
    // 调时模式:退出调时
    key_mode = 0;
    HAL_GPIO_WritePin(LED_GREEN_GPIO_PORT, LED_GREEN_PIN, GPIO_PIN_SET);
    }
    else
    {
    // 正常模式:进入调时模式(小时)
    key_mode = 1;
    HAL_GPIO_WritePin(LED_GREEN_GPIO_PORT, LED_GREEN_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_RED_GPIO_PORT, LED_RED_PIN, GPIO_PIN_SET);
    }
    break;

    case 2: // 加一键
    if (key_mode == 0)
    {
    // 正常模式:切换服药时间组
    current_group = (current_group + 1) % MAX_PILL_TIME;
    }
    else if (key_mode == 1)
    {
    // 调时模式(小时):小时+1
    RTC_CurrentTime.Hours = (RTC_CurrentTime.Hours + 1) % 24;
    RTC_Set_Time(RTC_CurrentTime.Hours, RTC_CurrentTime.Minutes, RTC_CurrentTime.Seconds);
    }
    else if (key_mode == 2)
    {
    // 调时模式(分钟):分钟+1
    RTC_CurrentTime.Minutes = (RTC_CurrentTime.Minutes + 1) % 60;
    RTC_Set_Time(RTC_CurrentTime.Hours, RTC_CurrentTime.Minutes, RTC_CurrentTime.Seconds);
    }
    break;

    case 3: // 切换键
    if (key_mode == 1)
    {
    // 调时模式:小时→分钟
    key_mode = 2;
    HAL_GPIO_TogglePin(LED_RED_GPIO_PORT, LED_RED_PIN);
    }
    else if (key_mode == 2)
    {
    // 调时模式:分钟→小时
    key_mode = 1;
    HAL_GPIO_TogglePin(LED_RED_GPIO_PORT, LED_RED_PIN);
    }
    break;

    default:
    break;
    }

    Key_Operate_Flag = 0; // 清空操作标志
    }

    4.7 RTC中断与主函数(main.c续)

    /* RTC中断处理函数 */
    void RTC_IRQHandler(void)
    {
    HAL_RTC_IRQHandler(&hrtc);

    // 秒中断触发(1秒一次)
    if (__HAL_RTC_GET_FLAG(&hrtc, RTC_FLAG_SEC) != RESET)
    {
    __HAL_RTC_CLEAR_FLAG(&hrtc, RTC_FLAG_SEC);

    // 读取当前时间
    RTC_Read_Time();

    // 检查是否触发服药提醒
    if (RTC_Check_PillTime() == 1 && Alarm_Flag == 0)
    {
    Alarm_Start();
    // 蓝牙推送提醒通知
    uint8_t alarm_msg[] = "Take your medicine now!\\r\\n";
    Bluetooth_Send_Data(alarm_msg, sizeof(alarm_msg)1);
    }

    // 处理提醒(红灯闪烁+计时)
    Alarm_Process();

    // 蓝牙接收指令解析(每秒检查一次)
    if (Bluetooth_Rx_Len > 0)
    {
    Bluetooth_Parse_Command();
    }
    }
    }

    /* 主函数 */
    int main(void)
    {
    /* 初始化HAL库 */
    HAL_Init();

    /* 配置系统时钟 */
    SystemClock_Config();

    /* 初始化外设 */
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_RTC_Init();

    /* 初始状态:绿灯常亮 */
    HAL_GPIO_WritePin(LED_GREEN_GPIO_PORT, LED_GREEN_PIN, GPIO_PIN_SET);

    /* 蓝牙发送欢迎信息 */
    uint8_t welcome_msg[] = "Smart Pill Box System Ready!\\r\\n";
    Bluetooth_Send_Data(welcome_msg, sizeof(welcome_msg)1);

    /* 主循环 */
    while (1)
    {
    // 按键扫描与处理
    Key_Scan();
    Key_Process();

    // 低功耗延时(减少CPU占用)
    HAL_Delay(10);
    }
    }

    五、代码烧录与调试

    5.1 代码编译与烧录

  • 代码补充:将上述所有代码按模块补充到Keil工程的main.c和main.h中,确保无遗漏;若提示sprintf未定义,需在Keil中开启“Use MicroLIB”(魔法棒→Target→勾选Use MicroLIB)。
  • 编译代码:点击Keil工具栏的“Build”按钮,检查是否有错误(Error)和警告(Warning),确保0 Error、0 Warning。
    • 若出现“RTC相关错误”:检查STM32CubeMX是否启用LSE晶振,RTC中断是否开启;
    • 若出现“串口中断错误”:确认USART1中断优先级配置正确。
  • 硬件连接:将ST-Link下载器连接到STM32最小系统板的SWD接口(SWDIO、SWCLK、GND),并连接电脑USB;给STM32供电(5V USB)。
  • 代码烧录:点击Keil工具栏的“Download”按钮,等待烧录完成(底部提示“Download completed”)。
  • 5.2 硬件调试步骤

    步骤1:基础功能调试
    • 电源检查:测量STM32 5V引脚电压为5V,HC-05蓝牙模块VCC引脚电压为5V,按键/LED引脚电压正常(上拉输入为3.3V)。
    • 蓝牙配对:
    • HC-05默认配对码:1234或0000;
    • 手机开启蓝牙,搜索“HC-05”并配对;
    • 打开手机“蓝牙串口助手”APP,连接HC-05,波特率设置为9600bps。
    • 初始状态验证:STM32上电后,绿色LED常亮,蜂鸣器不响,蓝牙串口助手收到“Smart Pill Box System Ready!”。
    步骤2:时间功能调试
    • 查询当前时间:在蓝牙串口助手发送“GET_TIME”,应收到类似“Current time: 2025-01-01 00:00:00”的回复。
    • 调整本地时间:
    • 按下“确认键(PA0)”,绿色LED灭、红色LED常亮(进入调时模式);
    • 按下“加一键(PA1)”调整小时,按下“切换键(PA2)”切换到分钟调整;
    • 再次按下“确认键”退出调时模式,发送“GET_TIME”验证时间是否更新。
    步骤3:定时提醒调试
    • 设置服药时间:在蓝牙串口助手发送“SET_PILLTIME 0 08:00”(设置0组服药时间为8:00),收到“PillTime 0 set to 08:00”回复;发送“GET_PILLTIME”可查看所有组的时间配置。
    • 触发提醒验证:
    • 调整系统时间到07:59:50,等待10秒后到08:00:00;
    • 蜂鸣器响起,红色LED每秒闪烁一次,蓝牙串口助手收到“Take your medicine now!”;
    • 按下“确认键”或发送“CONFIRM_ALARM”,提醒停止,绿色LED恢复常亮,蓝牙收到“Alarm confirmed!”。
    步骤4:异常场景调试
    • 重复提醒:设置ALARM_DURATION为0(无限提醒),触发后不确认,验证蜂鸣器和红灯持续工作;
    • 多组时间:设置5组不同的服药时间,验证每个时间点都能准确触发提醒;
    • 掉电续航:断开STM32主电源,仅保留纽扣电池供电,10分钟后重新上电,发送“GET_TIME”验证时间未丢失。

    5.3 常见问题与解决

    问题现象可能原因解决方法
    蓝牙通信乱码 波特率不匹配、未共地 确认HC-05和STM32波特率均为9600,检查GND接线
    时间不更新 LSE晶振未接、RTC中断未开启 焊接32.768kHz晶振,检查STM32CubeMX RTC中断配置
    提醒不触发 服药时间未启用、秒级匹配失败 发送“GET_PILLTIME”确认enable=1,检查RTC_Check_PillTime函数的秒级判断
    按键无响应 上拉输入未配置、消抖时间过短 确认GPIO为Input Pull-Up,调整Key_Scan中的消抖延时(20ms)
    蜂鸣器一直响 Alarm_Flag未清零、PB0电平错误 检查Alarm_Stop函数是否将BUZZER设为High,调试Alarm_Flag变量

    六、功能扩展建议

  • OLED显示模块:外接0.96寸I2C OLED屏幕,实时显示当前时间、服药时间列表、提醒状态,无需依赖蓝牙查看信息。
  • 多模式提醒:添加震动马达,适配听力障碍用户;区分“单次提醒”和“重复提醒”(如每天同一时间提醒)。
  • 服药记录存储:外接EEPROM(如AT24C02),记录每次服药确认的时间,通过蓝牙导出记录。
  • 低电量提醒:添加锂电池+电量检测电路,电量低于20%时触发低电量提醒,并通过蓝牙推送通知。
  • 手机APP定制:开发专属APP,替代通用串口助手,支持可视化设置时间、查看服药记录、远程提醒等功能。
  • 语音提醒:外接语音模块(如SYN6288),触发提醒时播放“该吃药了”等语音提示,提升易用性。
  • 总结

  • 本方案以STM32F103C8T6为核心,基于内置RTC实现精准定时提醒,通过HC-05蓝牙模块实现远程配置与交互,硬件接线简单,代码模块化且注释详尽,零基础小白可按步骤完全落地。
  • 系统核心逻辑通过Mermaid流程图清晰呈现,关键功能(时间设置、定时匹配、蓝牙解析、按键交互)拆分为独立函数,便于理解和修改;核心参数(最大服药时间组数、提醒持续时间)可通过宏定义快速调整。
  • 调试时需优先验证电源和蓝牙通信,再逐步测试时间调整、定时提醒等功能,遇到问题可通过Keil的变量监控功能(Watch Window)查看关键变量(如Alarm_Flag、RTC_CurrentTime),快速定位故障点。
  • 赞(0)
    未经允许不得转载:171主机测评 » 场景实战:基于STM32的智能药盒定时提醒与蓝牙通信解决方案解析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址