新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于STM32的智能药盒设计与实现:从状态机到实物

发布时间:2026/9/8 14:15:51来源:尧图网络
基于STM32的智能药盒设计与实现:从状态机到实物
说实话做这个项目前我在各种平台上翻过不少市售智能药盒功能全一点的动不动就几百上千元做工参数参差不齐而且很多按键对老人并不友好。最后我决定用STM32F103C8T6自己搭一套智能药盒/老人用药管理系统把定时提醒、分格配药、漏服记录、语音播报这些核心功能全部揉进一块小板子里顺手把代码、原理图和Proteus仿真工程全部开源。这篇文章就是这次从需求拆解、原理图设计、软件状态机、仿真联调到最后实物翻车的完整记录适合有单片机基础的开发者参考也适合想用STM32做毕业设计或嵌入式项目练手的朋友。1. 需求先行智能药盒到底解决老人用药的哪个痛点1.1 老人用药的真实痛点我身边有长辈同时吃高血压、糖尿病、冠心病三类药每天要按时按量吃四五种光靠记忆根本扛不住。真实场景里问题主要出在三类漏服事情一多就忘记吃等想起来已经过了饭点。错服几种药外形相似拿错瓶子、吃错药盒。重复服吃过一次之后忘记自己吃过过一两个小时又吃一遍这个比漏服更危险。所以智能药盒的第一KPI不是“能提醒”而是“能管理状态”——什么时间该吃哪一格、吃没吃、超时没吃这些状态必须能被记录和展示。这也是为什么不能简单用定时器加蜂鸣器糊弄过去需要引入一个清晰的状态机。1.2 功能清单与取舍做硬件最忌讳一上来就堆功能。我的原则是先列清楚场景再定功能优先级功能模块场景说明优先级分格药盒管理同一时间点需要吃多种药按格子提示P0定时提醒早中晚不同时段不同格子不同时间P0状态记录已服用/漏服/等待家人可查看P0语音播报告诉老人“请打开第2格服用2片”P1开盒检测检测是否真的打开对应药格P1缺药提醒药不够了屏幕提示或蜂鸣器提示P2远程通知通过WiFi/GPRS通知家属P3先砍掉第一版我砍掉了远程通知和摄像头原因是它们会显著增加联网调试成本和整机功耗而药盒最核心的“按时提醒、避免错服漏服”已经可以通过本地功能闭环解决。后面如果想扩展再单独加ESP8266模块也不迟。1.3 核心选型与成本主控选择STM32F103C8T6Cortex-M3内核72MHz主频64KB Flash、20KB SRAMLQFP48封装手焊也能搞定。价格低、例程多、Keil和CubeMX支持完善做药盒这种外设不算夸张的设备完全够用。其他关键器件选型如下器件型号方案选择理由实时时钟DS3231±2ppm温补晶振年误差一分钟级别比DS1302省心太多显示屏0.96寸OLEDI2C显示信息量够接口简洁驱动简单语音播报SYN6288或MP3-TF-16P串口控制、能播中文句子和音频文件蜂鸣器有源蜂鸣器提醒音量大驱动简单做备用提醒开盒检测微动开关/霍尔传感器检测药仓门是否被打开电源5V USB供电 3.3V LDO开发调试方便也可用4节AA电池供电整套BOM成本大概在80元左右比起成品药盒来说不算贵而且所有逻辑自己可控改一个剂量、换一段语音都是代码级别的事。2. 原理图设计从最小系统到每一路外设的关键节点2.1 最小系统三处易错点STM32F103C8T6的最小系统不复杂但三个地方我见过很多新手翻车这里单独说晶振与负载电容。8MHz主晶振两个负载电容选22pF32.768kHz低速晶振选6~10pF。电容选太大会导致晶振起振困难甚至不起振选太小时频率稳定性差。更稳妥的做法是使用DS3231自带的高精度时钟源作为时间基准主晶振只负责CPU运行频率。复位电路。复位引脚需要10kΩ电阻上拉到3.3V再接100nF电容到GND。很多朋友画原理图时省掉这个电容结果偶尔上电后程序跑飞非常难排查。BOOT0引脚。正常运行时BOOT0必须通过10kΩ下拉到GND让芯片从Flash启动。如果BOOT0悬空或者接高上电后芯片进系统存储器或者SRAM启动程序根本不运行但ST-Link下载时又会显示成功这个现象很容易让人怀疑程序有问题。给最小系统配一个0.1uF10uF的电源去耦组合靠近芯片电源引脚放置。不要迷信“STM32很耐造”电源纹波大的时候ADC采集、语音播报、I2C通信都会变得异常。2.2 RTC后备电池与掉电保持DS3231的VBAT引脚接一枚CR1220纽扣电池断电后时间可以继续走。这里有个细节如果系统本身用USB供电而VBAT外接电池要注意电池和主电源之间的隔离。最稳妥的方案是用一个1N4148二极管或者低漏电流的肖特基二极管做电源切换避免主电源给纽扣电池充电也避免电池反向灌电。用DS3231模块的话模块上基本都会带这个电路自己画板子时务必保留。还要注意INT/SQW引脚。这个引脚可以配置成方波输出也可以作为闹钟中断输出接到MCU的EXTI引脚这样MCU可以进入睡眠模式等闹钟触发再唤醒。对电池供电的药盒来说这个是降低功耗的关键手段。2.3 屏幕、语音、按键、开盒检测接口设计OLED的I2C接口SDA和SCL需要上拉电阻到3.3V典型值4.7kΩ。如果你用的是现成OLED模块模块上往往已经有上拉电阻MCU端可以不重复加但如果模块没有一定要补上否则I2C通信会时好时坏。语音模块用串口UART控制。注意绝大多数语音模块默认是TTL电平MCU也是TTL电平两边直接交叉连接就行。但如果你的板子上接了USB转串口模块电脑端的USB转串口芯片通常是RS232电平这里非常容易焊错导致语音模块要么不响应要么乱码。接线原则是TX接RX、RX接TX地线必须共地。按键矩阵的设计我优先使用了独立按键方式因为药盒最多只需4到6个按键独立按键逻辑简单直接接GPIO输入加上拉或下拉电阻。开盒检测用微动开关常开触点一端接GND另一端接GPIO并启用内部上拉当药仓门打开时GPIO被拉低检测到下降沿。机械开关一定要处理抖动硬件上在开关两端并联100nF电容软件上再做一次消抖双保险。2.4 成图与PCB前的自检清单以立创EDA画原理图为例我的自检顺序是检查电源网络3.3V和GND有没有漏连电源引脚是否都接去耦电容。检查MCU引脚分配同一个GPIO不能同时复用在两个外设上比如PA9/PA10用了串口就不要同时又接按键。检查晶振网络负载电容有没有接在晶振两端到GND而不是悬空。检查BOOT和复位BOOT0、NRST是否按规范连接。导出PCB前跑一次DRC重点看未连接网络、单端网络、逻辑错误。画原理图时建议把各功能模块用框线分区比如主控区、电源区、时钟区、人机交互区。清晰的分区不仅方便自己检查也方便别人读图这和你提供开源的初衷是吻合的。3. 软件核心药盒里的“时间引擎”和服药状态机3.1 时间基准RTC的选型决定了“准时”的天花板吃药提醒对时间精度要求不算变态但每天偏差几十秒累积几天就不对劲。DS3231内部集成温补晶振在-40℃到85℃范围内精度±2ppm换算成年误差大约1分钟满足用药提醒完全没问题。DS1302用的是外部32.768kHz晶振晶振质量一般的话一个月可能偏差几分钟需要反复校准。MCU内部RTC也可以直接用但它的低速时钟源通常是有偏差的除非用了外部32.768kHz否则不建议作为药盒的时间基准。我的做法是主时间基准用DS3231MCU通过I2C读取时间和设置闹钟软件里以DS3231的时间为准不对MCU内部RTC做长期依赖。3.2 服药状态机比定时器重要得多的东西只用定时器到点响铃是最低级的逻辑。一台合格的药盒必须能回答三个问题这一格药的计划状态是什么现在处于哪个提醒阶段老人的操作结果是什么我设计的核心状态枚举如下typedef enum { MED_IDLE, // 未到提醒窗口等待 MED_ALERTING, // 正在提醒中等待开盒 MED_TAKEN, // 已按时服用 MED_MISSED, // 超过可服用窗口标记漏服 MED_EARLY_LOCK // 未到时间药仓处于锁定状态 } MedStatus; typedef struct { uint8_t boxId; // 药格编号 uint8_t medicineName[16]; uint8_t dose; // 每次服用粒数 uint8_t planHour; // 计划小时 uint8_t planMin; // 计划分钟 MedStatus status; uint32_t lastActionTick; } MedPlan;每个药格可以配置多个服药时段。系统运行时的关键逻辑是系统时间接近计划时间前5分钟状态机进入MED_ALERTING启动语音提醒和OLED显示提示。提醒持续3分钟期间检测到开盒动作该格子状态置为MED_TAKEN记录实际服用时间。如果提醒结束后45分钟内仍没有开盒动作状态机置为MED_MISSED在屏幕上报漏服并将漏服记录写入Flash。同一个药格在计划时间到来前处于MED_EARLY_LOCK防止老人提前误吃。状态机的核心好处是把“几点做什么事”和“做了什么之后变成什么状态”彻底分离。主循环只需要不断读取RTC和按键、开盒传感器的输入喂给状态机状态机会决定输出。这样代码可读性好后期加“吃药后弹窗确认”“漏服二次提醒”等功能都不用推翻重写。3.3 定时器中断与主循环的合理分工裸机开发时最让人头疼的是定时器中断和主循环抢时间。我的分工方案是使用TIM2产生1ms的时基中断用来做按键消抖计时、OLED刷新节拍、蜂鸣器响铃节奏控制。主循环里按10ms周期扫描一次按键按50ms周期刷新OLED按200ms周期读取DS3231时间并做状态机更新。语音播报等耗时操作放在主循环里执行不在中断里发起避免中断阻塞。1ms中断里只做简单的计数器累加不调用耗时函数。比如void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); g_tick; keyScanTick; oledTick; } }主循环里根据对应的tick值决定是否执行任务。有人疑惑既然有FreeRTOS为什么不用它其实这个项目逻辑并不复杂裸机时间片轮询足够还能避免RTOS带来的任务栈分配和调度不确定性调试成本也更低。如果你后续要加LoRa、蓝牙、触摸屏等复杂外设再换RTOS不迟。3.4 按键、显示、语音的调度逻辑按键流程处理逻辑如下短按菜单键切换显示“当前时间”“今日服药汇总”“最近漏服记录”三个页面。设置状态下按键负责选择药格、设置小时分钟和剂量。长按确认键手动标记“本次已服用”这个设计是给老人的家属或护工用的在极端情况下老人忘记语音播报时可以手动确认。OLED显示采用局部刷新策略只有状态变化时才整屏刷新平时只刷新时间字段和状态图标这样能减少I2C通信量避免屏幕闪烁。语音播报则定制成三段式播报文案“现在是上午八点请服用第2号药盒中的药物用量2片”。播报结束后如果状态没有切换隔5分钟再播报一次最多播报3次避免过度打扰。3.5 掉电保存漏服记录不能一断电就丢漏服记录、上次服用时间、药盒配置信息这些必须掉电保存。STM32F103C8T6内部Flash有64KB可以使用最后几页保存数据。需要特别注意Flash擦写寿命约一万次写入策略要做磨损均衡。我的做法是记录数据采用“追加写”方式每条记录固定长度比如16字节。写入时先读这一页的状态如果满则擦除写入新的记录。由于有固定长度可以记录写入次数来定位最新记录位置。使用CRC8或CRC16校验值读取时先校验校验失败则跳过防止掉电写一半导致记录损坏。更省事的方案是外挂AT24C02/AT24C64 EEPROM但内部Flash不增加BOM成本对于记录量不大的药盒场景已经足够。4. 仿真与实物联调仿真跑通不代表板上能跑4.1 仿真搭出来先把逻辑跑顺再动烙铁我的习惯是先用Proteus把MCU最小系统、DS3231、OLED、按键和蜂鸣器搭好验证核心状态机逻辑再动手画PCB和焊接实物。这样做的好处是纯软件逻辑问题在仿真阶段就能暴露不需要反复烧写硬件。Proteus搭建STM32仿真的要点选择STM32F103C8添加8MHz晶振和复位电路。软件中配置好时钟树确保HSE启动。用“Virtual Terminal”代替串口方便打印调试日志。按键、LED、蜂鸣器等元件从库中拖出连接到指定引脚。仿真过程中我遇到过的坑也列出来晶振不起振Proteus的晶振模型有时把起振时间拖得很长可以在晶振属性里修改Rise/Fall Time或者把Clock Frequency改成8MHz并启用内部时钟模型。DS3231不走时检查I2C地址是不是0x68确认I2C上拉电阻是否连接另外部分Proteus版本中DS3231需要使能I2C调试接口。OLED不显示Proteus里的OLED模型对I2C时序要求严格代码里的延时TimeOut不能太短。仿真能跑通至少验证了RTC读取、状态机、按键扫描、显示输出这些逻辑是对的。但仿真永远替代不了实物接下来才是最花时间的部分。4.2 仿真正常、板子不跑的排查顺序从仿真移植到实物第一次上电很容易出现“仿真明明正常板子就是不跑”的情况。我的排查顺序是先确认供电万用表测3.3V和GND是否正常芯片电源引脚电压是否稳定。确认下载器连接ST-Link识别芯片是否正常如果出现no STM32 target found类提示优先查SWDIO/SWCLK接线、GND连接和BOOT0状态。确认程序烧录成功用STM32 ST-LINK Utility读回芯片ID和Flash内容确认程序真的写进去了。确认时钟起振示波器或逻辑分析仪观察PA8的MCO输出配置为内部时钟输出测频率是否正常没有示波器就点亮一颗LED闪烁验证主循环在跑。逐个外设接入每接入一个外设就编译烧录一次避免多个外设问题叠加排查难度翻倍。4.3 “error: no STM32 target found”的完整排查链路这个报错也许是STM32开发中最常见的连接问题我在做这个项目时也遇到过排查链路如下检查点操作方法常见结果接线SWDIO接PA13、SWCLK接PA14目标板GND和ST-Link共地接线错误会导致无法识别BOOT0引脚应拉低至GND进入Flash启动BOOT0高时芯片不执行用户程序供电目标板是否有独立供电ST-Link的3.3V输出可能电流不够板子供电不足时连接不稳定驱动与软件ST-Link驱动正确安装Keil/STM32CubeProgrammer选择对的调试器驱动问题报错换USB口后回复复位电路NRST是否被异常拉低复位引脚一直被拉低连接失败还有一个容易被忽略的点使用国产ST-Link或CMSIS-DAP时如果固件版本过旧Keil会拒绝连接这时需要先用ST-Link Utility或STM32CubeProgrammer更新固件再回到Keil里调试。5. 实测翻车记录我在整机点亮过程中踩到的坑5.1 OLED屏幕白屏急了一晚上第一次把OLED接到板子上通电后屏幕全白什么内容都没有。当时怀疑是I2C地址错误换地址无果后来用万用表量SDA/SCL电压发现SCL拉不上去只有0.8V左右。原因是OLED模块和MCU共享I2C总线时模块上的上拉电阻和MCU内部上拉同时存在出现上拉过度或者更常见的情况是模块的SDA/SCL之间短路焊点搭锡。处理方法是断开SDA/SCL单独量模块上的电压再检查模块排针附近的焊盘。重新焊接后恢复正常。OLED调试时强烈建议先用现成的I2C扫描程序确认设备地址而不要直接跑显示代码。5.2 语音模块“滋啦滋啦”听不清人声语音模块的问题出在电源上。SYN6288和功放模块工作瞬间电流可以达到几百毫安如果和MCU共用一个LDO当功放推动喇叭时3.3V电压被拉低MCU和语音模块一起异常听起来就是刺耳的杂音。解决办法是给语音模块单独供电而不是从MCU的3.3V网络取电。我用了一个独立的AMS1117-3.3给语音模块供电输入端的滤波电容加大到470uF同时喇叭地和信号地采用单点接地问题立即消失。如果板子空间允许最好用DC-DC电源模块效率更高发热更小。5.3 按键误触发现象极其频繁按下“确认”键时屏幕乱跳状态莫名切换。用示波器看按键的波形发现按下和松开时都有明显的机械抖动持续时间大约10到20ms。软件消抖和硬件消抖我都做了硬件上每个按键并联一个100nF电容形成一个简单的RC低通滤波能把大部分高频抖动滤掉。软件上采用“连续采样20ms一致才认为有效”的方式uint8_t Key_Scan(void) { static uint8_t keyState KEY_RELEASE; static uint8_t confirmCnt 0; if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) GPIO_PIN_RESET) { confirmCnt; if (confirmCnt 20) { keyState KEY_PRESS; } } else { confirmCnt 0; keyState KEY_RELEASE; } return keyState; }20ms的采样窗口由TIM2时基控制每毫秒采样一次连续20次都读到低电平才判定有效。这个方案成本很低也足够稳定。5.4 电池供电时屏幕闪烁、语音断续整机用USB供电一切正常一旦改用4节AA电池屏幕开始闪烁语音播报也断断续续。原因在于电池内阻大语音播报瞬间拉高电流时电压跌落严重。处理办法是加大电源输入端的储能电容并在电池正极和3.3V LDO之间串一个低压降肖特基二极管。锂电池方案下建议用TP4056充电模块加18650电池电池输出端加一个大电容效果比碱性电池好很多。6. 开源工程怎么组织后续能往哪些方向扩展6.1 工程目录与核心文件说明开源时我把工程组织成如下结构health_box/ ├── firmware/ │ ├── Core/ │ │ ├── Inc/ │ │ ├── Src/ │ │ │ ├── main.c │ │ │ ├── tim.c │ │ │ ├── i2c.c │ │ │ └── usart.c │ ├── Drivers/ │ │ ├── BSP/ │ │ │ ├── ds3231.c │ │ │ ├── oled.c │ │ │ ├── syn6288.c │ │ │ └── key.c │ │ └── App/ │ │ ├── med_state_machine.c │ │ └── ui.c │ ├── MDK-ARM/ │ └── README.md ├── hardware/ │ ├── smart_medicine_box_sch.pdf │ ├── smart_medicine_box_pcb.png │ └── BOM.md └── simulation/ ├── smart_medicine_box.pdsprj └── readme.txtmed_state_machine.c是整个项目的核心直接体现药盒逻辑ds3231.c和oled.c是相对独立的外设驱动仿真工程目录里放了Proteus工程和说明文档。README里我会写清楚开发环境为Keil MDK 5 STM32CubeMX生成的HAL库工程版本号对应。开源代码的License建议用MIT或者GPLv3并注明“非医疗设备不能替代专业医嘱”。虽然是老生常谈但这个声明对做健康和医疗相关项目尤其重要。6.2 编译烧录与首次使用流程拿到代码后编译烧录步骤如下安装Keil MDK5安装STM32F1系列器件包。打开MDK-ARM工程在Options里选择ST-Link Debugger。编译工程确认0 Error。连接ST-Link与目标板点击Download烧录。打开串口调试助手波特率115200观察初始化日志。首次上电后OLED会显示当前时间若时间不对通过按键进入设置模式校准。如果不能连接芯片先回到第4章的排查表确保硬件连接和驱动没有问题。6.3 想把项目继续做大的几个方向当前这套方案已经可以作为一个独立药盒使用。后续扩展有几个很不错的方向远程通知增加ESP8266模块通过MQTT把漏服记录推送到家属手机实现“老人没吃药家人手机响”。这种方案成本低、开发资料丰富适合第二步扩展。扫码服药在药盒上增加二维码扫描头服药前扫码确认系统记录更精确的服药数据适合护理机构。多盒联动通过RS485或者LoRa把多个药盒组网在养老院场景中做统一管理。语音交互接入语音识别模块老人直接说“我吃过药了”系统自动更新状态减少按键交互负担。还有一个很多人都会忽略的点药盒的机械结构部分尤其是药仓盖的阻尼和出口设计直接影响开盒检测的可靠性。电路软件做得再好如果仓盖卡滞导致微动开关误触发一切白搭。建议用3D打印样品做装配验证再考虑正式外壳开模。最后分享一个我调试这套系统时最得意的小技巧把DS3231的闹钟中断直接接到MCU的PA0引脚并配置成外部中断平时整个系统进入睡眠模式只有到了设定时间或者有人按键时才被唤醒。这样一来同样是4节AA电池供电待机时间从一个星期撑到了一个月以上。对需要长期放在老人家里的设备来说这个改动比换一块大电池有效得多。做这类嵌入式健康设备不能只追求功能跑通功耗、稳定性和操作容错性才是真正决定产品能不能长期用下去的关键。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

从Copilot到自主编程Agent:AI编程助手演进之路 2026/9/8 14:57:59

从Copilot到自主编程Agent:AI编程助手演进之路

2. 项目概述2.1 项目背景与问题定义2022年之前,绝大多数开发者对“AI写代码”的认知还停留在自动补全变量名、生成重复性样板代码的阶段。直到GitHub Copilot正式发布,整个行业才意识到:大语言模型驱动的编程助手,已经能把“读代码…

阅读更多 →
C++装饰器模式变体详解:经典继承、CRTP、函数式与宏实现 2026/9/8 14:57:59

C++装饰器模式变体详解:经典继承、CRTP、函数式与宏实现

1. 为什么我重新审视了装饰器模式 装饰器模式,说实话,在我刚接触C的头两年里,一直觉得这东西有点鸡肋。学过设计模式的人都知道它的标准定义:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式…

阅读更多 →
英语渣的救星!这样背单词真的会上瘾[特殊字符] 2026/9/8 14:57:59

英语渣的救星!这样背单词真的会上瘾[特殊字符]

姐妹们!谁懂啊!! 我真的是那种学了十几年英语、一开口还是“How are you?I‘m fine,thank you”的人😅 单词书买了好几本,永远停在abandon。 直到闺蜜给我安利了这个“英语学习手册”&#x…

阅读更多 →
Python零基础合辑④:Python变量(让电脑记住信息) 2026/9/8 14:57:59

Python零基础合辑④:Python变量(让电脑记住信息)

大家好~ 上一篇合辑接上了, (第③篇教大家用print语句写出了第一个程序), 在今天, 咱们要学习一个知识点, 它超基础, 并且又超重要, 这个知识点就是——变量!认为“变量”听起来繁杂的众多新手, 实际上并非难事, 今日以极为通俗易…

阅读更多 →
从芯片级到MW级:汽车电子全栈测试方案解析 2026/9/8 14:57:59

从芯片级到MW级:汽车电子全栈测试方案解析

连续三天站在Automotive Testing Expo 2026的展台附近,我感受最深的不是某台仪器参数有多亮眼,而是测试需求正在从“单点验证”向“全链覆盖”快速迁移。ITECH艾德克斯这次直接亮出从芯片级精度到MW级动力的全栈汽车电子测试方案,把车规芯片、…

阅读更多 →
DMA实战指南:从串口接收到PWM输出,一文搞定配置与避坑 2026/9/8 14:54:58

DMA实战指南:从串口接收到PWM输出,一文搞定配置与避坑

DMA这名字听起来像是什么高深莫测的硬件黑魔法,但说白了,它就是一块专门帮你搬数据的“肌肉劳工”。干嵌入式这行越久,越会发现一个真理:CPU最宝贵的是算力,而不是做搬运工。把内存、外设之间的数据挪动交给DMA去办&am…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞