ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32智能药盒开源:原理图、Keil工程与Proteus仿真全解析

STM32智能药盒开源:原理图、Keil工程与Proteus仿真全解析 做嵌入式这几年我见过不少开源项目但真正能把硬件电路、软件代码、仿真联调三块都摊开讲清楚的并不多。这次我把一整套 STM32 项目开源了智能药盒也就是老人用药管理系统带上位机交互逻辑、完整原理图、Keil 工程和 Proteus 仿真全部可以复现。这篇文章不只是丢个下载链接我会把从需求拆分到电路设计、代码架构、仿真调试的完整思路写一遍你拿过去可以直接当教程跑也可以根据自己的场景改。这套系统的核心任务很明确帮老人按时吃药。它不是简单定个闹钟而是真正围绕“提醒—确认—记录”设计的一整套闭环。适合正在做课程设计、毕业设计、电子竞赛题目的学生也适合想给家里老人做个实用小设备的嵌入式爱好者。如果你手里有块 STM32F103C8T6 最小系统板再加几块钱的外设模块跟着这篇文章的思路一两天就能把整个原型搭出来。1. 项目概述与整体设计思路1.1 项目背景与核心需求拆解老人用药管理最大的问题不是“不知道吃什么药”而是“忘了吃”和“重复吃”。尤其是有高血压、糖尿病这类慢性病的老人一天要分早中晚吃好几种药时间点还不一样。普通闹钟只能响一声响完老人转头就忘了。真正需要的是一台能定时提醒、能判断“药到底吃了没有”、还能让家人远程放心的设备。把这个需求翻译成嵌入式系统的功能点其实就这几条定时提醒在预设的用药时间点触发声音和灯光提醒不能错过分时分区一个药盒要分成多个格子不同时段对应不同格子避免吃错取药确认检测到药盒被打开/药被取走自动停止提醒状态显示屏幕上能看到当前时间、今天的用药计划、上次吃药时间交互设置家人或老人自己能设置闹钟时间和用药格子扩展能力预留通信接口后续可以接 WiFi 模块给子女发消息。拆完需求你会发现这套东西本质上就是一个“定时任务系统 状态机 人机交互”的组合难度不大但每个环节都要耦合好控制逻辑。这也是我选 STM32F103C8T6 的原因性能完全够用资料多市面上任何一块最小系统板都能跑学习成本低。1.2 系统架构与方案选型整个系统按功能模块可以切成五块主控、交互输入、信息显示、执行提醒、状态感知。我的选型方案如下带着选型理由一起列出来。模块选型理由主控STM32F103C8T672MHz主频20KB RAM64KB FlashI2C/SPI/UART/PWM齐全性价比高实时时钟芯片内部RTC 备用电池成本低不依赖外部RTC芯片项目初期够用显示0.96寸 OLED SSD1306I2C两线驱动功耗低显示信息密度够高声音提醒有源蜂鸣器 5V驱动简单一个三极管即可声音响亮取药检测干簧管 小磁铁比光电对管便宜稳定贴在药盒开合处就行出药执行SG90 舵机便宜、控制简单用PWM转角度驱动旋转药盘交互3个独立按键菜单切换、选择、确认操作逻辑简单电源USB 5V AMS1117-3.3通用性最强移动电源都能供电1.3 核心功能逻辑设计软件层面我把它做成一个简单的事件驱动系统。主循环里轮询按键状态和 RTC 时间当系统时间命中某条用药计划时进入“提醒状态”舵机转到对应格子角度蜂鸣器发出提醒OLED 显示“该吃药了”字样和对应药格编号。用户按下确认键系统记录当前时间蜂鸣器停止OLED 切换到正常显示。这里最容易踩的坑是“提醒逻辑写成了阻塞式”。如果蜂鸣器提醒是用了 while 循环死等那按键响应、屏幕刷新全部会卡死。我的做法是把提醒做成状态机主循环只负责轮询和状态切换蜂鸣器响 3 秒停 1 秒的节奏用定时器非阻塞实现这样整个系统始终响应灵敏。这个设计思路在后面的代码部分会展开讲。2. 硬件电路设计与原理图绘制2.1 最小系统与主控电路STM32F103C8T6 的最小系统主要有五件事电源、晶振、复位、启动模式、调试接口。原理图里必须把这些画全不能直接拿最小系统板模块往上一插就觉得完事了因为开源的原理图是要给别人“照着自己画板”用的。电源部分USB 输入的 5V 经过 AMS1117-3.3 降到 3.3V输入输出两端都加 10uF 钽电容和 100nF 陶瓷电容并联。这是 LDO 的标准滤波组合注意电容要靠近芯片引脚放不要走线绕远路。晶振部分我用了 8MHz 无源晶振两个 20pF 电容做负载匹配。负载电容的计算公式是 CL (C1 * C2) / (C1 C2) Cs其中 Cs 是 PCB 引脚的寄生电容一般取 3~5pF。如果晶振数据手册要求的负载电容是 18pF那么 C1 C2 取 20pF算下来 (20*20)/(2020)4 14pF偏差有点大。所以实际画板的时候我会把电容放在 20pF 和 22pF 之间配合示波器看起振波形微调。这地方很多人抄原理图时直接照搬参数结果板子做出来晶振不起振多半就是负载电容没算对。复位电路就是经典的上电复位10k 电阻上拉到 3.3V100nF 电容对地NRST 引脚接在中间。BOOT0 引脚通过 10k 电阻下拉到地保证从 Flash 正常启动。SWD 调试接口必须留出来标准 4PINSWDIO、SWCLK、GND、3.3V注意 SWDIO 和 SWCLK 分别加上拉的 10k 电阻到 3.3V。别问我为什么强调这个我见过不下三次有人省掉上拉然后 ST-Link 连接不稳定、烧进去一次程序之后第二次就找不到设备。2.2 外设模块电路设计各外设的原理图设计我挑几个有代表性的讲。蜂鸣器驱动电路我用的是 S8050 NPN 三极管做开关。GPIO 输出高电平时三极管导通蜂鸣器响输出低电平时关闭。基极串一个 1k 限流电阻蜂鸣器并联一个 1N4148 续流二极管。注意续流二极管方向一定要反接也就是阴极接电源正极、阳极接三极管集电极接反了会在关断瞬间直接打坏三极管。OLED 用的 I2C1 接口SCL 和 SDA 都需要接 4.7k 上拉电阻到 3.3V。SSD1306 的 I2C 地址常见是 0x3C七位地址但也有一批模块是 0x3D写驱动的时候要留一个宏定义可切换不然换块屏幕代码就要改一遍。舵机用的是定时器 TIM2 的 PWM 输出通道。SG90 舵机的控制信号是 50Hz 的 PWM周期 20ms高电平时间 0.5ms 对应 0 度2.5ms 对应 180 度。这个高电平时间由定时器预分频和比较值决定72MHz 时钟预分频 71得到 1MHz 计数频率ARR 设 19999得到 50Hz比较值 250 到 1250 对应 0.5ms 到 2.5ms。这个计算过程要自己手算一遍后面调舵机角度才能做到指哪打哪。干簧管的电路很简单一端接 3.3V一端串联 10k 下拉到地然后接到 GPIO。默认状态下干簧管断开GPIO 读到低电平磁铁靠近时干簧管导通GPIO 读到高电平。这样接的好处是万一干簧管或线路出问题GPIO 读到的一直是 0不会误判成“药已取走”。2.3 原理图绘制实操心得原理图我用的是嘉立创 EDA 画的方便开源分线左边放电源电路中间放大主控右边按功能块排外设用网络标签代替飞线。画的时候养成一个习惯电源网络用红色地网络用黑色信号网络用蓝色逻辑清楚才不容易遗漏连线。再提一个很多人会犯的低级错误原理图和 PCB 的位号、网络编号对不上。比如原理图里蜂鸣器位号是 BZ1PCB 里标成 BUZZER1后面 BOM 导出的时候对不上物料或者打样回来贴片对不上位置排查起来非常痛苦。开源项目更要保持位号规范统一。另外我在原理图里给每个功能模块都加了文字说明框描述这个模块的作用、调试注意点比如“蜂鸣器驱动高电平有效续流二极管方向勿反”。这样别人拿到原理图不需要看你的长篇大论也能快速定位模块。3. 软件代码实现与逻辑拆解3.1 工程结构与开发环境软件工程我用的 Keil MDK 5配 STM32F1 标准外设库。选择标准库而不是 HAL 库的原因很简单这个项目逻辑本身不复杂标准库代码更直观看寄存器配置更利于理解底层原理也方便面试时跟人解释。工程文件按功能拆分如下SmartPillbox/ ├── Core/ │ ├── main.c │ ├── stm32f10x_it.c ├── Hardware/ │ ├── bsp_oled.c/h │ ├── bsp_buzzer.c/h │ ├── bsp_servo.c/h │ ├── bsp_key.c/h │ ├── bsp_reed.c/h │ └── bsp_led.c/h ├── App/ │ ├── app_remind.c/h │ ├── app_menu.c/h │ └── pilbox_config.h └── Driver/ └── SSD1306/Hardware 层只管硬件寄存器操作不掺业务逻辑App 层只管业务状态不直接碰寄存器。这样分层的好处是今天用干簧管明天想换成霍尔传感器只需要改 bsp_reed.capp_remind.c 一点不用动。强烈建议所有嵌入式项目都按这个思路分文件后期维护成本差一个量级。3.2 用药计划与时间管理用药计划我设计成一个结构体数组每一条包含时分秒、药格编号、是否启用三个字段。早上 8 点吃第一格中午 12 点吃第二格晚上 6 点吃第三格结构体数组大致长这样typedef struct { uint8_t hour; uint8_t min; uint8_t sec; uint8_t slot; uint8_t enabled; } PillPlan_t; static PillPlan_t g_pillPlan[] { { 8, 0, 0, 1, 1 }, { 12, 0, 0, 2, 1 }, { 18, 0, 0, 3, 1 }, };判断是否到点在主循环里每秒读一次 RTC 时间把时分秒和每条计划做比对。注意一个细节计划触发之后要把“当天已提醒”的标记置位不然同一分钟会触发几十次。我的做法是记录上一次触发时间精确到分钟如果当前分钟和上次相同就跳过。RTC 用的是 STM32F103 内部 RTC它本质上是一个带独立备用电源域的 32 位计数器。外部需要一个备用电池CR1220 纽扣电池接到 VBAT 引脚断电后 RTC 才能继续走。这个很多人会漏结果每次断电重新上电时间都归零还以为是代码问题。3.3 提醒状态机与出药动作提醒逻辑用状态机实现状态定义如下typedef enum { ST_SLEEP, // 待机什么都没发生 ST_RINGING, // 正在提醒 ST_CONFIRMED, // 已确认吃药 } RemindState_t;主循环的轮询逻辑大致是如果在 ST_SLEEP 状态下命中计划时间切换为 ST_RINGING同时启动蜂鸣器的节奏定时器并让舵机转到对应格子角度在 ST_RINGING 状态下检测到按键确认或干簧管触发切换为 ST_CONFIRMED记录当前时间蜂鸣器静音在 ST_CONFIRMED 状态下如果时间过了当前分钟回到 ST_SLEEP等待下一条计划。蜂鸣器节奏提醒这里我用了一个软件定时器在定时器中断里翻转蜂鸣器 IO 和控制总响铃时长不占用主循环。主循环只做状态判断所以按键响应始终是及时的。我说个反面例子最开始我图省事直接在状态判断里写了“响 200ms 停 200ms”的三层嵌套延迟结果屏幕刷新卡成幻灯片按键按下去要等两三秒才反应这就是典型的阻塞式写法翻车。3.4 OLED 显示与菜单设置OLED 驱动用的 SSD1306 的 I2C 模式核心就是给显存数组刷数据。我按 128x64 分辨率规划了三个显示页面主页显示时间、日期、今天的吃药计划和各格状态提醒页显示大字体的“请吃药”和药格编号设置页 通过按键调整系统时间和每条用药计划。设置页的交互我提一个实用技巧按键采用短按和长按结合短按切换选中项长按进入二级设置。三个按键分别是“上移/下移”、“确认”、“取消”。菜单状态用一个枚举变量维护每层状态对应不同的按键处理这样即使菜单层级再多代码结构依然清晰。按键扫描的消抖也很关键我是先做 10ms 定时器轮询连续两次读到相同电平才算有效防抖效果比简单延时好得多。其中 OLED 中文字库是很多人卡壳的地方。SSD1306 默认不带中文字库需要把取模好的汉字点阵数据放到数组里比如“请吃药”三个字就是从取模软件生成 16x16 点阵每个字 32 字节数据放 const 数组。我做了一张常用字表包含“年月日时分”、“请吃药”、“星期”基本覆盖所有显示场景。取模的时候注意选择“纵向取模、高位在前”还是“横向取模、低位在前”不同驱动函数扫屏方式不一样选错了屏幕显示就是乱码这是新手最容易懵的地方。4. 仿真环境搭建与联调测试4.1 仿真平台选型与对比这个项目开源包含 Proteus 仿真文件。选 Proteus 的原因一是 STM32F103 的模型支持比较成熟可以直接加载 Keil 生成的 hex 文件跑二是它的虚拟仪器很全示波器、逻辑分析仪都能用。现在也有不少人在 Wokwi 在线平台上做 ESP32、Arduino 仿真但 STM32 的在线仿真生态还不算完整项目级仿真我还是推荐 Proteus 8 以上版本。如果你以前只用过 Proteus 仿真 51 单片机切换过来会有一点不习惯STM32 仿真必须在元件库里搜 STM32F103C8双击后还要填一个晶振频率参数默认是 8MHz如果你代码里用的是外部晶振这里不设置成 8MHz仿真里时间就跑不对。4.2 Proteus 仿真搭建步骤仿真工程的核心搭建流程我整理成几条照着做基本不会出问题新建工程在装配模式下放置 STM32F103C8 芯片、OLED仿真库里搜 SSD1306、按键、LED、蜂鸣器等元件芯片电源引脚默认隐藏要在“属性”里确认 VDD 接 3.3VVSSA/VDDA 这些别漏接双击 STM32 芯片在 Program File 选项里加载 Keil 工程编译出来的 hex 文件启动仿真正常情况下 OLED 会开始刷新按键操作也能响应。这里有个坑Proteus 里 STM32 的调试引脚 PF0/PF1 默认是 JTAG 复用如果你代码里把这两个引脚当普通 GPIO 用仿真会提示引脚冲突。解决办法是在代码里关闭 JTAG 功能只保留 SWDGPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);这条代码写不写直接影响仿真能不能正常运行。我在实物板上跑的时候没有这个问题因为 SWD 调试引脚占用的是 PA13/PA14跟 PE 口不冲突。4.3 仿真与实物的差异调试仿真毕竟不是实物有几个差异在联调阶段特别明显。第一个是时间基准。Proteus 的仿真时间依赖主机性能跑快了你会觉得 RTC 时间走得比真实时间快跑慢了又会觉得提醒晚点。这不代表代码有问题调试提醒逻辑时别去对表直接看状态跳转是否正确就行。第二个是 I2C 时序。仿真里 I2C 波形比较理想化不会像实物那样受上拉电阻、线缆电容影响。实物 OLED 偶尔白屏仿真却一切正常这基本说明你的 I2C 通信时序余量不够。解决办法是在 OLED 驱动里每次传输后加一个微小延时放宽 SCL 时序。我实际调这块的时候就把仿真当“逻辑调试器”用在 Keil 里打断点观察状态机变量、RTC 计数值、OLED 显存数据是否在预期时机变化确认逻辑没问题后再拿实物板联调硬件差异这是效率最高的调试路径。5. 常见问题与排查技巧实录5.1 硬件问题的排查思路硬件问题里蜂鸣器不响排第一。先用万用表量 GPIO 输出的电压程序置高后引脚有 3.3V而蜂鸣器需要 5V 驱动这就是问题关键S8050 的集电极接的是 5V不是单片机的 3.3V。很多人直接在面包板上从 STM32 的 3.3V 引脚给蜂鸣器供电音量自然上不去。我测下来 5V 驱动有源蜂鸣器声音在安静房间能覆盖整个屋子3.3V 下声音衰减得很明显。舵机抖动也比较常见。SG90 堵转或者飞线太长时电流尖峰很大会造成 STM32 的 3.3V 电源瞬间跌落甚至导致芯片复位。解决思路是舵机电源和逻辑电源分开用独立 5V 供电给舵机地线远端单点汇总。手头没有双路电源的话可以给舵机电源线串一个 100uF 电解电容能降低不少纹波。OLED 白屏是另一个高频问题我排查的顺序是I2C 地址是否匹配0x3C 还是 0x3D→ SCL/SDA 上拉电阻是否焊接 → 供电是否 3.3V 而不是接了 5V → SSD1306 初始化时序是否正确。这一步一步排除比乱换代码效率高得多。5.2 软件层面的调试技巧软件问题里最隐蔽的是 RTC 配置失败后死循环。标准外设库里 RTC 进入配置模式有时会因为“配置完成标志位没置起”而卡在等待循环里表现就是程序下载后完全不运行连 OLED 都点不亮。解决办法是在等待循环加超时退出机制超过约定时间就返回错误避免系统死锁。按键消抖偶尔也会有逻辑漏洞。我只在按下时消抖松手时没处理结果出现一次按键触发两次确认的“幽灵按键”。后来改成按下和释放都做 10ms 确认或者等按键稳定后再补一次电平逻辑判断问题就消失了。这种边界情况代码静态看很难发现要靠实际试按才暴露。I2C 通信偶发死锁也很值得说。当从设备拉低 SCL 时主设备如果还在正常发送总线状态就乱了这是硬件层的 I2C 死锁。我在 OLED 驱动里加了一个软件复位序列连续切换 SCL 9 个周期再发送起始位能把总线状态拉回来。实际测试下来这个策略能把 OLED 偶发白屏的概率降到几乎为零。5.3 仿真问题速查表现象可能原因排查方向仿真启动后 OLED 全黑hex 文件路径不对或未加载重新指定 hex确认编译成功时间走得比真实快/慢Proteus 运行速度受主机性能影响用逻辑分析仪对比波形不对比时钟按键按下无响应引脚定义和代码不匹配核对 Proteus 元件网络标签GPIO 引脚冲突报警JTAG/复用功能占用关闭 JTAG保留 SWD蜂鸣器持续响不停提醒状态未跳出检查状态机置位与清零逻辑仿真还有一个细节Proteus 的 LED 和蜂鸣器模型响应速度快很理想化实物上一脚踩一脚踹的“上电瞬间误触发提醒”问题仿真里永远复现不了。这块需要在代码里加一个“上电 3 秒内不触发提醒”的保护我加了个 3 秒延时后 RTC 时间才与计划比对实物上电瞬间的异常提醒就消失了。这类问题只能靠实物测试才能暴露仿真帮你验证逻辑就够了别指望它帮你覆盖所有硬件边界。这个项目开源的内容里代码、原理图、仿真工程和一份简单的使用说明我打包放一起了你拿到手先看 pilbox_config.h 里的宏定义把蜂鸣器引脚、按键引脚和你的板子对上编译烧录后直接进设置页校准一下时间就能跑起来。后续如果想把“老人忘了吃药”的警报发给子女代码里我已经在 App 层预留了串口输出事件的位置接一块 ESP8266 或者 SIM800C 模块把提醒和确认事件通过串口转发出去就能做成远程提醒这个方向值得继续折腾。
返回列表