1. 项目概述与核心价值
作为一名长期混迹于创客社区和嵌入式开发领域的“老鸟”,我经手过不少智能家居和健康监测项目。最近,我完成了一个让我自己都觉得很实用的作品:一个基于树莓派的自动药品分发器,我给它起名叫“DailyDose”。这个项目的初衷很简单,就是为了解决我自己和家人经常忘记按时吃药,或者搞不清该吃哪种药的麻烦事。市面上虽然有一些智能药盒,但要么功能单一,要么价格昂贵,而且总觉得少了点“自己动手,丰衣足食”的乐趣和完全按需定制的灵活性。
这个“DailyDose”智能药盒,核心就是利用树莓派作为大脑,配合RFID进行身份认证,再加上传感器和舵机,实现一个安全、可控的自动发药流程。它不仅仅是“到点就响”的提醒器,而是真正做到了“认证-检测-分发-确认”的闭环管理。想象一下,你只需要刷一下专属的卡片或钥匙扣,药盒检测到杯子已就位,就会自动转动内部的药仓,将一次剂量的药片精准投递到你的杯子里,然后你需要按一下确认按钮,系统才会记录“已服药”。整个过程,既避免了误操作,也留下了可追溯的记录。
对于有慢性病需要长期服药的患者、记忆力减退的老年人,或者只是单纯想规范自己用药习惯的年轻人来说,这样一个DIY方案的价值是显而易见的。它提升了用药依从性,减少了因遗忘或错拿导致的风险,并且通过物联网技术(虽然本项目侧重于本地控制,但留有扩展接口),为未来的远程监护或数据统计提供了可能。接下来,我就把这套从硬件选型、结构设计、代码编写到调试上线的完整过程,毫无保留地拆解给你看。
2. 系统整体设计与思路拆解
在动手之前,清晰的顶层设计是避免后期反复折腾的关键。这个自动药品分发器虽然不算极度复杂,但涉及硬件联动、逻辑控制和用户体验,必须想清楚再动手。
2.1 核心需求与功能定义
首先,我们要明确这个盒子到底要干什么。我把它归纳为四个核心功能和两个安全边界:
- 身份认证:发药是一个需要权限的操作,不能谁都能按。我选择了RFID读卡器,为每位用户配置一张专属卡片。这比密码输入更便捷,也比指纹(在潮湿或特定环境下)更稳定、成本更低。
- 环境检测:发药前必须确保“药有去处”。我使用了一个超声波测距传感器,安装在出药口下方,用于检测杯子是否放置到位。没有杯子,坚决不发药,防止药片滚落丢失。
- 可控分发:这是机械部分的核心。我需要一个能将存储的药片一次一份、可靠地推送出来的机构。经过权衡,我选择了舵机驱动的旋转式药仓。每个仓格存放一次剂量,舵机旋转固定角度,将对应仓格的药片倒入滑道。
- 服药确认与记录:药片掉落后,系统需要用户一个明确的“我已取走并服用”的反馈。我设置了一个独立的“确认按钮”。只有按下它,本次发药流程才算正式完成,系统可以记录一次成功的服药事件。这个设计避免了药片已出但用户未及时取走造成的“已发药未服用”的状态错乱。
安全边界:
- 硬件急停:设置一个独立的物理电源开关,在任何软件逻辑失效时,可以立即切断系统电源,确保安全。
- 软件互锁:在代码逻辑上,确保各个步骤严格顺序执行,并设置状态超时。例如,RFID认证成功后,如果在30秒内未检测到杯子,则流程重置,需要重新认证。
2.2 硬件方案选型与考量
为什么是这些零件?每个选择背后都有原因:
- 主控:树莓派 3B+:没选用更便宜的微控制器(如Arduino),是因为我预见到后期可能需要添加网络功能(如用药记录上传、远程提醒)、连接小型显示屏或处理更复杂的调度逻辑。树莓派的Linux环境和丰富的GPIO、USB接口,为未来扩展留足了空间。对于这个项目,树莓派Zero 2W其实也够用,性价比更高。
- 身份认证:RC522 RFID读卡器模块:这是最经典、资料最多的13.56MHz RFID读卡方案,价格低廉,与树莓派通过SPI接口通信,稳定可靠。卡片可定制,成本几乎可以忽略。
- 检测单元:HC-SR04超声波传感器:用于非接触式距离测量。安装在出药口下方约5-8厘米处,当检测到距离小于一个设定阈值(例如3厘米)时,就认为杯子已放好。选择它是因为其原理简单、精度对本案足够,且价格便宜。
- 执行机构:SG90舵机:这是一个9克微型舵机,扭矩适中(1.6kg/cm),足以驱动我设计的小型药仓旋转。通过PWM信号控制旋转角度,定位精确。我需要计算好药仓分格数量与舵机旋转角度的关系。
- 人机交互:轻触开关与LED灯带:两个轻触开关分别作为“发药请求按钮”和“服药确认按钮”。一个WS2812B可寻址RGB LED灯带用于状态指示(例如:待机蓝色、认证成功绿色、错误红色、发药中黄色等),比简单的单色LED能传达更多信息。
- 电源管理:整个系统由一块5V/2A的USB电源适配器供电。树莓派和舵机对电流需求较大,尤其是舵机动作瞬间。这里有个关键点:务必使用外部电源(如手机充电器)通过树莓派的USB-C口供电,绝对不要试图通过GPIO的5V引脚为树莓派供电,电流和稳定性都不够,极易导致树莓派重启或损坏。
2.3 机械结构与外壳设计思路
原作者的木制外壳方案很有质感,但制作门槛较高。我的设计思路更偏向于模块化和可重复制作,核心是药仓机构。
我设计了一个圆柱形的药仓,用3D打印制作。圆柱侧面均匀开设了6个或8个扇形仓格(具体数量取决于每日服药次数)。药仓中心轴与舵机的输出轴固定。舵机旋转0度时,第一个仓格对准上方的入药口(用于装药)和下方的出药口滑道。当需要发药时,树莓派控制舵机旋转360 / 仓格数量度,让下一个满载的仓格对准出药口,同时空的仓格对准入药口,实现循环。
外壳的作用是包裹所有电子部件和机械部件,提供结构支撑、防尘和美观。我采用激光切割亚克力板的方式来制作,设计图可以用Fusion 360或Inkscape绘制。亚克力板易于加工,透明或半透明的材质还能让你看到内部工作状态,很有科技感。面板上需要预留RFID读卡区、按钮孔、超声波传感器孔、出药口和电源开关孔。
注意:药仓的设计是成败关键。仓格的大小需要根据你常服用的最大药片尺寸(包括胶囊)来定,并留有余量。滑道的倾角要足够大(建议>45度),确保药片能依靠重力顺利滑落,不会卡住。可以在滑道内壁粘贴特氟龙胶带或打磨得非常光滑以减少摩擦。
3. 硬件连接与电路搭建详解
有了方案,接下来就是“搬砖”环节。正确的电路连接是系统稳定的基础。
3.1 树莓派GPIO引脚分配规划
树莓派有40个GPIO针脚,但并非所有都能随意使用。我们需要合理分配,避免冲突(如复用同一组SPI引脚)。下面是我的引脚分配表:
| 组件 | 连接树莓派引脚 | 功能说明 | 物理引脚 (BCM编码) |
|---|---|---|---|
| RC522 (SDA) | GPIO 8 (CE0) | SPI片选0 | 物理引脚24 |
| RC522 (SCK) | GPIO 11 (SCLK) | SPI时钟 | 物理引脚23 |
| RC522 (MOSI) | GPIO 10 (MOSI) | SPI主出从入 | 物理引脚19 |
| RC522 (MISO) | GPIO 9 (MISO) | SPI主入从出 | 物理引脚21 |
| RC522 (IRQ) | 未连接 | 中断,本项目未用 | - |
| RC522 (GND) | 任意GND | 接地 | 如物理引脚6, 9, 14, 20等 |
| RC522 (RST) | GPIO 25 | 复位引脚 | 物理引脚22 |
| RC522 (3.3V) | 3.3V Power | 供电 | 物理引脚1或17 |
| HC-SR04 (Trig) | GPIO 23 | 触发测距信号 | 物理引脚16 |
| HC-SR04 (Echo) | GPIO 24 | 接收回波信号 | 物理引脚18 |
| HC-SR04 (Vcc) | 5V Power | 供电 | 物理引脚2或4 |
| HC-SR04 (GND) | 任意GND | 接地 | 同上 |
| SG90舵机 (信号) | GPIO 18 (PWM0) | PWM控制信号 | 物理引脚12 |
| SG90舵机 (Vcc) | 外部5V电源 | 重要!勿接树莓派5V | - |
| SG90舵机 (GND) | 外部电源GND & 树莓派GND | 共地 | - |
| 发药按钮 | GPIO 17 | 输入,内部上拉 | 物理引脚11 |
| 确认按钮 | GPIO 27 | 输入,内部上拉 | 物理引脚13 |
| WS2812B灯带 (Din) | GPIO 21 (PWM) | 数据输入 | 物理引脚40 |
| WS2812B灯带 (5V) | 外部5V电源 | 供电,电流需求大 | - |
| WS2812B灯带 (GND) | 外部电源GND & 树莓派GND | 共地 | - |
接线实操要点与避坑指南:
- 电平匹配:RC522是3.3V器件,必须连接树莓派的3.3V引脚,其IO口也是3.3V电平,与树莓派GPIO直接兼容。
- 电源隔离与共地:这是最容易出问题的地方。舵机和LED灯带工作时电流可能瞬间很大(尤其是多个LED同时亮起或舵机堵转时),如果直接从树莓派的5V引脚取电,极易引起电压骤降,导致树莓派重启。正确做法:使用一个独立的5V/2A以上的电源适配器,其正极(5V)同时接给舵机和LED灯带的VCC,其负极(GND)必须与树莓派的GND(例如物理引脚39)连接在一起,即“共地”。树莓派自身仍由其专用的USB-C电源供电。
- 上拉电阻:两个按钮我配置为使用树莓派GPIO的内部上拉电阻。在代码中,将引脚模式设置为
GPIO.IN,并启用内部上拉pull_up_down=GPIO.PUD_UP。这样按钮一端接GPIO,另一端接地。当按钮未按下时,GPIO读到高电平(1);按下时,GPIO被拉到地,读到低电平(0),避免了外部接电阻的麻烦。 - PWM引脚:控制舵机需要硬件PWM以获得稳定信号,树莓派GPIO 12、13、18、19支持硬件PWM。我选择了GPIO 18。
- 焊接与整理:建议使用杜邦线连接面包板进行原型测试。确认所有功能正常后,再使用排针、排母和导线进行焊接,制作一个整洁的转接板或直接焊接在洞洞板上。良好的线材整理和固定能极大提升系统可靠性,防止因拉扯导致接触不良。
3.2 原型测试与分步验证
不要一次性接好所有线再上电测试!分模块验证是高效排错的不二法门。
- 树莓派基础系统:先单独给树莓派上电,确保能正常启动,SSH或屏幕输出正常。
- 测试RFID:只连接RC522模块,编写一个简单的Python脚本,循环读取卡片ID并打印到终端。验证SPI接口是否已启用(可通过
raspi-config配置),以及接线是否正确。 - 测试超声波传感器:单独连接HC-SR04,编写测距代码,在终端打印实时距离。用手在传感器前移动,观察数值变化是否灵敏、合理。
- 测试舵机:连接舵机(注意电源!),编写代码让舵机在0度和180度之间往复运动。观察转动是否平滑,有无异响。
- 测试按钮与LED:连接按钮和LED灯带,编写代码测试按钮按下触发和LED颜色变化。
每个模块都独立工作正常后,再将它们组合起来,进行集成逻辑测试。
4. 软件逻辑与核心代码实现
硬件是躯体,软件是灵魂。整个系统的智能都体现在Python代码的逻辑中。
4.1 开发环境与依赖库安装
我使用树莓派官方Raspbian系统,并通过SSH进行远程开发。首先安装必要的Python库:
sudo apt update sudo apt install python3-pip sudo pip3 install RPi.GPIO # GPIO控制 sudo pip3 install spidev # SPI通信(用于RFID) sudo pip3 install mfrc522 # RFID库 sudo pip3 install rpi_ws281x # WS2812B LED灯带驱动 sudo pip3 install gpiozero # 可选的更高层GPIO库,这里我们主要用RPi.GPIO对于超声波传感器,我们使用RPi.GPIO库直接操作IO口进行时序控制。
4.2 主程序逻辑流程图与状态机
程序的核心是一个状态机,它定义了系统在不同条件下如何切换行为。我将其设计为以下几个主要状态:
- IDLE (待机):系统启动后的初始状态。LED显示蓝色呼吸灯效。等待RFID认证。
- AUTHENTICATED (已认证):成功刷入授权卡片。LED变为绿色常亮。此时“发药按钮”被激活。
- CUP_CHECKING (检测杯子):用户按下“发药按钮”。LED变为黄色闪烁。系统触发超声波传感器,检查下方是否有杯子。
- DISPENSING (分发中):检测到杯子。LED变为黄色常亮。控制舵机旋转预定角度,完成一次发药动作。完成后进入等待确认状态。
- CONFIRMATION_WAITING (等待确认):LED变为紫色闪烁。等待用户按下“确认按钮”。如果超时(如60秒),则系统复位到IDLE状态,并记录一次“未确认”事件(可闪烁红灯报警)。
- COMPLETED (完成):用户按下“确认按钮”。LED快速闪烁绿色三次,然后恢复IDLE状态。记录一次成功的服药事件。
4.3 关键代码模块解析
以下是核心代码片段的讲解,完整代码可在文末的GitHub链接中找到。
1. 初始化与引脚设置
import RPi.GPIO as GPIO import time from mfrc522 import SimpleMFRC522 from rpi_ws281x import PixelStrip, Color import threading # 引脚定义 (使用BCM编码) PIN_BUTTON_DISPENSE = 17 PIN_BUTTON_CONFIRM = 27 PIN_TRIG = 23 PIN_ECHO = 24 PIN_SERVO = 18 LED_COUNT = 8 # LED灯珠数量 LED_PIN = 21 LED_FREQ_HZ = 800000 LED_DMA = 10 LED_BRIGHTNESS = 100 LED_INVERT = False # 初始化GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(PIN_BUTTON_DISPENSE, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(PIN_BUTTON_CONFIRM, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(PIN_TRIG, GPIO.OUT) GPIO.setup(PIN_ECHO, GPIO.IN) # 初始化RFID读卡器 reader = SimpleMFRC522() # 初始化LED灯带 strip = PixelStrip(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS) strip.begin() # 初始化舵机PWM GPIO.setup(PIN_SERVO, GPIO.OUT) servo_pwm = GPIO.PWM(PIN_SERVO, 50) # 50Hz频率 servo_pwm.start(0) # 初始占空比0,舵机不会动 # 预定义授权卡片ID列表 (实际使用时,通过一个学习程序获取并存入) AUTHORIZED_CARDS = [123456789012, 987654321098] # 替换为你的实际卡片ID # 系统状态变量 current_state = "IDLE" current_user_card_id = None dispense_timer = None2. 超声波测距函数
这个函数负责驱动HC-SR04并计算距离。
def get_distance(): """使用HC-SR04超声波传感器测量距离,返回厘米值""" # 确保触发引脚先输出低电平 GPIO.output(PIN_TRIG, False) time.sleep(0.000002) # 2微秒延迟,确保稳定 # 发送10微秒的高脉冲触发信号 GPIO.output(PIN_TRIG, True) time.sleep(0.00001) # 10微秒 GPIO.output(PIN_TRIG, False) # 等待回波引脚变为高电平,记录开始时间 start_time = time.time() timeout = start_time + 0.04 # 设置40ms超时(对应约6.8米) while GPIO.input(PIN_ECHO) == 0 and start_time < timeout: start_time = time.time() if start_time >= timeout: return -1 # 超时,返回错误 # 回波引脚高电平结束,记录结束时间 stop_time = time.time() timeout = stop_time + 0.04 while GPIO.input(PIN_ECHO) == 1 and stop_time < timeout: stop_time = time.time() if stop_time >= timeout: return -1 # 超时,返回错误 # 计算时间差,声速取34300 cm/s,除以2(往返距离) elapsed_time = stop_time - start_time distance = (elapsed_time * 34300) / 2 # 过滤掉过近或过远的无效值(根据实际安装高度调整) if distance < 2 or distance > 20: return -1 return round(distance, 1)3. 舵机控制函数
控制SG90舵机旋转到指定角度。SG90的PWM控制周期为20ms(50Hz),脉宽0.5ms到2.5ms对应0到180度。
def set_servo_angle(angle): """控制舵机旋转到指定角度(0-180度)""" # 将角度转换为占空比 # 0.5ms脉冲对应0度,2.5ms脉冲对应180度,占空比 = 脉冲时间 / 周期(20ms) duty_cycle = (0.5 + (angle / 180.0) * 2) / 20.0 * 100 servo_pwm.ChangeDutyCycle(duty_cycle) time.sleep(0.5) # 给舵机足够时间转动到位 servo_pwm.ChangeDutyCycle(0) # 停止发送PWM信号,防止舵机抖动和过热4. 主状态机循环
这是程序的核心,在一个while True循环中不断检查状态并执行相应操作。
def main_loop(): global current_state, current_user_card_id, dispense_timer try: while True: if current_state == "IDLE": led_breathing(Color(0, 0, 255)) # 蓝色呼吸 # 检测RFID卡片 card_id, _ = reader.read_no_block() # 非阻塞读取 if card_id in AUTHORIZED_CARDS: print(f"认证成功!卡片ID: {card_id}") current_user_card_id = card_id current_state = "AUTHENTICATED" led_set_color(Color(0, 255, 0)) # 绿色常亮 elif current_state == "AUTHENTICATED": # 检查发药按钮是否被按下(低电平触发) if GPIO.input(PIN_BUTTON_DISPENSE) == GPIO.LOW: time.sleep(0.05) # 简单消抖 if GPIO.input(PIN_BUTTON_DISPENSE) == GPIO.LOW: print("发药请求已接收,开始检测杯子...") current_state = "CUP_CHECKING" led_blink(Color(255, 255, 0), 0.3) # 黄色闪烁 elif current_state == "CUP_CHECKING": distance = get_distance() if distance != -1 and distance < CUP_DETECTION_THRESHOLD: # 例如阈值设为5cm print(f"杯子检测成功!距离: {distance}cm") current_state = "DISPENSING" led_set_color(Color(255, 255, 0)) # 黄色常亮 else: print("未检测到杯子,请放置杯子。") # 可以在此处添加超时逻辑,比如10秒后仍未检测到杯子则返回IDLE状态 elif current_state == "DISPENSING": print("正在分发药品...") # 控制舵机旋转到下一个药仓位置 # 这里需要根据你的药仓设计计算角度。假设8个仓格,每次转45度。 set_servo_angle(current_dispense_angle) current_dispense_angle = (current_dispense_angle + 45) % 360 # 更新下一次角度 time.sleep(1) # 等待药片滑落 print("分发完成,请取药并确认。") current_state = "CONFIRMATION_WAITING" led_blink(Color(255, 0, 255), 0.5) # 紫色闪烁 # 启动确认超时计时器(在另一个线程中) dispense_timer = threading.Timer(60.0, confirmation_timeout) # 60秒超时 dispense_timer.start() elif current_state == "CONFIRMATION_WAITING": # 检查确认按钮 if GPIO.input(PIN_BUTTON_CONFIRM) == GPIO.LOW: time.sleep(0.05) if GPIO.input(PIN_BUTTON_CONFIRM) == GPIO.LOW: print("服药已确认。") if dispense_timer: dispense_timer.cancel() current_state = "COMPLETED" elif current_state == "COMPLETED": # 成功反馈 led_success_animation() # 记录日志(这里可以写入文件或数据库) log_event(current_user_card_id, "MEDICATION_CONFIRMED") # 重置状态 current_user_card_id = None current_state = "IDLE" time.sleep(0.1) # 主循环延迟,降低CPU占用 except KeyboardInterrupt: print("程序被用户中断") finally: cleanup() def confirmation_timeout(): """确认超时回调函数""" global current_state, current_user_card_id print("确认超时!") current_state = "IDLE" current_user_card_id = None led_set_color(Color(255, 0, 0)) # 红色报警 time.sleep(3) # 记录超时事件 log_event(None, "CONFIRMATION_TIMEOUT")5. 辅助函数(LED控制、日志等)
def led_set_color(color): """设置所有LED为同一颜色""" for i in range(strip.numPixels()): strip.setPixelColor(i, color) strip.show() def led_blink(color, interval): """LED闪烁效果(简易版,实际可用线程实现更复杂效果)""" led_set_color(color) time.sleep(interval) led_set_color(Color(0,0,0)) time.sleep(interval) def led_breathing(color): """LED呼吸灯效果(简易版)""" # 实现一个简单的亮度循环,此处省略详细代码 pass def led_success_animation(): """成功确认的LED动画""" for i in range(3): led_set_color(Color(0, 255, 0)) time.sleep(0.2) led_set_color(Color(0, 0, 0)) time.sleep(0.2) def log_event(card_id, event_type): """记录事件到日志文件""" timestamp = time.strftime("%Y-%m-%d %H:%M:%S") with open("/home/pi/medication_log.csv", "a") as f: f.write(f"{timestamp},{card_id},{event_type}\n") def cleanup(): """程序退出前的清理工作""" servo_pwm.stop() led_set_color(Color(0,0,0)) GPIO.cleanup() print("GPIO已清理,程序退出。")实操心得:状态机是编写此类嵌入式交互程序的利器。它让复杂的逻辑变得清晰,每个状态只关心自己的进入条件、执行动作和退出条件。调试时,可以通过打印当前状态和关键变量值,快速定位问题出在哪个环节。
5. 机械组装、调试与优化
当代码在面包板上跑通后,就可以着手将它们和机械部分整合到一个完整的外壳中了。
5.1 药仓与传动机构安装
- 固定舵机:将舵机用螺丝或热熔胶牢固地固定在外壳内部的底座上。确保其输出轴朝向正确,并且有足够的空间进行旋转。
- 安装药仓:将3D打印的药仓中心孔与舵机输出轴对接。我使用了一个小的联轴器(也可以直接用螺丝顶紧)来连接。关键是确保药仓与舵机轴同轴,并且药仓在旋转时不会与周围结构发生摩擦。
- 校准零点:这是关键步骤!在代码中,你需要找到舵机旋转0度时,药仓的哪个仓格正好对准出药口。上电后,手动将药仓转到这个位置,然后运行一个校准程序,将此时舵机的角度定义为“零点”。后续所有发药动作都基于这个零点进行角度累加。
- 安装滑道:出药口下方安装一个倾斜的滑道,引导药片落入杯中。滑道内壁务必光滑。可以用亚克力板折弯制作,或者3D打印。在出药口与滑道连接处,可以贴一小块柔软的海绵或硅胶,起到缓冲和静音的作用。
5.2 传感器与面板集成
- 超声波传感器:将其固定在出药口滑道末端的正下方,探头朝下。测量从探头到放置杯子平面的距离,将这个距离减去杯子的高度,就是“有杯子”时的检测距离。通过多次测试,确定一个可靠的阈值(比如
CUP_DETECTION_THRESHOLD = 5cm)。 - RFID读卡器:将读卡器模块的天线板(通常是那个方形线圈)粘贴在外壳面板上预先开好的孔洞后面。面板材料不能是金属,否则会屏蔽信号。亚克力或木材是理想选择。可以在天线板位置画一个图标,提示刷卡区域。
- 按钮与LED:将轻触开关和LED灯带安装在面板上。LED灯带可以用扩散条覆盖,使光线更柔和均匀。
- 走线与固定:将所有连接线用扎带或线槽规整好,避免它们干扰舵机或药仓的转动。电路板(树莓派、面包板/转接板)用尼龙柱或螺丝固定在外壳内部。
5.3 系统联调与问题排查
组装完成后,进行全流程测试:
- 上电自检:系统启动后,LED应执行预设的启动动画(如流水灯),舵机应归零。观察有无异常发热或异响。
- 功能逐项测试:
- 刷卡:用授权卡片靠近读卡区,LED应变绿,终端打印认证成功信息。
- 无杯检测:不放置杯子,按下发药按钮。系统应进入“检测杯子”状态(黄闪),并持续报“未检测到杯子”或最终超时返回待机。
- 有杯发药:放置杯子,按下发药按钮。系统应检测到杯子(打印距离),舵机旋转,听到药片(可以用几粒豆子模拟)落入杯子的声音,然后进入等待确认状态(紫闪)。
- 确认与超时:按下确认按钮,LED应显示成功动画并返回待机。不按确认按钮,等待60秒,系统应超时报警(红灯)并复位。
- 压力与异常测试:
- 连续快速刷卡、按按钮,测试系统防抖和状态机稳定性。
- 在发药过程中突然断电再上电,看系统能否正常复位(药仓位置可能错乱,需要重新校准)。
- 尝试用未授权卡片刷卡,系统应无反应。
6. 常见问题、优化与扩展思路
在实际制作和测试中,你肯定会遇到一些我踩过的坑。这里列出来,希望能帮你节省时间。
6.1 硬件与机械问题
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 舵机不转或抖动 | 1. 电源功率不足。 2. PWM信号线接触不良。 3. 舵机卡死(机械阻力过大)。 | 1.首要检查:确保舵机使用独立5V电源,且与树莓派共地。 2. 用万用表测量舵机VCC和GND之间电压,动作时是否跌落到4.5V以下。 3. 脱开舵机与药仓的连接,单独测试舵机是否能正常转动。 |
| 药片卡在仓格或滑道 | 1. 仓格尺寸太小或形状不合适。 2. 滑道倾角不够。 3. 药片表面太涩或有静电。 | 1. 重新设计药仓,仓格尺寸至少比最大药片宽2-3mm。 2. 增大滑道角度,确保大于45度。 3. 在滑道内壁涂抹少量食用级滑石粉或使用特氟龙胶带。 |
| 超声波传感器读数不稳定或总是-1 | 1. 供电不稳(特别是与舵机共用电源时)。 2. 触发和回波引脚接反。 3. 传感器前方有障碍物或过于靠近边缘。 | 1. 给HC-SR04的VCC引脚并联一个10uF以上的电解电容,以稳定电压。 2. 仔细核对Trig和Echo引脚连接。 3. 确保传感器探测方向开阔,且安装牢固,避免振动干扰。 |
| RFID读卡距离变短或不灵敏 | 1. 天线板背面有金属或大面积导电材料。 2. 天线板损坏或线圈断开。 3. 树莓派SPI未启用或频率设置不当。 | 1. 确保读卡区面板为非金属材料。 2. 检查天线板焊接点。可以尝试用另一张卡片或模块测试。 3. 运行 ls /dev/spi*检查SPI设备是否存在。在/boot/config.txt中调整spi相关参数(一般默认即可)。 |
6.2 软件与逻辑问题
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 按钮按下无反应或连触发 | 1. 未启用内部上拉电阻,引脚悬空。 2. 机械按键抖动。 | 1. 确认代码中设置了pull_up_down=GPIO.PUD_UP。2. 在代码中添加软件消抖:检测到低电平后,延时20-50ms再次检测,如果仍是低电平才视为有效按下。更可靠的方法是使用 gpiozero库的Button类,它内置了消抖功能。 |
| 状态机逻辑混乱,状态乱跳 | 1. 多个按钮或传感器检测在循环中阻塞。 2. 没有处理好并发事件(如发药过程中又刷卡)。 | 1. 确保主循环time.sleep的延迟很短(如0.1秒),保证响应及时。2. 在关键状态(如DISPENSING)下,忽略其他无关输入事件。或者使用多线程,将RFID读取、超声波检测等放在独立线程中,通过线程安全的队列与主状态机通信。 |
| 舵机角度不准,每次位置有偏差 | 1. PWM占空比计算或控制不精确。 2. 舵机存在“死区”或回差。 3. 电源电压波动导致舵机基准变化。 | 1. 使用硬件PWM(如GPIO.PWM)而非软件模拟。2. 每次发药动作后,让舵机回到一个固定的“归零”位置,而不是依赖相对角度累加。可以增加一个光电传感器或微动开关作为“零点”传感器进行物理校准。 |
| 程序运行一段时间后卡死或无响应 | 1. 内存泄漏(在循环中不断创建对象)。 2. 文件读写或网络操作阻塞。 3. 异常未捕获导致线程崩溃。 | 1. 检查代码,确保在循环外初始化对象,循环内只调用方法。 2. 将日志写入等IO操作放在单独的线程,或使用异步方式。 3. 用 try...except包裹可能出错的代码段(如传感器读数),并记录错误日志。 |
6.3 功能优化与扩展方向
这个基础版本已经可用,但还有很大的提升空间:
- 增加显示与交互:添加一块小OLED或LCD屏幕,可以显示当前时间、下次服药时间、药品名称、欢迎语等。配合旋转编码器或更多按钮,可以实现设置菜单。
- 联网与数据同步:让树莓派连接Wi-Fi。可以:
- 将用药记录实时上传到云端数据库(如InfluxDB)或物联网平台(如Home Assistant, Blynk)。
- 实现远程提醒:如果用户超时未取药,通过手机APP、短信或语音电话(集成Twilio等API)提醒。
- 支持远程查看药盒状态(剩余药量、电池电量等)。
- 多用户与药品管理:为每个RFID卡关联一个用户配置文件。在代码中维护一个字典或小型数据库(如SQLite),记录每个用户对应的药品名称、每次剂量、服药时间表。系统可以根据时间自动提醒对应用户服药。
- 药量监测:在药仓底部增加一个重量传感器(如HX711模块+称重传感器),实时监测每个仓格的剩余药量,并在快用完时提醒补充。
- 电源管理:加入锂电池和充电管理模块,实现断电续航。配合树莓派的休眠/唤醒功能,可以极大延长待机时间。
- 外观与人性化设计:优化外壳设计,使其更美观、更坚固。增加语音提示功能(使用USB小音箱和TTS引擎),对每一步操作进行语音引导,对视力不佳的用户更友好。
这个基于树莓派的智能药盒项目,从构思到实现,是一个典型的硬件、软件、机械结合的嵌入式系统实践。它没有用到特别高深的技术,但完整地走通了从需求分析、方案设计、部件选型、编程调试到组装测试的全流程。过程中遇到的每一个问题,都是宝贵的经验。希望这份超详细的拆解,能给你带来启发,也欢迎你基于这个框架,创造出功能更强大、更适合自己需求的智能药盒。