文章目录
-
- 一、方案整体设计与原理说明
-
- 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,接线如下:
| PA9 | TXD | STM32_USART1_RX(接收蓝牙数据) |
| PA10 | RXD | STM32_USART1_TX(发送蓝牙数据) |
| 5V | VCC | 蓝牙模块供电(底板版支持5V) |
| GND | GND | 共地(必须共地,否则通信乱码) |
(2)STM32与提醒模块接线
| PB0 | 有源蜂鸣器IN端 | 低电平触发蜂鸣器 |
| PB1 | 绿色LED正极 | 正常状态常亮(串220Ω电阻) |
| PB2 | 红色LED正极 | 提醒状态闪烁(串220Ω电阻) |
| GND | 蜂鸣器/LED负极 | 共地 |
(3)STM32与本地按键接线
| PA0 | 按键1一端 | 确认提醒(上拉输入,按下接地) |
| PA1 | 按键2一端 | 时间+1(上拉输入) |
| PA2 | 按键3一端 | 时间切换(时分/组号,上拉输入) |
| GND | 按键另一端 | 所有按键共地 |
(4)RTC备用电源接线(可选)
若STM32最小系统板无RTC备用电池座,需外接CR2032电池:
| VBAT | 电池正极 | RTC掉电供电 |
| GND | 电池负极 | 共地 |
三、软件开发环境搭建
3.1 环境准备
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 代码编译与烧录
- 若出现“RTC相关错误”:检查STM32CubeMX是否启用LSE晶振,RTC中断是否开启;
- 若出现“串口中断错误”:确认USART1中断优先级配置正确。
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变量 |

