2026 ADI CodeFusion 竞赛 - 基于max32655制作智能药盒提醒器
该项目使用了max32655,code fusion studio,实现了模块化智能药盒提醒器的设计,它的主要功能为:利用RTC、OLED实现定时服药提醒。
标签
嵌入式系统
justbe
更新2026-09-29
79
KiCad文件
全屏

摘要

针对传统用药提醒依赖手机、药仓位置容易变化以及多药仓状态难以统一管理等问题,本文设计并实现了一套基于 MAX32655FTHR 的模块化智能药仓提醒系统。系统由主控开发板、可拆装药仓模块、RTC、BLE、128×64 OLED、RGB 指示灯和 Android App 组成。

设备端通过级联电路识别药仓 ID、连接状态和开盖状态,并依据本地 RTC 完成提醒判断。手机端通过 BLE 完成设备连接、时间同步、提醒配置和状态查看。系统将提醒配置与逻辑 ID 绑定,将当前状态与物理槽位绑定,使药仓在插拔或调整顺序后仍能保持正确的提醒关系。

关键词: MAX32655FTHR;模块化药仓;RTC;BLE;OLED;Android App

1 项目概述

1.1 任务选择

所选任务:模块化智能药仓提醒系统设计与实现。

1.2 项目背景

手机 App 适合进行提醒配置,但手机后台限制、蓝牙断开和 App 退出可能影响提醒可靠性。因此,本项目将提醒判断放在药仓设备端,由单片机和本地 RTC 独立完成提醒;手机主要承担配置和信息查看功能。

项目采用可拆装药仓结构,多个药仓通过级联方式连接到主控。系统能够识别当前已接入的药仓,并根据药仓 ID 管理每日提醒时间。

1.3 项目目标

  1. 实现最多 8 个药仓的识别、开盖检测和状态显示。
  2. 实现基于本地 RTC 的每日提醒和开盖确认。
  3. 实现 BLE Android App,完成提醒时间配置和设备状态查看。

1.4 系统功能

系统主要功能包括:

  • 药仓 ID 和级联状态识别;
  • 药仓开盖检测;
  • 每日提醒时间配置;
  • RTC 本地提醒和 RGB 灯提示;
  • OLED 显示当前时间、药仓 ID 和状态;
  • BLE 连接、时钟同步、配置写入和状态通知;
  • 药仓插拔后的重新扫描和状态更新。

2 系统方案与设计思路

2.1 系统方案

image.png

2.2 设计思路

(1)提醒本地化

Android App 通过 BLE 写入提醒时间和设备时钟,设备将配置保存到 Flash,并由本地 RTC 完成提醒判断。手机暂时断开时,设备仍可独立工作。

(2)身份与位置分离

提醒时间按照药仓逻辑 ID 保存,物理槽位只表示药仓在级联链中的当前位置。药仓重新插拔或调整顺序后,设备重新扫描拓扑,并按照 ID 恢复提醒关系。

(3)事件与状态分离

开盖和插拔信号只触发唤醒事件,业务层延时后再扫描药仓。通过连续一致的扫描结果确认稳定状态,减少连接器抖动造成的误判。

(4)适配小尺寸显示屏

OLED 分辨率为 128×64,状态页采用横屏八列布局,从左到右对应物理槽位 1 至 8。每列显示十进制 ID 和开盖图标,不存在的槽位直接刷黑,列间采用分隔线。

3 硬件设计

IMG_20260915_224625.jpgIMG_20260915_224634.jpg2710359e10d7bfbc6193a54e3e9c7188.jpeg

IMG_20260915_224233.jpgIMG_20260915_224211.jpgIMG_20260915_224222.jpg

3.1 硬件组成

硬件

功能

MAX32655FTHR

系统主控,提供 BLE、RTC、GPIO、SPI 和 I2C

SSD1312 128×64 OLED

显示时间、药仓数量、ID 和开盖状态

74HC165

读取药仓 ID、连接状态和开盖状态

74HC164

输出 RGB 指示灯控制信号

CC6201ST

检测药仓盖子开合

W25 Flash

保存提醒配置

MAX20303 PMIC

主控板电源管理

PMOS 负载开关

控制 OLED 和 RGB 等负载电源

RGB LED

表示提醒、确认和异常状态

74LVC2G17、74AHC1G86、74LVC1G32

热插拔检测、脉冲处理和唤醒级联

3.2 硬件工作原理

主控通过 SPI 读取 74HC165 输入链,获得每个药仓的 3-bit ID、下一级连接状态和开盖状态。74HC164 输出串行 RGB 控制帧,并将数据传递给下一级药仓。OLED 通过 I2C 连接在主控板上,用于显示系统状态。

系统采用电源分层设计。VCC_3V3_AON 为药仓检测和唤醒逻辑供电,LED_POWER 为 OLED 和 RGB 等负载供电。系统空闲时可以关闭显示和灯光,同时保留必要的检测和唤醒能力。

3.3 开盖唤醒

每个药仓使用 `CC6201ST` 霍尔开关检测盖子。闭盖输出低电平,开盖输出高电平。该信号同时送入 74HC165,作为开盖状态数据;也送入 `74LVC1G32` 或门,作为本地唤醒信号。

开盖唤醒采用高电平级联:本地开盖信号与下游传来的 `COVER_WAKE` 经过或门合并,再向上游输出。任意一个药仓开盖后,高电平会逐级传到主控,触发主控唤醒;只要仍有药仓开盖,唤醒线就保持高电平。主控唤醒后再读取 74HC165,确认具体是哪一个药仓开盖。

image.png

3.4 热插拔检测

药仓插拔通过下一级存在检测线判断。下一级插入时,`LINK_DET_N_LOCAL` 被拉低;下一级拔出后,该信号由上拉恢复高电平。这个稳定电平送入 74HC165,用于扫描药仓链路是否存在。

为了让插入和拔出都能唤醒主控,`LINK_DET_N_LOCAL` 分成两路:一路直接经过施密特整形得到快速边沿,另一路经过 RC 延时和施密特整形得到延迟边沿。两路送入异或门,在电平变化后的短暂时间内,两路电平不同,异或输出为高电平;延时结束后两路恢复一致,输出回到低电平。因此一次插入或拔出都会被转换成一个高电平脉冲。

image.png

各药仓的插拔脉冲再通过 74LVC1G32 或门向上游级联,最终输入主控的 `PLUG_WAKE`。主控收到脉冲后重新读取首仓存在电平和 74HC165 数据,确认实际药仓数量、ID 以及链路状态。也就是说,或门负责传递唤醒电平,RC 加异或负责把电平变化变成脉冲,最终拓扑仍以重新扫描结果为准。

4 原理图与 PCB 设计

项目使用 KiCad 完成药仓模块和主控板相关的原理图、PCB 设计。硬件设计内容包括电源部分、74HC165 输入链、74HC164 输出链、开盖检测、热插拔检测以及唤醒级联电路。

药仓模块使用上下游连接器进行级联,电源、串行时钟、输入链、输出链和唤醒信号按照模块结构布置。PCB 布局围绕主控接口、药仓连接器和信号级联展开,便于多个药仓连续安装。

image.png

5 软件设计

5.1 软件平台

固件位于 fw/CM4,使用 ADI MSDK、Cordio BLE 和 WSF 事件框架。设备作为 BLE Peripheral/GATT Server,Android App 作为 BLE Central/GATT Client。

Android App 使用 Kotlin、Jetpack Compose 和 Android BluetoothGatt,完成 BLE 扫描、连接、数据读写和界面显示。

5.2 软件流程

image.png

5.3 固件关键模块

模块

主要功能

main.c

完成系统初始化和 WSF 主循环

medcab.c

管理药仓拓扑、提醒状态、显示模型和 RGB 输出

medcab_rtc.c

读取 RTC、设置时间和执行分钟调度

medcab_config_store.c

保存和恢复提醒配置

shift74hc.c

读取 74HC165 和写入 74HC164

ble_peripheral.c

提供 Status、Config、Clock GATT 特征

u8g2_port.c

完成 OLED 通信和显示适配

到达设定时间后,当前存在的对应药仓进入 DUE 状态并亮红灯;超过一定时间但仍在 5 分钟窗口内时进入 MISSED;用户打开对应药仓后进入 ACKNOWLEDGED。超过 5 分钟后清除未处理提醒并关闭 RGB 灯。

5.4 单片机关键代码说明

5.4.1 启动入口与事件分发

main()负责完成板级初始化、应用驱动初始化和 BLE 平台初始化。初始化完成后,主循环只取出检测事件、更新定时器、分发 WSF 事件和轮询日志,不在中断中执行 SPI、Flash 或 BLE 操作。

int main(void)
{
PinInit();
PeripheralInit();
AppLogInit();
AppDriversInit();
BlePlatformInit();

while (1) {
uint32_t events = BoardDetectTakeEvents();
if (events != 0U) {
MedcabNotifyDetectEvents(events);
}
WsfTimerSleepUpdate();
wsfOsDispatcher();
AppLogPoll();
}
}

这种结构将初始化、硬件事件和业务处理分离。GPIO ISR 只负责置位事件,真正的扫描和状态变更由 MedcabHandler()在 WSF 上下文中完成,避免中断执行时间过长。

5.4.2 药仓输入快照与 ID 解析

medcabReadSnapshot()先读取首仓存在信号,再决定是否读取 74HC165 输入链。首仓不存在时直接生成空快照,避免对已经断开的级联链路进行无效读取;首仓存在时才执行一次 SPI 扫描。

static int medcabReadSnapshot(medcab_snapshot_t *snapshot)
{
int result = BoardDetectGet(BOARD_DETECT_HOTPLUG_FIRST,
&snapshot->hotplug_first);

if (result != E_NO_ERROR) {
return result;
}
if (!snapshot->hotplug_first) {
memset(snapshot->raw, 0, sizeof(snapshot->raw));
return E_NO_ERROR;
}
return Shift74hcRead(snapshot->raw, sizeof(snapshot->raw));
}

每个 74HC165 原始字节的低 3 位是 ID,0x08表示后面仍有药仓,0x10表示当前药仓开盖。软件遇到 ID 7立即停止,当前值及其后的数据不加入有效拓扑;连续两次扫描结果一致后才提交新的拓扑,避免插拔抖动造成错误状态。

static bool medcabNextConnected(uint8_t raw)
{
return (raw & MEDCAB_RAW_PRESENT) == 0U;
}

static bool medcabCoverOpen(uint8_t raw)
{
return (raw & MEDCAB_RAW_COVER_OPEN) != 0U;
}

5.4.3 拓扑变化与开盖确认

medcabApplySnapshot()将新快照与上一份稳定快照比较,只保留仍处于相同物理位置的提醒状态。新插入的药仓初始化为 IDLE,移除的药仓清除待处理状态;因此缺失药仓不会在到期后产生提示,重新插入时也不会补发已经错过的提醒。

对开盖状态的处理以物理槽位为单位。当检测到 DUE或 MISSED药仓由闭盖变为开盖时,只确认该槽位,并将其置为 ACKNOWLEDGED;同一个 ID 的其他物理药仓不会被同时确认。关盖和普通开盖事件则生成短时状态页,触发 OLED 刷新。

5.4.4 RTC 提醒状态机

MedcabProcessRtcTime()将 RTC 秒数转换为中国标准时间,并按 ID 遍历每日配置。只有当前拓扑中存在的 ID 才参与判断。提醒时间到达后,1 分钟内进入 `DUE`,超过 1 分钟但仍在 5 分钟窗口内进入 `MISSED`,超过窗口则直接清除,不再补发。

if (!medcabIdPresent(id)) {
medcabCb.idReminderDay[id] = MEDCAB_DAY_INVALID;
medcabCb.idReminderState[id] = MEDCAB_SLOT_IDLE;
continue;
}

reminderAge = localEpochSeconds - dueSeconds;
if (reminderAge >= MEDCAB_REMINDER_WINDOW_SECONDS) {
medcabSetReminderForId(id, MEDCAB_SLOT_IDLE, day);
continue;
}

medcabSetReminderForId(id,
reminderAge <= 60UL ? MEDCAB_SLOT_DUE : MEDCAB_SLOT_MISSED,
day);

提醒状态以 ID 记录当天是否已经触发,再映射到当前实际存在的物理槽位。这样可以处理设备重启、药仓插拔、重复 ID 和提醒超时等情况;在提醒有效期间发生药仓状态变化时,UI 计时器会重新开始 5 分钟倒计时。

5.4.5 RGB 输出帧

medcabRenderLeds()根据每个物理槽位的提醒状态生成 74HC164 输出帧。因为数据从首仓向下游移位,最远端药仓的字节需要先写入,因此代码使用 length - 1 - slot反向排列。共阳 RGB 为低电平点亮,未使用的输出预先填充熄灭值。

memset(frame, MEDCAB_LED_OFF, sizeof(frame));

if (!medcabCb.displaySleeping) {
for (uint8_t slot = 0U;
slot < medcabCb.committedTopology.active_count;
slot++) {
frame[length - 1U - slot] = medcabLedColor(slot, warning);
}
}

if (!medcabCb.ledFrameValid ||
(memcmp(frame, medcabCb.lastLedFrame, sizeof(frame)) != 0)) {
Shift74hcWriteRaw(frame, length);
}

medcabLedColor()的状态映射为:待提醒和超时提醒显示红色,普通开盖显示绿色,拓扑异常显示黄色,闭盖空闲或睡眠状态关闭灯光。只有输出帧变化时才重新移位,减少无效总线操作。

5.4.6 128x64 OLED 绘制

u8g2PortDrawSlotStatus()将屏幕宽度平均分为 8 列,每列固定宽度 128 / 8 = 16像素。不存在的槽位先用黑色矩形覆盖状态区;存在的槽位居中绘制十进制 ID,再绘制开盖图标,最后绘制列间分隔线。

static const uint8_t columnWidth = APP_SSD1306_WIDTH / MEDCAB_SLOT_COUNT;

for (uint8_t slot = 0U; slot < MEDCAB_SLOT_COUNT; slot++) {
uint8_t x = slot * columnWidth;

if ((slot >= model->slot_count) ||
(model->topology_ids[slot] == UINT8_MAX)) {
u8g2_SetDrawColor(&appU8g2, 0U);
u8g2_DrawBox(&appU8g2, x, 14U, columnWidth,
APP_SSD1306_HEIGHT - 14U);
u8g2_SetDrawColor(&appU8g2, 1U);
continue;
}

snprintf(id, sizeof(id), "%u", model->topology_ids[slot]);
u8g2_DrawStr(&appU8g2, centeredX, 29U, id);
u8g2PortDrawCoverIcon(x, model->topology_cover_open[slot]);
}

状态页只显示实际存在的药仓,ID 使用十进制,开盖和闭盖使用图标区分。顶部标题和状态区使用固定坐标,保证动态数据不会改变列宽,也不会在 128x64 屏幕上发生文字重叠。

5.4.7 BLE 配置写入

blePeripheralMedcabWriteCback()对 App 写入的 Config数据进行长度、时间范围和 Flash 写入检查。数据包由 7 个 little-endian uint16_t组成,0xffff表示禁用。只有完整配置通过校验并成功写入双副本 Flash 后,才调用 MedcabApplyConfig()替换运行配置。

if (len != sizeof(blePeripheralMedcabConfigValue)) {
return ATT_ERR_LENGTH;
}

for (uint8_t id = 0U; id < MEDCAB_ID_COUNT; id++) {
uint8_t byte = id * sizeof(uint16_t);
config.minute_of_day[id] = (uint16_t)pValue[byte] |
((uint16_t)pValue[byte + 1U] << 8U);
}

if (!MedcabConfigIsValid(&config)) {
return ATT_ERR_VALUE_RANGE;
}
if (MedcabConfigStoreSave(&config) != E_NO_ERROR) {
return ATT_ERR_UNLIKELY;
}
MedcabApplyConfig(&config);
return ATT_SUCCESS;

这种整包提交方式避免了逐个设置时间时出现部分配置生效。`Clock` 特征使用 4 字节 Unix 秒写入 RTC,Status特征则在拓扑、提醒或开盖状态变化后通过 WSF 消息排队发送通知。

5.4.8 事件与低功耗处理

GPIO 中断只记录首仓检测、插拔脉冲和开盖事件,主循环调用 BoardDetectTakeEvents()取出事件后交给 `MedcabNotifyDetectEvents()`。药仓扫描、去抖、Flash、OLED 和 BLE 操作均在 WSF 事件处理上下文执行,避免中断时间过长。

提醒超时或普通空闲达到 30 秒时,控制器清空 OLED 并将 RGB 输出帧改为全灭;开盖状态和插拔事件仍保持扫描。设备从假休眠恢复后,先完成药仓扫描和状态确认,再重新绘制显示内容,避免使用休眠前的过期拓扑。

5.5 BLE 与 Android App

BLE 设备作为 Peripheral,手机作为 Central。业务服务包含 Status、Config 和 Clock 三个特征:

- Status:读取和通知 RTC、药仓数量、ID、提醒状态及开盖状态。

- Config:读取或整包写入 7 个 ID 的每日提醒时间。

- Clock:写入手机当前时间,对设备 RTC 进行校时。

Android App 使用 Kotlin、Jetpack Compose、BluetoothGatt 和协程实现。设备页负责扫描和连接,闹钟页使用时间选择器设置每日提醒,状态页展示设备实时状态。App 不自行判断是否到点,也不直接控制 RGB 灯。

5.6 调试软件

固件使用 CodeFusion Studio™ 进行工程管理、代码查看、固件调试。调试过程中重点查看 RTC 恢复、药仓扫描、ID 解码、开盖状态、提醒触发和显示更新日志。
image.png

Android App 使用 Android Studio 进行开发和调试,重点验证 BLE 扫描、GATT 服务发现、配置读写、时钟同步和状态通知。

image.png

6 设备界面设计

6.1 单片机 UI

OLED 采用横屏固定八列布局。顶部显示 SLOTS N/8,每列显示物理槽位的十进制 ID 和开盖图标;不存在的药仓整列刷黑,列之间使用分隔线。RGB 灯用于表达提醒颜色,OLED 用于表达位置和状态。

首页用于显示当前时间和下一次提醒信息:

b83e126120ea205eef13104c19e6a16f.png

image.png

状态页固定显示 8 个物理槽位,避免药仓数量变化造成界面跳动:

image.png

image.png

提醒页面显示待处理药仓,配合 RGB 红灯提示;用户打开对应药仓后完成确认:

qq_pic_merged_1788068845136.jpg

6.2 手机 App

Android App 包含设备页、闹钟页和状态页。设备页负责扫描和连接:

20a60f21259889e27afd86ab6ea36cb5.png

闹钟页显示十进制 ID 0..6,使用 24 小时制时间选择器配置提醒:

ac38379cc8c57adbe835118be0584281.pngScreenshot_2026_0830_134026.png

状态页显示设备时间、药仓数量、ID、提醒状态和开盖状态。状态变化弹窗固定显示 8 列,不存在的药仓显示黑色:

0effdd7a9c4918458fa36b8882b59964.png4a90a8bf5ec924d864e8f0058cd20b7e.png

7 遇到的难点及解决方法

7.1 RTC 上电状态不稳定

RTC 在上电或复位后可能需要一定时间才能稳定读取。系统在进入业务逻辑前先等待并重试读取 RTC;只有读到有效时间后才参与提醒判断,无效时显示 SET CLOCK。

7.2 级联药仓扫描容易受到抖动影响

药仓插拔时连接器和检测信号可能短暂变化。系统将中断和业务处理分开,事件发生后延时约 50 ms,再进行输入链扫描,并使用连续一致的结果确认当前拓扑。

7.3 药仓位置变化导致提醒错配

如果直接按物理位置保存提醒时间,药仓换位后可能对应错误的提醒。系统将提醒配置绑定到逻辑 ID,物理槽位只保存当前状态,从而避免位置变化导致的错配。

7.4 128×64 屏幕显示空间有限

屏幕无法同时显示大量文字和完整状态信息。系统固定使用八列布局,采用十进制 ID、开盖图标、缺仓刷黑和列间分隔线,并将提醒颜色交给 RGB 灯表达。

7.5 提醒重复触发和过期补发

设备重启或药仓重新插入时,旧提醒可能被重复触发。系统记录当天提醒状态和 5 分钟有效窗口,只对当前存在的药仓提醒;超过窗口或缺仓期间的提醒不再补发。

8 心得体会、意见与建议

通过本项目,我对嵌入式系统中硬件、固件、BLE 和 Android App 的协同关系有了更直观的认识。定时提醒看似简单,但实际涉及 RTC 初始化、级联扫描、状态机、异常处理、低功耗和 UI 设计等多个方面。

项目实施过程中,提前划分逻辑 ID、物理槽位和瞬时事件非常重要。对于资源有限的显示屏,也应先确定信息优先级,再设计固定布局,而不是简单缩小所有文字。

建议后续活动提供统一的硬件接口说明、基础示例工程和阶段性测试要求,帮助参赛者更早完成电源、通信和显示等基础验证。后续项目将继续通过真实硬件测试确认 RTC、药仓级联、BLE、OLED、RGB 和低功耗功能。



附件下载
max32655_medbox.zip
团队介绍
无
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号