十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

单片机智能药盒设计:从选型到状态机编程实战

单片机智能药盒设计:从选型到状态机编程实战 简介一份面向单片机/嵌入式学习者的智能药盒设计说明文档适合作为课程设计、毕业设计或相关产品开发的参考资料。该设计以STC90C516单片机为核心结合LCD1602液晶显示与定时/计数器实现时钟显示、每天四次用药提醒与四种药品用量记录等功能硬件结构与软件流程描述完整。资源为单个doc格式文档压缩包大小约3.74MB内容涵盖系统硬件电路设计、各模块工作原理、软件编程思路、系统调试结果及应用前景分析可直接用于理解整个智能药盒的从设计到调试的完整过程。该文档已有416人浏览学习对需要快速掌握单片机系统开发流程、或搭建同类提醒装置设计框架的读者具有实用参考价值。1. 基于单片机的智能药盒到底在解决什么问题一个看起来只是“定时响铃”的药盒实际做出来却不简单用户每天要吃的药可能不止一种间隔不同、条件不同家人或护工还想知道老人有没有真的按下去断电之后时间要能保住服药记录不能清零。这些问题落到单片机上就变成时钟管理、按键输入、状态机、掉电存储和低功耗设计。拿 STC89C52RC 这类 51 内核做核心能覆盖大部分需求资料和元件好找要接语音或蓝牙就换 STM32。这里按设计说明的常见结构从选型、电路、代码到调试顺序讲一遍适合课程设计、毕业设计和养老产品原型开发。2. 药盒硬件设计从单片机选型到时钟与掉电保存电路智能药盒的硬件设计不需要追求主频高关键是外设之间的电平、时序和掉电行为要匹配。下面从选型开始到时钟源、EEPROM、电源复位把最小系统讲透。2.1 单片机选型51 内核够用STM32 留给扩展先列外设数码管或 LCD、按键、蜂鸣器、DS1302、EEPROM再加一个串口调试。这些外设对主控的要求主要是 IO 数量、定时器数量和 5V/3.3V 电压兼容性。51 内核的 STC89C52RC 有 32 个 IO、3 个定时器、6 个中断源应付“4 位数码管 4 个按键 蜂鸣器”绰绰有余。更重要的是51 单片机硬件设计资料非常多课程设计和毕业设计常用这个型号遇到问题容易查参考。如果后续要加语音识别、Wi-Fi 或 TFT 屏STM32F103 系列会更合适因为它的定时器带 PWM 和捕获SDIO 和 FSMC 也能让外部存储器接起来更省事。对比项STC89C52RC51 内核STM32F103C8T6Cortex-M3定时器资源3 个 16 位4 个 16 位带 PWM/捕获IO 数量32 个5V 兼容37 个3.3V 电平RTC 方案外接 DS1302内部 RTC 后备电池EEPROM 方案外接 AT24C02内部 Flash 模拟或外接开发门槛寄存器少上手快需要理解时钟树、外设库选 51 时要注意复位引脚和烧录接口STC 单片机复位一般是高电平复位10μF 电容接 VCC、10kΩ 电阻接 GND烧录用串口但 P3.0/P3.1 如果被复用为按键下载时需要把它们断开或做成跳线。选 STM32 时则要确认 BOOT0 引脚下拉下载器用 SWD 只需占用 PA13/PA14比较简单。2.2 时钟源用 DS1302 还是直接用定时器智能药盒的核心时序来自时间基准。如果只做课堂演示单片机定时器累计秒数完全可以但产品级药盒要求断电后时间不丢、一天误差不超过 1 秒定时器很难满足。常见做法使用 DS1302 实时时钟芯片它自带 32.768kHz 晶振接口和涓流充电电路主电源掉电后切换到纽扣电池继续走时。需要注意 DS1302 的 CE、IO、SCLK 三根线都要接 10kΩ 上拉否则长时间运行后会产生偶发乱码。晶振的地线要单点接地两边电容尽量靠近晶振引脚。DS1302 引脚单片机引脚说明CEP3.5片选高有效SCLKP3.6串行时钟空闲低IOP3.7双向数据VCC25V主电源VCC13V 电池备用电源GNDGND共地写时序前先做一次“关闭写保护”操作否则后续写时间会失败。DS1302 内部寄存器 0x8E 是写保护控制位写 0x00 解锁。读时间时最好连续读 7 个字节避免在秒进位时读到不一致的数据如果只是读秒再校准会出现分秒跳变的边界问题。有些设计会利用 DS1302 的 1Hz 脉冲输出接到单片机外部中断用来做秒标志比主循环查 IO 更精准。2.3 掉电保存EEPROM 存什么、什么时候写药盒的掉电保存内容不只是“当前时间”更多是“某次提醒是否已服药”。用户按完确认键后状态必须写入 EEPROM否则断电回来又变成未服。写入时机要在按键确认的瞬间而不是每个循环都写。AT24C02 是常用的 EEPROM容量 256 字节I2C 接口地址由 A0/A1/A2 决定。写一个字节要等 5ms 写周期写满一页后更要留出擦写时间。// 模拟 I2C 写一个字节到 AT24C02 void eeprom_write_byte(unsigned char dev_addr, unsigned char mem_addr, unsigned char dat) { i2c_start(); i2c_send_byte(dev_addr); // 器件地址高 7 位 写位 0 i2c_send_byte(mem_addr); // EEPROM 内偏移地址 i2c_send_byte(dat); // 要保存的数据 i2c_stop(); delay_ms(5); // 写周期 tWR至少 5ms // 回读校验防止写失败后静默丢记录 if (eeprom_read_byte(dev_addr, mem_addr) ! dat) { eeprom_write_byte(dev_addr, mem_addr, dat); // 重试一次 } }dev_addr 是 0xA0 还是 0xA2取决于 A0/A1/A2 接地还是接高。I2C 发送时每发送 8 位后要等待从机的 ACK这个细节容易漏。delay_ms(5) 是保守写法也可以用轮询写状态寄存器代替延时。回读校验放在重试前如果校验失败立即重写一次连续失败两次需要在串口打印错误码不要无限重试。2.4 电源和复位5V 和 3.3V 混合设计时的隐藏坑如果药盒主控是 51而显示或传感器是 3.3V 模块就需要电平转换不能直接把两个电源并联。简单场合用两个 10kΩ 电阻分压只能单向传递想双向通信还是用 TXS0102 这类电平转换芯片最稳。掉电检测可以用一个 100kΩ 到 1MΩ 的电阻分压网络将电源输入压到单片机 ADC 或 IO 口当电压低于阈值时先触发外部中断在中断里把当前状态和服药记录写入 EEPROM。复位方面STC 的复位引脚接 10μF 电容到 VCC、10kΩ 电阻到 GND手动复位按钮并联在电容两端。使用 STC 系列时P3.0/P3.1 用于串口下载而这两个引脚经常被复用为按键或传感器。设计时最好在这两个引脚上串 1kΩ 隔离电阻或者在 PCB 上留跳线避免下载时外部电路把下载时序拉低。调试时先串口打印版本号和状态比频繁烧录更高效。3. 智能药盒软件架构状态机与多路定时提醒硬件确定后软件要解决的核心问题是多组提醒时间怎么管理用户按键如何影响流程以及怎么不让定时器中断占满 CPU。这里用状态机作为骨架按定时器中断和主循环分工来展开。3.1 服药流程的状态划分与状态转移表状态机适合描述药盒这种交互确定、状态有限的场景。把系统划分为待机、响铃、已服、漏服、稍后提醒五个状态每个状态只处理自己能响应的事件。状态转移表是写代码前最重要的图表也是设计说明里最值得写清楚的部分。当前状态事件下一状态动作IDLE当前时间等于计划时间RING启动蜂鸣器、点亮指示灯RING确认键按下TAKEN记录本次已服关闭蜂鸣器RING超时未按键如 5 分钟MISS记录漏服关闭蜂鸣器并置告警MISS用户补按确认TAKEN更新记录为补服RING“稍后”键按下SUPPRESS关闭本次响铃不再提醒“RING”状态下的超时计时不要用独立 delay而是用一个计数器记录响铃持续多少秒在每秒时钟标志置位时累加达到 300 秒就触发漏服。这样响铃期间用户按键扫描和显示刷新都还能正常执行。typedef enum { SYS_IDLE 0, SYS_RING, SYS_TAKEN, SYS_MISS, SYS_SUPPRESS } SysState; typedef struct { unsigned char hour; unsigned char min; unsigned char slot; // 药槽编号 0-7 unsigned char active; // 1 表示启用 } MedPlan;SysState 用枚举而不是宏是为了在调试器里直接看到状态名。MedPlan 里把时、分和药槽编号放在一起比较计划时只需要遍历数组不需要额外解析时间字符串。3.2 多路定时提醒用最小 tick 驱动计划列表药盒一般支持 3 到 8 组计划。让定时器产生稳定的 1ms 或 50ms tick主循环只在 tick 到点时检查和硬件相关的工作。以 51 单片机、12MHz 晶振为例定时器 0 工作在方式 1是 16 位定时器溢出后需要手动装初值。#define TIMER0_RELOAD_H 0x3C #define TIMER0_RELOAD_L 0xB0 void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // 定时器 0 工作方式 1 TH0 TIMER0_RELOAD_H; TL0 TIMER0_RELOAD_L; ET0 1; // 开定时器 0 中断 TR0 1; // 启动定时器 EA 1; // 开总中断 } void Timer0_ISR(void) interrupt 1 { TH0 TIMER0_RELOAD_H; // 手动装初值 TL0 TIMER0_RELOAD_L; tick_50ms; if (tick_50ms 20) { tick_50ms 0; one_second_flag 1; } }定时器 0 在方式 1 下是 16 位计数器从 0x3CB0 计数到 0xFFFF 再溢出需要 50000 个机器周期12MHz 晶振下机器周期是 1μs所以 50ms 中断一次。20 次就是 1 秒。这里的“秒”只用于喂狗和状态机里的超时不用于实际时间显示实际时间从 DS1302 读取避免累计误差。时间比较在主循环里做if (one_second_flag) { one_second_flag 0; read_ds1302_time(cur_time); for (i 0; i MAX_PLAN; i) { if (plans[i].active plans[i].hour cur_time.hour plans[i].min cur_time.min last_checked_min ! cur_time.min) { enter_state(SYS_RING, plans[i].slot); } } last_checked_min cur_time.min; }这里用 last_checked_min 做去重防止同一分钟内第二次循环再次触发。如果计划循环里还要检查“是否已执行过本日”可以在 MedPlan 里加一个日期字段每次比较日期不同才执行避免跨天重复。3.3 前后台结构中断只做标记主循环处理任务药盒代码不复杂但很容易把所有事情都塞进中断。中断里做延时、读 DS1302、驱动数码管都会让主循环卡顿。推荐的前后台结构是定时器中断只维护 tick 和一个秒标志主循环按顺序执行按键、显示、状态机、时间同步。这样即使一个任务阻塞了 20ms其他任务依然有响应余地。void main(void) { hardware_init(); load_plans_from_eeprom(); while (1) { key_task(); // 5ms 调用一次按键防抖 display_task(); // 动态扫描每轮刷新一位 state_machine(); // 根据状态和事件切换 if (one_second_flag) { one_second_flag 0; check_plan(); // 更新时间并比较计划 } feed_watchdog(); // 主循环喂狗 } }喂狗位置放在主循环最后不能放在中断里。如果主循环因为某个函数死循环中断仍然在跑看门狗会失效。如果换成 STM32HAL_TIM_PeriodElapsedCallback里也应只置位one_second_flag不要在回调里调用 printf 或驱动数码管。STM32 的定时器自动重装值不能设置为 0否则不会溢出。4. 人机交互与可靠性按键防抖、显示刷新和数据记录硬件和主框架定了之后最容易出问题的就是按键和显示。它们直接跟人打交道时序稍有不慎就被用户感知。这一章把接触问题整理一遍顺带补一个 TF 卡记录日志的扩展设计。4.1 按键防抖用状态机代替 delay 消抖按键抖动在按下和释放瞬间会产生 5~15ms 的电平毛刺。delay 消抖虽然代码短但会让整个系统停顿。状态机消抖的做法是每 5ms 采样一次按键电平连续两次采样到稳定按下才认为是一次有效按下。这样既不阻塞主循环又能滤除抖动。unsigned char key_scan(void) { static unsigned char state 0; unsigned char key_in read_key_level(); unsigned char event KEY_NONE; switch (state) { case 0: // 空闲 if (key_in KEY_ACTIVE) state 1; break; case 1: // 第一次按下等待确认 if (key_in KEY_ACTIVE) state 2; else state 0; break; case 2: // 连续两次按下产生事件 event KEY_PRESSED; state 3; break; case 3: // 等待释放避免重复触发 if (key_in KEY_INACTIVE) state 0; break; } return event; }这个函数需在 5ms 定时标志置位时调用一次。如果项目里按键很多可以扩展成一次扫描 4 个键状态机共用一套时间基准。防抖窗口 10~20ms 比较合适采样周期太短抖动毛刺会被当成长按键太长则用户快速连击会丢事件。4.2 数码管动态扫描段选和位选的刷新顺序药盒显示最常见的是 4 位数码管用来显示时间或药槽号。动态扫描的原理是让各位数轮流点亮利用视觉暂留。刷新顺序必须“先消隐、再位选、后段码”否则会在切换瞬间看到前一位的拖影。位选消耗电流较大通常需要加三极管或 ULN2003 驱动。void display_task(void) { static unsigned char pos 0; P0 0xFF; // 关段码消隐 P2 pos_select[pos]; // 位选低电平有效 P0 sg_code[display_buf[pos]]; // 送当前位段码 pos; if (pos 4) pos 0; }display_task 在主循环每轮调用一次4 位扫完约耗时几毫秒。扫描频率低于 50Hz 时会看到闪烁高于 100Hz 好处不大反而占用 CPU。所以主循环一轮时间要控制在 5ms 以内若某些模块处理时间长需要把长任务切成小片段执行。4.3 看门狗与低功耗喂狗位置和休眠唤醒长期运行的智能药盒必须看门狗。独立看门狗超时后会对芯片复位避免程序跑飞后药盒一直不工作。喂狗位置在主循环末尾理由前面已经说过。常见参数WDT 预分频超时时间约243ms8171ms32684ms建议选 32 分频主循环正常情况下 100ms 以内喂一次。低功耗场景下主循环进入休眠前要关闭看门狗或喂狗后休眠再用定时器或外部中断唤醒。注意很多 51 单片机看门狗一旦开启无法软件关闭只能靠复位重新初始化所以设计时要留一个跳线开关在调试器烧录时关闭看门狗。供电方面51 正常模式大约 5~10mADS1302 大约是微安级数码管峰值电流却可能到几百毫安所以别用普通干电池长期供电用锂电池时要按数码管峰值电流选择足够容量的 LDO。4.4 扩展把服药日志以 CSV 写进 TF 卡如果药盒要溯源或者给医生看可以把每次操作追加到 TF 卡。常见做法是使用 FATFS 文件系统以 CSV 格式记录每行是一段操作流水。追加写入时先打开文件移到文件末尾再写入一行写完立即关闭。FIL f; FRESULT res; res f_open(f, 0:/dose_log.csv, FA_OPEN_ALWAYS | FA_WRITE); if (res FR_OK) { f_lseek(f, f_size(f)); // 移动到文件末尾 f_printf(f, %04d-%02d-%02d,%02d:%02d,%d,%s\r\n, date.year, date.month, date.day, time.hour, time.min, slot, (taken) ? TAKEN : SUPPRESS); f_close(f); }f_lseek 到文件末尾使用 f_size 返回值不用 f_eof 判断。f_printf 依赖 FATFS 的字符串输出宏使用前需要在 ffconf.h 里打开 _USE_STRFUNC。每次操作都开关文件会降低写入速度但换来的是断电情况下 CSV 最多丢失最后一行不会破坏文件系统结构。5. 验证智能药盒时序用逻辑分析仪抓 DS1302、用示波器校准定时器设计说明写到“时序正常”容易现场查问题却常耗掉半天。这里给两个调试方法都是做药盒时最常用的验证手段。5.1 用逻辑分析仪抓 DS1302 的读写时序DS1302 乱码最常见的场景是刚上电读时间正常运行十几分钟后恢复出 0xFF。处理办法不是改代码盲试而是抓波形。逻辑分析仪的三个通道分别接 CE、SCLK、IO采样率 1MHz抓一次读时间的过程。重点看两个参数一是 CE 拉高到第一个 SCLK 上升沿之间的建立时间至少要留出 4μs二是每个 SCLK 上升沿前 IO 上的数据已经稳定不能和时钟沿同时翻转。如果波形里 SCLK 低电平和数据跳变几乎重合就把 SCLK 翻转之间的 delay 从 1μs 加大到 5μs 或 10μs。很多国产兼容 DS1302 芯片对最小时序要求并不苛刻但过快的电平翻转容易产生毛刺导致移位寄存器偶发错位。读时间时最好连续读 7 个字节并且在读出后判断数据合法性比如秒寄存器的 bit7 是暂停位小时寄存器的高 2 位不能超过 12 或 23。如果读出的小时是 25直接丢弃本次结果回到上次有效值。这样即使某一帧被干扰也不会把乱时间写入状态机。5.2 用示波器校准定时器溢出波形如果设计里没有 DS1302而是直接用单片机定时器走时就需要验证 50ms 中断是否精确。在定时器中断服务里加一条翻转 P1.0 的语句示波器测 P1.0 的方波周期正常应为 100ms。实测发现周期偏长或偏短要先排除晶振负载电容的影响而不是急着改重装值。12MHz 无源晶振若配 30pF 负载电容起振频率可能偏低导致走时偏慢。用手头现有的 15pF 或 20pF 电容替换后再测方波周期。若误差固定比如实测周期 100.1ms一天误差约 86.4 秒说明每天快太多软件补偿需要在每分钟加一个计数微调而不是靠几次中断叠加去修正。更稳妥的办法仍是加外部 RTC。最后还有一个容易忽略的细节给 DS1302 的 VCC1 加一颗 0.1μF 去耦电容并放在电池座附近很多“走时乱”的故障就是这颗电容缺失导致的。本文还有配套的精品资源点击获取
返回列表