STM32智能药盒设计与实现:从原理图到Proteus仿真全攻略

发布时间:2026/9/8 12:21:06
STM32智能药盒设计与实现:从原理图到Proteus仿真全攻略 家里老人一天要吃的药经常比年轻人点的外卖还复杂降压药早饭后一片降糖药随餐服用阿司匹林临睡前一颗……记错、漏吃、甚至重复吃哪一条都是隐患。我做的这套STM32智能药盒就是奔着解决这个问题去的。整块板子用STM32F103C8T6做主控配合RTC时钟模块、OLED显示屏、药仓状态检测和蜂鸣器提醒到点自动提示老人吃哪一格药取药之后自动记录时间家人回家翻开记录就知道今天有没有漏服。项目代码、原理图、Proteus仿真工程全部开源从零复现大概两三天就能跑起来特别适合电子相关专业做课程设计、参加竞赛或者单纯想给家里老人亲手做点东西的嵌入式爱好者。很多人第一反应是“买个智能药盒不就行了”但市面上的成品药盒主要分两类一类只有物理分格压根没有提醒功能另一类带语音和联网价格动辄三四百操作界面对老人极不友好。自己用STM32做硬件成本大概五六十元功能还能按家里老人的习惯定制——提醒铃声大小、药仓数量、服药时间表都可以改。更关键的是整个项目从原理图到代码全部开源每一步都能看懂、能修改这才是自己做东西最大的价值。下面我把这套系统的设计过程、原理图细节、代码核心逻辑和Proteus仿真经验从头到尾拆一遍希望能帮你少走弯路。1. 项目整体设计从老人用药痛点倒推硬件选型1.1 老人用药管理到底难在哪先别急着画原理图把需求想清楚再做后面能省不少事。老人漏服药的场景我观察下来主要分为三种第一种是记不住时间饭后和空腹的药物服用时间不一样干脆就忘了第二种是记不住次数药盒里用剩下的药片数一数完全对不上第三种是重复服用吃完一次后过半小时又吃了一次这是最危险的。所以智能药盒要解决的核心问题不是“把药装起来”而是“让老人在正确的时间、吃正确的药并且把服药行为留下来”。这就推导出功能重点第一必须有一个准确的时间基准不能隔几天就偏几分钟第二提醒方式要足够明显蜂鸣器加LED闪烁是最简单可靠的组合第三要有“是否真的取药”的检测手段不能只靠老人按一下按键说“吃过了”第四要把每一次服药记录保存起来方便家人查看。后面所有的硬件选型和软件设计都是围绕这四点展开的。1.2 功能清单与系统架构这套智能药盒的完整功能可以拆成几个模块梳理清楚了再动手会顺畅很多。主控采用STM32F103C8T6负责所有逻辑处理RTC模块用DS3231保证时间长期准确显示部分用0.96寸OLED实时显示当前时间和下一组服药倒计时输入用独立按键支持设置时间和调节参数提醒输出使用有源蜂鸣器和双色LED药仓状态用光电开关或红外对管检测服药记录存储在AT24C02 EEPROM里掉电不丢失。整个系统架构其实很简单属于典型的单主控外设组合没有复杂的通信协议。STM32通过I2C总线挂载RTC和OLED通过GPIO读取按键和药仓检测信号通过GPIO控制蜂鸣器和LED。Proteus仿真工程里也按同样的结构搭建烧写HEX文件后就可以联调。这样的架构对新手非常友好代码逻辑清晰后期加WiFi模块或者语音模块也有足够的IO口预留空间。1.3 核心器件选型对比不是越贵越好而是越合适越好主控选STM32F103C8T6的原因很简单便宜、资料多、稳定。蓝板子核心板十块钱以内能拿下Flash 64KB、RAM 20KB跑一个不带RTOS的裸机程序绰绰有余。更重要的是Proteus直接内置这颗芯片的仿真模型课设和竞赛演示时用仿真图也能交代得过去。RTC部分我在DS3231和DS1302之间对比过最终选了DS3231。虽然DS1302便宜到只要两三块钱但它需要外部32.768kHz晶振精度受温度和PCB布局影响很大即使出厂校准过用一段时间也会漂。DS3231内置温度补偿晶振I2C接口直连STM32精度能做到正负2ppm对用药提醒这种对时间敏感的场景来说多花几块钱买稳定是值得的。如果你做的是预算极简版用DS1302也能跑起来但代码里要处理BCD码转换和晶振电容补偿整体工作量会大不少。功能模块器件选型作用主控STM32F103C8T6逻辑控制、计时运算、外设驱动时钟DS3231提供走时精度高的系统时间显示0.96寸 OLEDSSD1306显示时间、日期、服药信息和倒计时输入独立按键 x4设置时间、切换界面、确认操作提醒有源蜂鸣器 双色LED声音和光双重提醒药仓检测光电开关 / 红外对管判断药片是否被取走记录存储AT24C02 EEPROM保存服药记录和设置参数电源5V USB AMS1117-3.3为系统提供稳定的3.3V电源OLED选I2C接口版本而不是SPI版本是因为I2C只需要两根信号线接线简单代码驱动也成熟。蜂鸣器选有源版本直接IO口给高电平就响不需要PWM省掉一个定时器资源。药仓检测没奢望做到重量传感器那种精度红外对管加比较器就够用取放药片这种开合事件检测完全能稳定判别。2. 硬件原理图设计要点每一路都讲得出理由2.1 STM32最小系统与电源这是最容易翻车的地方STM32F103C8T6的最小系统看似简单但很多第一次画板子的人会在细节上栽跟头。8MHz外部晶振是必备的虽然芯片内部有HSI但USB、高精度计时和后续串口通信都依赖外部晶振提供稳定的时钟源。晶振的两个引脚各接一个20pF负载电容到地走线尽量短而且要远离其他信号线。复位电路用10K电阻上拉到3.3V再接一个100nF电容到地这样上电复位和手动复位都能兼顾。BOOT0引脚必须用10K电阻下拉到地否则上电后芯片可能进入ISP模式程序烧不进去。电源部分很多人喜欢直接拿模块板上的AMS1117方案抄但我会多说一句AMS1117-3.3输入5V输出3.3V压差1.7V确实没问题但它不是低压差器件在输出电流较大时自身发热明显。药盒整板电流一般不超过200mA问题不大但尽量不要用劣质USB线供电线阻太大会导致5V到板端只剩4.5VAMS1117输出会掉到3.0V以下芯片直接复位或者跑飞。实际画板时我在AMS1117输入端和输出端各放了一个10uF钽电容和一个100nF陶瓷电容有效改善了纹波。每个VDD引脚旁边要放一个100nF去耦电容这个细节特别容易被忽略。STM32F103C8T6有多个VDD引脚每一个都要就近放电容不要图省事只在其中一个引脚放。去耦电容的作用是为芯片提供高频瞬态电流放置位置比电容本身质量更重要。3.3V主干线上再放一个10uF电容对整个电源网络做个低频缓冲实测下来这组配置稳定性很高。2.2 RTC时钟模块DS3231的接线和I2C上拉DS3231是I2C接口地址固定为0x68接STM32的PB6I2C1_SCL和PB7I2C1_SDA。I2C总线上必须加上拉电阻我用的是4.7K一端接3.3V另一端分别接SCL和SDA。要注意DS3231是3.3V逻辑器件如果STM32板子和DS3231模块都用3.3V供电直接连没问题但如果你手头只有5V供电的DS1302或DS1307模块务必加电平转换或者确认模块上是否已经带电平转换电路直接接5V容易把STM32的IO口打坏。DS3231模块上通常已经带了CR2032电池座这个电池的作用是给RTC芯片内部供电保证外部主控断电后时间继续走。接线时还要注意模块的SQW引脚方波输出如果悬空没问题但如果模块出厂设置成使能状态它可能会持续输出1Hz方波干扰I2C信号。稳妥的做法是在RTC初始化代码里关闭SQW输出或者干脆不接这个引脚。原理图里还有一个容易踩的坑I2C上拉电阻必须接到正确的电源域。如果OLED模块和DS3231模块都挂在同一个I2C总线上它们的SCL/SDA要分别加一个上拉电阻不要只加一处。因为不同模块的I2C引脚漏电流不同上拉太弱会导致边沿变缓通信偶尔出错。我实测在5cm排线和长跳线场景下4.7K上拉稳定换成10K就容易出现OLED花屏或者RTC读取失败。2.3 药仓检测与提醒驱动电路设计药仓检测的电路方案我纠结过一段时间最省事的办法是买现成的光电开关模块输出数字高低电平直接接STM32的GPIO。自己做的话用红外对管加LM393比较器成本更低但要多画一级电路。如果药盒每个格子装一个检测点用LM393比较器比较接收管电压和参考电压输出接到STM32的PA0-PA3取药后输出电平翻转代码里读GPIO就能判断。比较器的参考电压可以用可调电阻设置但我建议在原理图中直接用两个固定电阻分压省去调试时反复拧电位器的麻烦。红外发射管和接收管的摆放位置要注意发射管的驱动电流一般10mA左右5V供电时串一个330欧限流电阻接收管接上拉电阻到3.3V输出给比较器。检测时如果药片挡住了光路接收管电平变化比较器输出翻转。这个方案在Proteus里没法完全模拟光路所以仿真时我用一个按键开关模拟药仓检测信号实物调试再换回红外对管。蜂鸣器驱动我用了S8050三极管。STM32的IO口驱动能力有限直接推有源蜂鸣器虽然能响但电压会被拉低声音也小。用三极管放大电流后蜂鸣器接在5V和集电极之间发射极接地基极串1K电阻由IO口控制。注意有源蜂鸣器内部没有续流二极管断电瞬间会产生反向电动势最好在蜂鸣器两端反向并联一个1N4148。LED指示灯用推挽输出加330欧限流电阻做一个红色一个绿色分别表示电源状态和提醒状态这个成本很低但很实用。2.4 原理图绘制与PCB布局避坑原理图绘制我常用立创EDA元件库全还能直接联动PCB下单。绘制时有两件事必须做一是给每个药仓检测接口、按键排针、电源接口添加网络标签方便后续查线二是把晶振、去耦电容、蜂鸣器这些关键器件在原理图中标注重点防止PCB阶段忽略布局问题。PCB布局上STM32尽量放板子中央晶振靠芯片引脚放置晶振下面不要走任何信号线最好铺一块完整的地铜。AMS1117放在板边方便散热输入输出电容不要离它太远。蜂鸣器的驱动三极管靠近蜂鸣器座避免长走线把电流信号衰减掉。I2C上拉电阻放在主控一侧因为I2C信号是漏极开路上拉电阻靠近主控芯片能减少总线噪声。药仓检测插座、OLED排针、按键都放在板子边缘方便装配和更换。很多人画完原理图就直接投板我建议先在Proteus里把仿真跑通再画PCB。仿真能验证逻辑正确性但仿真跑通不代表硬件没问题——比如DS3231的上拉电压、蜂鸣器三极管的基极电阻阻值、红外对管的安装方向这些在仿真里都看不出来。我的习惯是原理图完成后再花一晚上做PCB布局检查把每路电源、每个元器件的数据手册关键参数对照一遍。3. 软件设计与代码实现代码不是堆功能而是搭状态机3.1 程序架构时间片轮询配合状态机这套系统的软件并发任务不多我用了裸机时间片轮询没有上RTOS。核心思路是用SysTick定时器做1ms基准在中断里把按键扫描、OLED刷新、RTC读取、提醒检测这些任务按不同周期调度。这样做的好处是代码简单可控每个任务都在主循环里执行不会有RTOS的优先级抢断和资源竞争问题。状态机是我做这个项目的重点。整个药盒可以抽象成几个状态等待状态、提醒状态、取药确认状态、逾期状态、参数设置状态。系统上电后处于等待状态每秒读取一次当前时间与预设的服药时间表比较到达提醒时间后进入提醒状态蜂鸣器间歇鸣叫、LED闪烁、OLED切到提醒界面检测到对应药仓有取药动作后进入取药确认状态记录当前时间到EEPROM蜂鸣器停止如果超过规定时间没有取药则进入逾期状态蜂鸣器改为每隔一分钟短鸣一次OLED显示“漏服”。程序文件我按功能模块拆分包括主循环、RTC驱动、OLED驱动、按键驱动、蜂鸣器控制、药盒逻辑、EEPROM存储。这样每个文件都很短后期维护或加功能只需要动对应模块。很多初学者喜欢把所有代码堆在main.c里跑起来没问题但后面想加一个功能就头大我的建议是模块化拆开写哪怕多几个文件也好过几千行挤在一起。3.2 RTC驱动与服药时间管理DS3231的驱动最关键的是寄存器读写顺序。芯片默认返回BCD格式的时分秒代码里要么先转成十进制再参与比较要么直接用BCD比较。我习惯把时间统一转成十进制因为需要做分钟数的加减运算用BCD做加减法太绕了。起始代码里设置好时间之后主循环每秒读一次RTC更新一个全局结构体变量。服药时间表用结构体数组保存每组包括小时、分钟、药品编号、是否启用四个字段。这样最多可以配置比如早8点吃1号药、中午12点吃2号药、晚9点吃3号药。比较时间时把当前时间转成“从零点开始的总分钟数”服药时间表也转成同样的单位两者相等就触发提醒。typedef struct { uint8_t hour; uint8_t min; uint8_t medIndex; // 对应第几个药仓 uint8_t enabled; // 该组提醒是否启用 } MedAlarm_t; MedAlarm_t alarmTable[8]; uint16_t TimeToMinute(uint8_t hour, uint8_t min) { return (uint16_t)hour * 60 min; } void CheckAlarm(RTC_Time_t *now) { uint16_t nowMin TimeToMinute(now-hour, now-minute); for (uint8_t i 0; i ALARM_NUM; i) { if (!alarmTable[i].enabled) continue; uint16_t alarmMin TimeToMinute(alarmTable[i].hour, alarmTable[i].min); if (nowMin alarmMin !alarmTriggered[i]) { StartAlarm(alarmTable[i].medIndex); alarmTriggered[i] 1; } } }这里有个细节状态转移时不仅要考虑“到达提醒时间”还要处理“当天已经提醒过”这个标志位。我在代码里用一个数组保存每组闹钟当天的触发状态每次日期变化时清零避免睡一觉起来系统判断时间相同又响一次。还有一个更稳妥的方案是把触发状态直接存EEPROM系统断电重启后不会重复提醒我这个版本为了简化没有做只做了RAM标志。3.3 按键扫描、消抖与OLED界面按键模块用了4个独立按键分别承担设置、加、减、确认功能。IO口配置为上拉输入按键另一端接GND按下时读到低电平。这里有一个很容易范的错STM32的GPIO内部上拉电阻只有约40K长线连接时抗干扰能力差在按键两端并联一个100pF电容能有效滤除高频噪声。程序里我用定时器每10ms扫描一次按键连续两次读到同一电平才确认状态变化这就是最基本的消抖逻辑。uint8_t Key_Scan(GPIO_TypeDef *port, uint16_t pin) { static uint8_t stable_level 1; static uint8_t debounce_cnt 0; uint8_t level GPIO_ReadInputDataBit(port, pin); if (level stable_level) { debounce_cnt 0; return 0; } if (debounce_cnt 2) { stable_level level; debounce_cnt 0; if (level 0) return 1; // 按下事件 } return 0; }这段代码注意不要在按键长时间按住时频繁返回按下事件因为stable_level变化后只有再次变化才会进else分支所以长按只触发一次按下事件。如果你需要“长按连续加时间”要单独设计计数逻辑。OLED显示采用SSD1306驱动芯片I2C接口。我要提醒一个坑SSD1306的I2C地址可能是0x3C也可能是0x3D取决于模块背面地址电阻。买模块时看清楚标注代码里地址就要对应改。我的显示界面分三层主界面显示日期、时间、下一组服药倒计时和药仓状态设置界面通过按键调节当前时间提醒界面全屏显示“请服用第X格药”。界面切换用一个简单的currentPage变量控制逻辑非常直接。3.4 服药提醒与漏服检测逻辑提醒逻辑是整个系统最核心的部分也是仿真时最容易看到效果的部分。当检测到当前时间等于某组闹钟时间后系统进入提醒状态蜂鸣器按照“响一秒停两秒”的节奏鸣叫绿色LED熄灭红色LED闪烁。OLED显示提醒界面提示老人目前需要服用哪一个药仓的药。药仓检测部分我循环读取各药仓的光电开关电平。以低电平表示药仓有药为例取药后变为高电平。程序检测到对应药仓从有药变为无药就认为老人完成了取药动作此时把当前RTC时间写进EEPROM记录区然后退出提醒状态。如果超过30分钟仍然检测到未取药系统进入逾期状态蜂鸣器改为每60秒短鸣一次OLED显示“漏服第X格药”提醒强度降低避免半夜频繁打扰。记录存储用AT24C02把服药记录组织成固定长度的结构体数组循环写入。EEPROM有10万次擦写寿命药盒一天记录三五条用几十年没问题。这里要注意EEPROM写一个字节大概要5ms不能在提醒过程中频繁写入我的方案是只在状态机进入“取药确认状态”时写一次写完立即切换到主界面。读取记录时通过按键进入记录浏览界面OLED上每次显示一条上下翻页查看家人能随时确认老人是否按时服药。4. Proteus仿真没有实物板子也能先把逻辑跑通4.1 搭建Proteus仿真工程元件选择与HEX加载如果你还没入手实物先用Proteus把逻辑验证一遍是最省时间的方式。建议使用Proteus 8.9以上版本打开新工程后从元件库搜索STM32F103C8T6、DS1307或DS3231模型、SSD1306 OLED、按键、LED、有源蜂鸣器、LM393和电阻电容。有一点要提前说明Proteus元件库里不一定有DS3231但一般有DS1307两者I2C寄存器地址和接口类似仿真时可以直接用DS1307替代只是精度上的差异在仿真里体现不出来。STM32F103C8T6模型放好后双击它在Program File路径里选择Keil编译生成的HEX文件。如果Keil的Output选项里没有勾选Create HEX File就不会生成这个文件需要先到Options for Target的Output选项卡里勾上。还要在模型的Clock Frequency里填8MHz与原理图上晶振保持一致否则程序里依赖时间的延时和定时器全部不准。搭建时把OLED、RTC、蜂鸣器、按键、LED与STM32对应引脚相连注意加上拉电阻。OLED模块在Proteus里也是I2C接口需要把SCL、SDA分别连到PB6、PB7并且加4.7K上拉。RTC模块同样挂在I2C总线上。按键一端接GPIO另一端接GNDGPIO配置成上拉输入。LED和蜂鸣器驱动的三极管电路在Proteus里可以直接用逻辑状态代替但为了接近实物我还是把S8050模型放进了仿真图这样能同时验证三极管驱动的连接关系。4.2 仿真调试技巧让RTC快速跑到提醒时间Proteus仿真跑RTC最大的问题是时间太慢你要是想在仿真里等RTC从当前时间走到预设闹钟那得等到天荒地老。我常用的技巧是把DS1307的初始时间直接设置成提醒时间前一分钟这样跑几十秒就能看到整个提醒和取药流程。代码里用一个InitTime变量仿真前把RTC初始时间写死成比如7:59闹钟设置成8:00这样一运行一分钟就会触发效率高很多。如果系统已经跑起来了但RTC不走多半是I2C通信问题。这时候可以用Proteus自带的I2C Debugger挂在总线上观察时序。常见问题是上拉电阻没加、I2C地址不对、总线上设备地址冲突。OLED和RTC挂在同一条总线上如果OLED显示正常但RTC读不到优先检查RTC地址DS1307一般默认地址是0x68跟DS3231一样。仿真的另一个坑是蜂鸣器不会真的发声只能用示波器或逻辑探针看电平变化。我会在蜂鸣器驱动引脚加一个信号探针观察提醒状态下引脚是否输出周期性的高低电平。OLED上的文字信息是仿真的直接反馈如果显示屏显示了“请服用第1格药”说明逻辑走到了提醒状态后面再验证取药检测按键触发后的状态切换就行了。4.3 仿真和实物之间的差距心里要有数仿真能帮你验证90%的软件逻辑但剩下10%的硬件问题只有实物会暴露。比如Proteus里不可能模拟红外光路被药片遮挡的过程所以药仓检测我用按键模拟Proteus里也不会出现杜邦线接触不良、电源纹波导致的芯片跑飞。我见过很多同学仿真跑通了焊完实物却一点反应都没有最后发现是晶振没焊好或者BOOT0悬空这类问题在原理图里画得再对实物焊接时也会出纰漏。建议的做法是仿真阶段验证逻辑实物调试时分模块上电。先只焊最小系统烧LED流水灯程序确认芯片能跑再焊OLED确认I2C通信正常接着接RTC确认时间能走最后再把蜂鸣器和药仓检测接进来。每一步都有对应验证点出问题能快速定位而不是等整板焊完才通电到时候两眼一抹黑。5. 常见问题与排查实录都是实际踩过的坑5.1 下载程序报错No STM32 Target Found怎么处理这个问题在第一次给新板子烧录时出现频率极高报错信息类似error: no stm32 target found! if your product embeds debug authentication, please ...。很多人看到这行英文就慌其实核心就一句话调试器没有和芯片建立连接。排查顺序我建议按下面来第一检查ST-Link和目标板的接线是否一一对应。SWDIO连SWDIO、SWCLK连SWCLK、GND连GND、3.3V连3.3V杜邦线尽量短超过20cm就容易通信不稳定。第二确认Keil的Debug选项里选择的是ST-Link Debugger不是ULINK或J-LINK然后点Settings看是否能检测到IDCODE。第三检查目标板是否真的上电了如果STM32板子没有独立供电ST-Link的3.3V又带不动整板也会连接失败。第四检查BOOT0是否被拉高如果BOOT0为高电平芯片上电后进入ISP模式而不是正常运行模式SWD连接可能会失败把它恢复为低电平再试。如果以上都没问题还有一种情况是芯片之前烧录过程序中把SWD引脚给复用成普通GPIO了导致调试器连不上。处理方法是在STM32CubeProgrammer里选择Connect Under Reset模式或者把BOOT0拉高进入ISP模式先擦除整个Flash再回到正常模式烧录。这个问题我在项目开发中至少遇到三次第一次折腾了一晚上后来学会了新板子拿到手先烧个点灯程序验证SWD可用再做其他功能。5.2 RTC时间走不准别急着怪芯片DS3231正常情况下精度很高年误差也就一两分钟。如果你发现时间走得明显偏快或偏慢大概率问题出在代码上。最常见的是BCD码转换错误比如读取到的秒寄存器值是0x35如果直接用这个值当十进制35来用实际上它表示的是BCD的35也就是十进制53。所有RTC的时分秒寄存器输出都是BCD格式一定要用代码转换或者用库函数自带的转换函数。另一个常见坑是I2C读取时序不当。DS1307和DS3231在读取秒寄存器时如果刚好赶上秒值进位可能读到的是当前秒和下一秒的混合值。解决办法是连续读两次如果两次结果一致再采用。再有就是电池问题DS3231接的CR2032如果电量不足或者电池接触不良停电后重启时间就会恢复到默认值老感觉“时间不对”还以为是晶振跑偏。检查方法很简单拔掉主电源等一分钟再上电看RTC时间是否还在走。5.3 OLED花屏、按键误触发、蜂鸣器不响OLED花屏绝大多数情况是I2C通信被干扰。排查时先用示波器或逻辑分析仪看SDA和SCL波形如果边沿很缓多半是上拉电阻阻值太大或者接线过长。我用4.7K上拉稳定换成10K在长排线场景下就出现过花屏。还有一种情况是OLED模块本身的地址引脚焊错了模块上0x3C和0x3D的选择电阻不对代码里读不到响应的I2C设备屏幕上只显示雪花点。按键误触发通常是硬件去抖不够。程序里已经做了软件消抖但物理上如果按键没有并联电容在静电或电磁干扰强的环境还是会跳。我的建议是按键两端并联100nF电容同时程序里消抖时间加长到20ms以上。蜂鸣器不响的问题大部分出在三极管驱动基极电阻太大导致IB太小三极管进入不了饱和区或者蜂鸣器正负极接反了。有源蜂鸣器正极一般标号负极接三极管集电极串一个万用表测电流一般正常工作时20mA左右如果电流只有几毫安声音会很小甚至不响。5.4 仿真工程跑不起来的几个原因Proteus里烧了HEX文件但STM32完全不运行先检查是否生成了正确的HEX。Keil里默认不生成HEX必须在Options for Target的Output选项卡勾选Create HEX File重新编译后去项目目录里确认有.hex后缀的文件。第二检查MCU模型的时钟频率设置如果代码里延时函数基于72MHz计算但Proteus模型默认8MHz跑起来所有时间都会慢9倍。第三确认RTC模型是否有有效时间值DS1307在Proteus里需要预先设置初始时间否则读到的时间是随机值闹钟比较逻辑永远不会触发。还有一个小问题Proteus仿真时按键悬空电平不确定。如果你把GPIO配置成内部上拉输入但按键另一端接GND在仿真里是没问题的但如果你用矩阵键盘或者外部上拉一定要在仿真图里放上拉电阻不能光靠代码里的内部上拉因为Proteus模型对上拉输入的处理和实物有细微差别。遇到程序没反应时先点仿真下方的暂停按钮再看程序跑到了哪一行逐步定位。最后再分享一点实际心得这个项目我从画原理图到仿真调试再到焊实物板子前前后后改了三版。最有感触的一点是智能药盒看起来是个简单的单片机项目但真正做下来涉及的知识点非常综合RTC驱动、I2C通信、按键消抖、状态机设计、EEPROM存储几乎把STM32的常用外设都过了一遍。对新手来说把这套代码吃透比单纯背一个开发板例程有意义得多因为它是从一个真实需求出发有完整的产品逻辑在里面。做实物的时候我还有一个个人习惯药盒外壳用3D打印每个药仓的格子尺寸按家里药瓶大小设计光电开关开孔位置也提前预留。软件和硬件都完成后再做外壳边装配边调整能避免很多尺寸对不上的尴尬。如果后续你打算继续扩展可以考虑加一个ESP8266模块把服药记录上传到自己的服务器或手机这样老人在家按时吃药家人在外面也能实时掌握情况。这套开源的代码和原理图只是一个起点真正有意思的地方在于你根据自家老人的习惯去改它。