
简介基于单片机的智能药盒设计说明文档面向电子设计、嵌入式开发学习者和单片机课程设计人员系统讲解以STC90C516为核心、LCD1602液晶显示与定时/计数器协同的智能药盒完整设计方案。文档从硬件结构入手详细阐述STC90C516内部结构与引脚功能、LCD1602硬件电路与显示原理、蜂鸣器提醒机制并给出模块化软件设计思路和基于C语言的编程思想同时覆盖系统启动、时钟运行、用药提醒与参数设置等完整工作流程以及每日四次用药、四种用药量的提醒逻辑涉及家庭用药、医院管理、药店管理等应用前景。资源为1个doc文档压缩包大小3.74MB正文包含摘要、目录及各章节细节涉及STC90C516参数、LCD1602命令、定时/计数器和蜂鸣器原理等知识点便于读者跟进设计步骤并迁移到同类项目。已有416人学习浏览适合需要撰写课程设计报告、准备毕业设计或入门单片机智能药盒开发的读者参考。1. 基于单片机的智能药盒设计说明先想清楚要给谁看基于单片机的智能药盒本质是一个时间驱动的小型嵌入式系统主控读实时时钟和预设服药计划比对到点就驱动显示屏、蜂鸣器和指示灯提醒同时记录服药状态。系统的复杂度不在算力而在“按时、可靠”四个字——时钟准不准、按键灵不灵、掉电后计划还在不在。设计说明的读者通常有两类学生要把原理图、程序和测试数据整理成能过答辩的文档工程师想用最低成本验证提醒功能。两条路需求不同硬件选型和软件框架是同一套。下文按选型、电路、程序、排错、验证的顺序展开正好也是一份设计说明应有的章节顺序。2. 单片机与模块选型智能药盒的硬件底座怎么定2.1 主控选 STC89C52 还是 STM32先看外设账智能药盒需要的资源其实很少一组 IO 驱动显示一路三线读取实时时钟几个按键输入一个蜂鸣器输出外加掉电保存用药计划。这些资源 51 单片机完全够用而 STC89C52RC 作为 51 系列里最常见的型号跑 12MHz 晶振、5V 供电IO 直驱 LCD1602 和独立按键都没有压力。如果换 STM32F103 这类 32 位主控优势在片上 RTC、多定时器和更快的 Flash 操作但物料成本、PCB 布局难度和代码量都上去了。我的建议是课程设计用 STC89C52RC 或 STC15 系列就足够答辩时能把时序逻辑讲清楚如果课题要求用 STM32重点应放在低功耗和 RTC 校准上而不是在药盒上堆外设。对比项STC89C52RCSTM32F103C8T6内核/主频8051 / 12MHzCortex-M3 / 72MHz片内 EEPROM有IAP 区需外挂或自建模拟RTC需外接 DS1302片上 RTC开发环境Keil C51Keil MDK / STM32CubeMX适合场景低成本功能验证、课程设计低功耗、多任务产品原型2.2 模块拆解显示、时钟、提醒、存储四件套显示方案首选 LCD1602驱动资料多、调试直观用 4 位模式只占 6 个 IO如果嫌字少换 LCD12864 或者 OLED 都可以但代码量明显增加。纯用数码管也行段选位选加动态扫描只是显示“第几格药、什么时间”这类信息时体验差一些。时钟模块用 DS1302自带 31 字节 RAM 和涓流充电电路外接 3V 纽扣电池后主电源断开时间不丢这是药盒的刚需。提醒部分用一个有源蜂鸣器加三极管驱动软件只需给高电平就响比无源蜂鸣器省掉一笔频率生成的代码同时并一个 LED 作视觉提示照顾不方便听声音的场景。存储直接复用 STC 片内 EEPROM省掉外挂 24C02 的一条 I2C 总线也少一类调试问题。2.3 信号流向从按键到提醒的一次完整闭环整个系统的信号流是一条闭环按键设置计划 → 计划写入 EEPROM → 主循环读 DS1302 当前时间 → 与计划比对 → 命中窗口则拉高蜂鸣器引脚并刷新 LCD。代码上体现为一个主循环加一个定时器中断中断产生节拍主循环做逻辑判断。配套的数据结构先定下来后面程序和电路都好对应// plan.h 用药计划的数据结构 typedef struct { unsigned char hour; // 提醒小时 0~23 unsigned char minute; // 提醒分钟 0~59 unsigned char taken; // 0未服1已服 unsigned char enable; // 1启用这条提醒 } PlanItem; PlanItem plans[4]; // 每天最多 4 个时段用一句话概括选型原则能用 IO 完成的事情就不要引进串行总线能用片内资源解决的就不要外挂芯片。药盒的可靠性来自“简单”而不是“高级”。3. 硬件电路与参数LCD、DS1302 和蜂鸣器的接线表3.1 LCD1602 四线模式接线与对比度参数LCD1602 常见接法是 8 位并行但智能药盒引脚紧张用 4 位模式更省D4~D7 接 P2.4~P2.7RS、E 接 P2.0、P2.2RW 在只写场景下直接接地。VO 脚通过一个 10K 电位器到地用来调对比度这条最容易被忽略——焊好不显示十有八九是 VO 悬空或电压不对。背光的 A/K 脚要串一个 100~220Ω 限流电阻再接到 5V直接怼电源会让背光寿命明显缩短。E 脚是数据锁存信号写时序时先拉高再拉低保持最小脉冲宽度 450ns12MHz 晶振下一条 NOP 约 1μs两条 NOP 就够延时太长反而拖慢刷新。// lcd1602_pin.h sbit LCD_RS P2^0; // 寄存器选择0 命令 / 1 数据 sbit LCD_RW P2^1; // 读写选择接 GND 后固定为写 sbit LCD_E P2^2; // 使能脉冲高电平锁存数据 sbit LCD_D4 P2^4; // 4 位数据的高四位 sbit LCD_D5 P2^5; sbit LCD_D6 P2^6; sbit LCD_D7 P2^7;所有引脚定义集中在一个头文件里后面换主控或改板子只动这一处。RW 接地后读忙标志就不能用了用延时替代每条命令后delay(2ms)基本覆盖执行时间。3.2 DS1302 时钟电路晶振负载电容和备用电池DS1302 是三线时序SCLK、IO、CE 三个脚各串一个 100Ω 电阻再接主控用来防毛刺。最关键的是 32.768kHz 晶振两端必须配 6pF~12.5pF 负载电容电容不匹配会出现走时偏快或偏慢而且温度漂移明显。很多“时间越走越快”的案例都不是程序问题是负载电容没装。我一般会在 X1/X2 两端各焊 10pF 电容实测走时后再微调容值。备用电池用 CR2032 加一颗 1N4148 二极管隔离到 VCC1主电源 VCC2 有电时电池不耗电。DS1302 的涓流充电功能默认关闭不要去寄存器里写 0x90 开启它锂纽扣电池过充鼓包是实打实的事故。注意DS1302 的涓流充电控制寄存器默认是 0x00保持这个值不要改动接锂纽扣电池时尤其如此。DS1302 引脚接法参数X1/X232.768kHz 晶振 负载电容6~12.5pF推荐 10pFCE/RST接 P3.5串联 100Ω并 10K 上拉SCLK接 P3.6串联 100Ω—IO接 P3.7串联 100Ω双向口读前要先置 1VCC1CR2032 1N41483VVCC25V 主电源与 VCC1 之间二极管隔离3.3 蜂鸣器驱动三极管参数与续流二极管蜂鸣器工作电流在 20~30mA51 单片机 IO 口驱动能力有限不能直接带。常见做法是用 NPN 三极管 S8050 做开关IO 通过 1K 基极限流电阻接 B 极蜂鸣器接在 VCC 和 C 极之间E 极接地。有源蜂鸣器给高电平就响无源蜂鸣器需要 2~4kHz 方波。无源蜂鸣器是感性负载关断瞬间会产生反向电动势必须在蜂鸣器两端反向并联 1N4148 续流二极管否则三极管容易被击穿。有源蜂鸣器内部自带振荡基本没有这个问题但从可靠性出发还是建议预留二极管焊盘。按键输入用 10K 上拉电阻加 104 电容硬件先削掉一层抖动软件再做一层消抖两层配合才是完整方案。4. 软件实现定时器节拍、按键状态机与服药判定4.1 主程序框架一个 tick一次循环智能药盒的程序适合时间片轮询定时器中断每 5ms 产生一个节拍并置标志位主循环检测到标志后处理当前周期的任务。这样按键扫描、LCD 刷新、DS1302 读取共享同一个时间基准不会因为某处延时函数把其他模块卡死。// main.c 主循环骨架 void main(void) { unsigned char tick 0; Timer0_Init(); // 定时器05ms 中断一次 LCD_Init(); DS1302_Init(); EEPROM_ReadPlan(plan);// 上电把计划从片内EEPROM读回 while (1) { if (flag_5ms) // 中断里置1主循环清零 { flag_5ms 0; tick; Key_Scan(); // 每 5ms 扫一次按键 if (tick % 4 0) // 每 20ms 刷一次 LCD LCD_Refresh(); if (tick % 200 0) // 每 1 秒读一次 DS1302 { DS1302_ReadTime(now); Check_Alarm(now, plan); } } } }新增一个“每 500ms 闪一次 LED”的任务就加一行if (tick % 100 0)扩展性很好。注意 DS1302 的读写要在主循环里做不要在中断服务函数里碰三线时序12MHz 下读 7 字节时间约 200μs5ms 的时间片足够。// timer0.c 定时器 0 初始化12MHz 晶振方式 15ms 中断 void Timer0_Init(void) { TMOD 0xF0; // 清空定时器 0 的模式位 TMOD | 0x01; // 方式 116 位定时器 TH0 (65536 - 5000) / 256; // 5000 个机器周期 TL0 (65536 - 5000) % 256; // 12T 模式下 1 机器周期 1μs ET0 1; // 开定时器 0 中断 EA 1; // 开总中断 TR0 1; // 启动定时器 }12MHz 晶振配合 12T 模式一个机器周期正好 1μs重装值 5000 就是 5ms。如果换 11.0592MHz 晶振重装值要按 4608 左右计算误差在可接受范围但秒累计偏差仍需要靠后面说的软件秒补来修正。4.2 按键消抖50ms 稳定计数与状态切换硬件电容只滤掉高频毛刺机械抖动仍有几毫秒到十几毫秒软件必须再做一次消抖。用延时函数阻塞消抖会卡住主循环正确做法是状态机每次 5ms 采样连续 10 次读到低电平才认为按键真的按下。// key.c 状态机按键扫描每 5ms 调用一次 bit key_down 0; // 按键事件标志 void Key_Scan(void) { static bit press_state 1; // 1释放0已触发按下 static unsigned char cnt 0; if (KEY_PIN 0) // 采样到低电平 { if (cnt 10) // 连续 10 次50ms都为低 { if (press_state 1) // 之前处于释放态 { key_down 1; // 只产生一次事件 press_state 0; } } } else { cnt 0; // 出现高电平计数器清零 press_state 1; // 恢复释放态 } }50ms 的稳定时间对机械按键的抖动余量足够同时又不至于让快速连按失效。key_down是事件标志主循环处理完动作后手动清零press_state保证一次按下只触发一次按住不放不会重复报警。读 P3 口前要记得先写 1这是 51 准双向口和 STM32 输入模式最大的习惯差异。4.3 服药提醒判定时间窗口而不是时间点直接比较“当前时分 计划时分”会因为秒位和读取时机漏报正确做法是开时间窗口。计划设在 08:00只要当前落在 07:55 到 08:05 之间就触发提醒并用一个标志位防止窗口期内重复响铃。// alarm.c 时间窗口判定 #define WINDOW_MIN 5 void Check_Alarm(Time *now, Plan *plan) { static bit window_active 0; // 当前窗口是否已触发 int now_min now-hour * 60 now-minute; int plan_min plan-hour * 60 plan-minute; int delta now_min - plan_min; if (delta -WINDOW_MIN delta WINDOW_MIN !plan-taken !window_active) { Buzzer_On(); LED_Flash(1); LCD_ShowMsg(Please take med!); window_active 1; // 同一窗口只响一次 } // 离开窗口复位窗口标志 if (delta WINDOW_MIN || delta -WINDOW_MIN) window_active 0; }窗口宽度 5 分钟是经验值太窄容易漏太宽会提前响。比较时把时分统一转成分钟数避免小时借位产生负数错判plan-taken在响铃后由“确认服药”按键置 1跨天时在主循环检测到时间归零后统一清零并回写 EEPROM。5. 掉电存储与硬件排错EEPROM、晶振和驱动电路5.1 用药计划存进 STC 片内 EEPROM 的流程STC89C52RC 片内有约 2KB 的 IAP/EEPROM 区读写要走 ISP/IAP 特殊功能寄存器。写操作前必须关 EA 总中断否则写入过程中约 5ms 的中断响应会导致写失败读操作不受此限制。// eeprom.c STC89C52 片内 EEPROM 单字节读写 void IAP_Disable(void) { IAP_CONTR 0; // 关闭 IAP 功能 IAP_CMD 0; IAP_TRIG 0; } unsigned char EEPROM_Read(unsigned int addr) { unsigned char dat; IAP_CONTR 0x80; // 使能 IAP IAP_CMD 0x01; // 读命令 IAP_ADDRL addr 0xff; IAP_ADDRH addr 8; IAP_TRIG 0x5A; IAP_TRIG 0xA5; // 触发序列顺序不能反 dat IAP_DATA; IAP_Disable(); return dat; } void EEPROM_Write(unsigned int addr, unsigned char dat) { EA 0; // 写期间关总中断 IAP_CONTR 0x80; IAP_CMD 0x02; // 写命令 IAP_ADDRL addr 0xff; IAP_ADDRH addr 8; IAP_DATA dat; IAP_TRIG 0x5A; IAP_TRIG 0xA5; EA 1; IAP_Disable(); }注意IAP_TRIG 的 0x5A、0xA5 两个字节必须依次写入顺序写反不会报错但操作不生效。STC 的 EEPROM 按扇区擦除每扇区 512 字节修改前要先整扇区擦除再写不能像 RAM 那样直接改写单字节。擦除命令是 IAP_CMD 0x03同样要关中断并走触发序列。我一般会把用药计划放在固定扇区修改时整体重写第一次上电先检查“初始化标志”读到全 0xFF 的脏数据就写默认计划。5.2 三个经典故障的排查顺序故障现象排查顺序常见原因LCD 不显示对比度电位器 → E 脚时序 → RW 接地VO 悬空、E 脉冲太短、背光限流电阻过大时间不走/走时乱DS1302 晶振负载电容 → 三线时序 → 电池电压负载电容不匹配、IO 上拉缺失、VCC2 纹波大蜂鸣器不响三极管基极电压 → 续流二极管方向 → 驱动电平基极电阻过大、无源蜂鸣器没有方波LCD 不显示先量 VO 脚电压正常在 0~1V 之间背光亮但无字就用逻辑分析仪看 E 脚有没有 450ns 以上的高电平脉冲。DS1302 时间走得快先量 X1 脚波形幅值小于 0.5V 基本就是负载电容不对。蜂鸣器不响先量三极管 B-E 极电压按下测试键时应接近 0.7V如果 IO 高电平被拉到 3V 以下说明主控电源被拉垮查电源滤波电容而不是换蜂鸣器。5.3 看门狗与上电时序量产级药盒一定要开看门狗。STC89C52 的看门狗由 WDT_CONTR 寄存器控制喂狗周期设 2~3 秒主循环每个周期末尾喂一次。LCD 初始化或 EEPROM 操作卡死时看门狗能兜底复位不会让药盒“静默死机”到第二天。上电时序上DS1302 的 VCC2 要领先或同步于主控上电否则首次上电可能读到未初始化时间。解决办法是在 DS1302_Init 里先关闭写保护写入一次当前时间再进主循环EEPROM 里的“首次上电标志”也一并检查避免把 0xFF 当真实计划执行。6. 验收清单智能药盒的测试表与设计说明的打磨最后落地一份可执行的验收方法。功能测试按表逐项打勾每项记录测试时间、实际结果和现象描述这份表可以直接搬到设计说明的“测试与验证”章节。编号测试项测试条件通过标准T01走时精度上电 24 小时与标准时间偏差 ≤ 5 秒T02提醒触发计划 08:00等待窗口07:55~08:05 内响铃且只响一次T03按键防抖快速连按 50 次无一次触发两次动作T04掉电保持断电 5 分钟恢复计划与时间不丢失T05服药确认响铃后按键记录写入并有 LCD 反馈走时精度测试只校准晶振还不够软件秒补更实用连续跑 24 小时算出偏差秒数然后在每小时整点由程序补 1 秒精度可做到每天 ±1 秒以内。这个“软件秒补”是很多智能药盒方案里不会写进文档的细节。设计说明的打磨顺序我一般会先写功能需求表再画原理图因为每个需求都要落回到硬件引脚和代码分支。电路说明里附上关键参数——负载电容、限流电阻、三极管型号——而不是只贴一张原理图。程序章节按模块拆分每个函数写明输入输出和调用周期答辩时被问到任何一处都能迅速指到对应代码行。Keil 工程在 Options for Target 的 Output 页勾选 Create HEX File烧录软件里的波特率和晶振频率写进文档附录拿到板子的人按这份说明就能复现一次完整的下载流程。本文还有配套的精品资源点击获取