资讯动态

基于STM32F103C8T6的智能药盒设计:定时提醒与自动出药系统

发布时间:2026/9/10 3:56:52 来源:尧图企业网站定制
家里有老人的朋友应该都懂吃药这件事真不是小事。每天几种药、不同时间吃、有的饭前有的饭后别说老人记不住年轻人工作一忙也会忘。我家里老人就出过两次漏服、一次重复服的情况当时就想着能不能做一个能定时提醒、自动出药的设备。刚好手头常备STM32开发板于是就把这个智能药盒/老人用药管理系统从需求到实物完整做了一遍代码、原理图、仿真文件全部整理好了。这套系统的主控用的就是STM32F103C8T6配合OLED显示屏、实时时钟、温湿度传感器、蜂鸣器、按键和舵机实现了定时提醒吃药的完整闭环。全程在Proteus里做了仿真验证电路设计从最小系统到电源管理都给出了完整原理图代码部分我按模块封装好方便你直接改菜单、改时间、改用药方案。如果你是正在做STM32课程设计的学生或者想给家里老人做个实用小设备那这篇文章正好适合你照着抄作业基本能跑通。1. 项目概述与设计思路拆解1.1 需求分析智能药盒到底在解决什么问题老人用药管理这个需求看起来简单实际上隐藏着三个核心痛点。第一是记性退化老年人经常忘记什么时间该吃什么药尤其是一天要吃三四种药的时候漏服率非常高。第二是重复服用记不清上次吃药时间吃过又吃这是最危险的。第三是子女无法远程掌握年轻人上班忙很难做到每天都盯着等发现问题的时候可能已经晚了。针对这三个痛点智能药盒需要有四样核心能力能告诉老人现在该吃什么药能用声音和灯光强制提醒能自动把对应药舱推出来最好还能记录吃药历史。市面上的商用智能药盒动辄几百上千块功能还未必贴合自家老人的用药习惯自己动手做一个不仅便宜关键是能按真实需求定制。我在这版方案里把功能分成了五块时间显示、用药提醒、声光报警、自动出药、按键交互。OLED屏实时显示当前时间和温度系统内部维护一张用药时刻表当系统时间匹配到某个时刻时蜂鸣器响、屏幕弹出提醒、舵机把对应药舱推出老人按一下确认键表示已经吃药记录同步保存。1.2 主控选型为什么选STM32F103C8T6用STM32做这个项目是很自然的选择。51单片机当然也能做但考虑到系统要同时管理显示、时钟、传感器、舵机、按键、蜂鸣器多个外设还要有充足的定时器和中断资源51的代码写起来会相当憋屈。Arduino更简单但开源硬件方案的“学习含金量”明显不足距离真实工业开发范式差得远。STM32F103C8T6这颗芯片在业界被称为“性价比之王”72MHz主频64KB Flash20KB SRAM资源对于这个项目来说完全够用关键是相关教程和资料密集度极高网上随便一搜就能找到大量参考代码遇到坑也容易搜到解决方案。另外一个重要原因是开发工具链免费Keil MDK社区版就能正常开发标准外设库和HAL库都能用不需要额外花一分钱。我综合考虑后选了标准外设库来做理由是这份代码开源后读者更容易跟踪到寄存器层面的细节也方便不同开发环境之间移植。如果你更习惯HAL库后面我会说明哪些地方需要替换。1.3 方案对比与51单片机和Arduino的取舍我用下面这个表格把三种方案的核心差异整理了一下方便你在立项时做决策对比维度STM32F103C8T651单片机(如STC89C52)Arduino Uno主频与性能72MHz资源充裕12MHz外设紧张16MHz上手简单外设数量多个定时器、I2C、USART外设少需软件模拟库封装完整操作简单学习深度有寄存器/库两种层次较浅适合入门最浅偏应用搭建在线仿真支持Proteus支持良好Proteus支持良好仿真支持一般项目含金量适合毕设/课程设计稍显单薄工业味不足移植扩展性可上RTOS、WiFi模块扩展困难扩展一般从表格能看出来STM32在“性能不浪费”和“学习收益合适”之间找到了平衡点。尤其考虑到后续可以扩展ESP8266做远程提醒、加蓝牙模块联动手机App、上RTOS做任务调度这套系统的成长空间明显更大。2. 系统硬件架构核心模块与原理图设计详解2.1 STM32F103C8T6最小系统晶振电容计算与复位设计一个可靠的最小系统是整个项目的根基。STM32F103C8T6的最小系统由电源电路、晶振电路、复位电路、BOOT配置电路四部分组成。晶振电路是很多新手最容易出错的地方。主晶振我使用8MHz无源晶振配合两个20pF负载电容。这个20pF不是随便选的它的计算公式是CL (C1 * C2) / (C1 C2) Cs其中CL是晶振要求的负载电容值STM32F103C8T6的负载电容典型值是18pF到20pFCs是引脚寄生电容经验值约3~5pF。如果取C1 C2 20pF寄生电容按4pF估算实际等效负载就是(20*20)/(2020) 4 14pF接近典型值起振可靠。注意如果你手头只有33pF电容也能凑合起振但会导致频率偏差变大如果后面接了串口通信波特率误差会明显。实测下来用20pF电容配8MHz晶振串口9600波特率跑一整晚误码率完全可以忽略。复位电路采用经典的10kΩ上拉电阻加0.1μF对地电容方案。这个RC组合的时间常数大约为1ms能满足STM32复位脚的最小低电平时间要求。BOOT0和BOOT1两个引脚都通过10kΩ电阻下拉到地确保从Flash启动这点接错会导致程序下载后无法运行。2.2 显示模块0.96寸OLED与I2C通信人机交互的核心是显示。我选用的是0.96寸I2C接口OLED屏SSD1306驱动芯片分辨率128x64。选OLED而不是LCD1602的原因有两个一是OLED自发光可视角大老人站在侧面也能看清二是I2C接口只需要SDA和SCL两根线连线极其简洁占用引脚少。在原理图设计上OLED的SCL接PB8SDA接PB9这两个引脚是STM32F103的I2C1硬件引脚。虽然也可以用GPIO软件模拟I2C但既然能硬件I2C就别浪费。供电直接接3.3V。这里有一个经验要分享OLED模块上的I2C地址选择电阻默认是0x78有些模块是0x7A这个地址在驱动代码里必须匹配否则屏幕死活不亮。我在代码里做了一个自动地址探测开机会尝试两个地址这样换成不同家的屏幕都能自适应这个细节对读者来说非常实用。2.3 时钟与温度感知DS1302实时时钟和DS18B20系统必须知道“现在是几点”才能判断“该不该提醒吃药”。实时时钟模块我选了DS1302它是一款非常经典的RTC芯片支持年、月、日、时、分、秒计时通过三线接口SCLK、I/O、CE与MCU通信。为什么不用DS3231DS3231精度确实高很多带温度补偿晶振价格贵不少。但DS1302在室内环境下一天误差通常在1~2秒以内连续一周累计误差可以在10秒内对吃药提醒这种分钟级精度的场景完全够用。我在软件里还做了自动校准逻辑每次上电时如果检测到时间异常会提示重新设置。DS18B20温度传感器负责监测环境温度用于判断药品储藏环境是否过热。虽然默认项目里只显示温度数值但这个模块为后续扩展恒温提醒和高温报警留好了接口。DS18B20采用单总线协议只需一个GPIO引脚我接在PA1上外部加一个4.7kΩ上拉电阻到3.3V——这个上拉电阻不能省没有它通信会不稳定温度数据经常读到85℃这种荒谬值。2.4 提醒与执行机构蜂鸣器报警电路与舵机出药设计用药提醒不能只靠屏幕老人可能没盯着屏幕看所以声音报警非常重要。蜂鸣器我选用的是5V有源蜂鸣器模块MCU的GPIO输出高电平即可驱动模块工作模块自带的S8550三极管负责放大电流。注意如果是直接把无源蜂鸣器接到GPIO上一定要加三极管驱动电路GPIO的灌电流能力不足以直接驱动蜂鸣器。我在原理图里也画了NPN三极管驱动方案如果你手头是单独的蜂鸣器元件就参考那个部分。出药机构我采用SG90舵机。舵机的控制信号是周期20ms的PWM波其中高电平时间在0.5ms到2.5ms之间对应0度到180度。STM32的定时器2通道1输出PWM我配置在PA0引脚。每顿药对应一个药舱舵机转动到指定的角度把对应的药盒推盘推到取药口。这里要补充一个设计细节一个舵机只能控制一个活动挡板如果药盒有四格药舱就需要4个舵机或者设计一个旋转药盘。我在开源项目里做了一个“多档位旋转出药盘”方案用单个舵机配合一个圆盘圆盘上均匀分布四个药槽每次吃药时舵机旋转固定角度把对应药槽转到取药口。这样成本低机械结构也简单。2.5 电源系统设计3.3V与5V分层供电整机的电源拓扑是这样的USB口的5V输入进来一部分直接给蜂鸣器模块和舵机供电另一部分通过AMS1117-3.3稳压芯片降压到3.3V给STM32、OLED、DS1302、DS18B20供电。原理图上AMS1117的输入和输出端各需要10μF和0.1μF电容配对输入端的电容用于稳定输入电压输出端的小电容用于滤除高频噪声。布局时这两个电容要尽量靠近芯片引脚这一步看似不起眼但能明显提升系统稳定性。我调试时遇到过单片机不定时重启的问题最后排查就是AMS1117输出电容离得太远导致的后来把电容焊到芯片附近问题就消失了。电源部分还有一个很多人会忽略的问题舵机启动瞬间会拉低电压导致MCU复位。解决办法是在舵机供电线上串联一个二极管做隔离再接一个100μF电解电容做储能实测能稳稳扛住舵机启动瞬间的电流冲击这个细节在实物调试时极其关键。3. 软件代码设计与实现要点3.1 软件整体框架前后台系统与状态机软件架构我采用了经典的前后台系统一个大循环处理主要业务逻辑定时器中断处理时基和显示刷新。没有上RTOS因为项目规模还达不到需要任务调度的程度裸机编程反而更容易理解和修改。程序的主循环里跑了一个状态机系统的主要状态分为正常显示、菜单设置、服药提醒、出药执行、历史记录。状态机的好处是逻辑清晰不会出现界面跳转混乱的问题也方便增加新功能。每次循环查询当前状态执行对应的处理函数。说一个我踩过的坑主循环里的延时函数千万别用长延时比如用delay_ms(1000)来做秒计数。这不仅会阻塞整个系统的按键响应还会让状态机失去实时性。我的做法是使用SysTick定时器产生1ms时基在主循环里通过标志位判断时间是否过去1秒再更新显示这样系统能做到按键秒响应同时时间显示也保持准确。3.2 OLED显示与菜单系统SSD1306驱动细节OLED驱动部分我先把SSD1306的最底层读写函数封装好。I2C通信的本质就是主设备向从设备写数据SSD1306的写入格式是“控制字节数据字节”。控制字节的0x00表示后面的数据是命令0x40表示后面的数据是显示内容。这一点是屏幕能正常显示的基础新手经常在这一步混淆导致屏幕花屏或者完全不亮。菜单系统我设计了三级结构一级是待机界面显示时间、温度、今日用药状态二级是设置界面可以调整用药时间、手动出药测试、闹钟开关三级是具体某项设置比如修改某次吃药的小时和分钟。OLED屏的128x64像素画布上我用16号字体显示中文菜单一屏可以排四行每行八个字符信息呈现刚刚好。// OLED写命令和写数据的核心函数 void OLED_WriteCommand(uint8_t cmd) { I2C_Start(); I2C_SendByte(OLED_ADDR); I2C_SendByte(0x00); // 控制字节后面是命令 I2C_SendByte(cmd); I2C_Stop(); } void OLED_WriteData(uint8_t dat) { I2C_Start(); I2C_SendByte(OLED_ADDR); I2C_SendByte(0x40); // 控制字节后面是数据 I2C_SendByte(dat); I2C_Stop(); }完整的显示驱动代码里有取字模工具生成的中文字库如果你需要自定义字符直接用取模软件生成数组替换即可。这段代码我在网上开源时特意把注释写得比较细就是为了让你改起来不用查太多资料。3.3 用药时间匹配RTC读取与提醒判断逻辑用药管理的核心逻辑其实并不复杂就是“当前时间”和“设定时间”的比对。我维护了一个用药计划结构体数组每个元素包含小时、分钟、是否启用、提醒文字、对应的药槽编号。typedef struct { uint8_t hour; uint8_t min; uint8_t enable; uint8_t slot; char remind_msg[16]; } MedicinePlan;主循环每秒读取一次DS1302的当前时间然后遍历这个数组如果当前小时、分钟和计划匹配并且今天这条计划还没被执行过就触发提醒流程。提醒触发后只有在按键确认后才会对当天做标记这样老人即使没到药盒前提醒也会反复响起不会被误消掉。这里有个容易疏忽的问题RTC芯片的时间寄存器中数值是BCD码格式比如十进制的15会被存储为0x15而这两者直接计算时就会出错。读取后必须做一次BCD转十进制写入时反过来。我在代码里专门封装了工具函数BCD2HEX()和HEX2BCD()如果你自己写代码这个坑一定要提前避开。3.4 舵机出药控制PWM占空比精确计算舵机控制的基础是PWM脉冲宽度与角度之间的对应关系。SG90舵机使用20ms周期高电平时间从0.5ms到2.5ms对应0到180度那么每1度对应的脉宽增量是(2.5ms - 0.5ms) / 180 0.0111ms/度如果要转到45度高电平时间就是0.5 45 * 0.0111 1.0ms。STM32的TIM2在72MHz时钟下我配置预分频器PSC为71计数值从0数到19999这样计数周期正好是20ms。那么45度对应的比较值就是CCR 20000 * (1.0 / 20) 1000把这些计算逻辑直接固化在代码里我就封装了一个SetServoAngle(uint8_t angle)函数传入角度值自动计算比较寄存器数值调用起来非常方便。实际测试中SG90舵机的转角精度在正负2度左右对于推动药槽出药完全够用。void Servo_SetAngle(uint8_t angle) { // angle: 0~180 // 0度对应0.5ms, 180度对应2.5ms, 周期20ms uint16_t duty 500 (uint16_t)angle * 2000 / 180; TIM_SetCompare1(TIM2, duty); }3.5 按键交互与防抖处理系统有三个按键确定、选择、返回。按键处理是嵌入式软件中最容易被忽视但实际问题最多的部分。我采用的方案是“10ms定时扫描 状态机消抖”在定时器中断里每10ms扫描一次按键连续三次扫描到同一个电平状态才认为按键状态稳定按键按下沿触发一次动作松开沿不触发这个方案比自己写delay_ms(50)做消抖强太多不会阻塞主循环也不会在按键乱按的时候产生误触。实测下来即使老人的手有轻微抖动按键依然能准确响应不会出现按一次跳两个菜单的问题。4. Proteus仿真环境搭建与联调演示4.1 仿真元件准备与电路连线这套设计我完整在Proteus 8.12上验证过仿真文件已经放进开源包里你直接打开就能跑。如果你打算自己从零搭一遍仿真核心元件是这样选的元件名称Proteus库中的关键字参数/备注STM32F103C8T6STM32F103C8主控OLED显示屏无原生库用替代方案用LCD12864或I2C虚拟屏DS1302DS1302RTC芯片DS18B20DS18B20温度传感器蜂鸣器BUZZER有源蜂鸣器舵机MOTOR-SERVOSG90模型电阻/电容RES/CAP按原理图取值Proteus里没有SSD1306 OLED的原生模型我仿真时用一个I2C虚拟显示屏来验证数据输出的正确性然后用逻辑分析仪观察I2C波形确认时序。如果担心虚拟屏和实物OLED显示不一致其实不用操心只要能正确把数据通过I2C发出去OLED真机上一定会显示两者驱动逻辑是完全一样的。连线的核心思路是硬件原理图的在线映射。SDA接PB9SCL接PB8DS1302的三根线接PA7、PA6、PA5DS18B20接PA1蜂鸣器接PA2舵机接PA0三个按键分别接PB0、PB1、PB10。4.2 Keil编译与HEX文件加载仿真能运行的前提是先把代码编译成HEX文件。在Keil里安装好STM32F103C8的器件包后配置目标选项时注意这几点Device选择STM32F103C8Target页的晶振频率填8MHzOutput页勾选Create HEX FileDebug页选择Use Simulator即可。CTRLF5编译如果没有报错就能在工程目录的Objects文件夹里找到生成的HEX文件。双击Proteus里的STM32芯片在Program File一栏选择刚才生成的HEX文件点击OK。然后点击仿真运行按钮系统应该就开始工作了——OLED屏幕显示时间温度蜂鸣器和舵机在到达设定时间后开始动作。这里有一个比较容易卡住的地方Proteus的STM32模型默认复位时BOOT0是低电平吗我在仿真时发现如果不配置BOOT引脚电平芯片会进入DFU模式不执行代码。你要在STM32芯片属性里把BOOT0和BOOT1都设为低电平LOGIC LOW才能正常从Flash启动。这个细节在官方文档里写得比较含糊仿真报错时检查一下这里。4.3 仿真演示效果与常见调试手法仿真里我设置了一个演示模式把用药时间默认设定为当前仿真时间加2分钟这样一启动虚拟机你就能看到完整流程时间到后蜂鸣器响OLED弹出“服药时间到”提醒舵机旋转到对应药槽位置按下确认键后提醒停止当天该时段标记为已服。调试仿真的时候多用Proteus的虚拟仪器。比如不确定DS1302的时钟是否在走可以拉一个数字示波器出来看DS1302的SCLK引脚有没有脉冲。不确定I2C波形对不对直接把SCL和SDA的信号同时在示波器里看波形是否有ACK响应。这些都是仿真环境比实物更好用的地方——你可以在虚拟环境里观察任何节点的电信号完全不用动用万用表。5. 常见问题与排查技巧实录5.1 编译与烧录环节的坑“Error: No STM32 Target found”是很多读者私信我提到最多的报错。这个错误通常出现在Keil点击下载按钮时最常见原因有三个ST-Link没有正确连接、调试器驱动没装好、或者Keil里Debug选项没有选择对应的烧录器型号。我在实物联调时也遇到过最后发现是ST-Link和板子之间的排线松动导致SWDIO信号时断时续。还有一个不那么明显的原因如果你的板子不是完整的官方最小系统板而是自己焊的STM32电路板那要重点检查SWDIO和SWCLK引脚上是否接了上拉电阻以及NRST引脚是否被外电路拉低了。这些信号线只要一个出问题J-Link或者ST-Link都无法和芯片建立连接。网上搜这个报错大部分答案都不完整我这里直接给你把检查顺序列出来先看Debug里选没选对烧录器再看接线松不松最后查NRST电平。5.2 硬件电路典型故障速查现象可能原因排查方法OLED完全不亮I2C地址不匹配代码里切换0x78/0x7A地址OLED花屏供电不稳或数据线干扰缩短I2C线加0.1μF去耦电容蜂鸣器不响GPIO驱动能力不足确认采用了三极管驱动电路舵机抖动/不转供电电压被拉低舵机独立供电加100μF电容RTC时间不走主晶振未起振示波器测OSC_IN引脚波形温度读到85℃DS18B20上拉电阻缺失确认数据线接4.7kΩ上拉其中舵机抖动这个问题我多说一句。SG90的标称工作电压是4.8V到6V直接接5V是可以的但如果USB口供电能力不足或者共用一组细杜邦线启动时压降非常明显。我曾经试过用面包板飞线舵机一转STM32就复位换上一根粗导线加强供电后问题就消失了。这也是为什么原理图里我在舵机供电前单独加了100μF储能电容和二极管隔离。5.3 仿真与实物差异必须知道的三个事实仿真通过不代表实物一定能跑这几乎是所有嵌入式项目都躲不开的现实。这个项目里仿真和实物最大的差异有三个第一舵机在Proteus里是理想模型不会考虑负载扭矩和供电问题。仿真里看起来转得非常顺畅但实物可能因为机械卡涩或者电机力矩不足而转不到位。解决思路是增加舵机转动后的到位检测或者改用带减速箱的MG995金属齿轮舵机。第二DS1302在仿真里的走时速度可能比实物快这是因为仿真时钟和真实晶振存在误差。仿真里你可以直接修改芯片的频率参数来校准实物则需要通过软件定期校准。第三Proteus的I2C仿真比较简化如果IO模拟I2C时序有微小延迟问题仿真看不出来但实物OLED可能偶发花屏。所以代码里我加了写数据完成后的短暂延时确保兼容不同速度的屏幕模块这也是实物验证过的经验。5.4 用药逻辑边界场景处理用药提醒最怕出现“该吃时没提醒、不该吃时乱提醒”的情况。我在代码里额外处理了几个边界场景系统刚上电时如果正好到了某个用药时间点要判断这个时间点是否刚错过避免一上电就疯狂报警老人如果错过了提醒时间比如过了两小时才回来系统需要显示出“已错过”的状态而不是把这次药跳过去时间修改之后所有当天已完成的用药标记需要自动重建否则第二天提醒逻辑会混乱。这些逻辑用伪代码描述就是读取时间后先判断“今天日期是否切换过”切换过就先清理当天状态再执行提醒判断。在“待提醒”状态下只要没按确认键蜂鸣器每30秒响一次同时OLED上显示药品名称和药槽编号。这些细节都是老人真实使用过程中的刚需你在设计时一定要想清楚前后逻辑。5.5 代码工程目录结构建议开源包里我整理了一个清晰的工程目录拿到手就能看懂SmartMedicineBox/ ├── Doc/ │ ├── 原理图.pdf │ ├── 系统说明.md │ └── BOM清单.xlsx ├── Hardware/ │ ├── SmartMedicineBox.dsn │ └── SmartMedicineBox.pdsprj ├── Firmware/ │ ├── Core/ │ │ ├── Inc/ │ │ └── Src/ │ ├── Drivers/ │ │ ├── BSP/ (OLED、DS1302、DS18B20、蜂鸣器、舵机驱动) │ │ ├── Middlewares/ (菜单库、定时器服务) │ │ └── CMSIS/ │ └── Project/ │ ├── Keil工程文件 │ └── Objects/ (生成HEX的位置) └── README.md这个组织方式参考了企业级嵌入式项目的分层思路把驱动和应用层分开。你想改界面文字去App层找你想换传感器芯片去BSP层改对应驱动不需要从几百行代码里大海捞针。这也是我做开源项目的一个习惯——项目是给别人看的结构清晰比代码炫技更重要。我个人在实际操作中的体会是这种综合性项目硬件和软件配合的问题占掉了一半以上的调试时间。原理图连线、电容取值这些看似基础的东西一旦出错排查起来比写代码痛苦得多。如果你在仿真阶段就把电源、复位、晶振这些基础模块调通再做实物的时候会省心很多。另外吃药提醒功能建议自己做一轮模拟测试把提醒时间间隔缩短为1分钟来验证逻辑确认没Bug后再改回真实周期。这套项目的所有资料我都放在开源仓库里了代码、原理图、仿真文件齐全你拿到后先从仿真跑通一遍再逐步替换成实物模块会是一个非常顺滑的上手路径。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价