- 基于单片机的智能书架控制系统设计
点击链接下载protues仿真设计资料:https://download.csdn.net/download/m0_51061483/92081526
- 系统概述
2.1 设计背景与应用意义
随着智能家居与自动化设备的普及,传统书架已经不再仅仅是被动存放书籍的家具,而逐渐向"可管理、可控制、可追踪"的智能终端演进。尤其是在图书馆、档案室、实验室文献柜、企业资料室以及个人高价值藏书环境中,书籍的管理需求往往不仅体现在"存放"上,还体现在"精准定位""权限控制""便捷取用""防止误拿"等方面。
因此,将单片机控制、矩阵化书籍单元管理、按键输入定位、电机驱动门锁等技术融合到书架系统中,可以显著提高书籍存取效率,并增强安全性与智能化水平。
本设计以单片机为控制核心,将书架划分为 4 行 9 列,共 36 个独立存放单元。每个单元对应一个"门/锁"结构,通过电机模拟开闭动作。用户可以通过按键输入书名或编号,系统识别后控制对应单元执行开启或关闭。由于整体电路规模较大,扫描按键与控制输出的任务较多,可能造成按键响应略慢,因此系统在交互逻辑上允许用户多次按键输入,并通过软件滤波、队列处理与提示机制提高可用性。
2.2 系统总体目标
本系统的总体设计目标如下:
1)构建 36 个独立书籍单元的可控书架系统,实现单元级开闭控制;
2)提供按键输入方式,支持输入书名或编号来定位对应书籍单元;
3)通过电机驱动模拟书架门或门锁的开关动作,实现智能化存取;
4)提供操作提示与容错机制,在电路复杂、响应略慢的情况下依然可用;
5)系统运行稳定,可扩展(可扩展显示屏、刷卡、联网等功能)。
- 系统功能设计
3.1 书籍管理功能(4×9 共 36 单元)
系统将书架划分为 4 行 9 列,共 36 个存放单元,每个单元对应一个独立编号。编号可以采用:
- 行列编号方式:R1C1 ~ R4C9
- 序号方式:001~036
例如可定义: - 第 1 行第 1 列 = 1
- 第 1 行第 9 列 = 9
- 第 2 行第 1 列 = 10
...... - 第 4 行第 9 列 = 36
每个单元具备独立控制能力:
- 开启(Open)
- 关闭(Close)
- 状态查询(若具备传感器反馈)
在不增加额外传感器的情况下,系统也可以通过"逻辑状态"记录每个单元的当前状态。例如:用户开启后系统记录为 Open,关闭后记录为 Close,用于避免重复动作或提示用户。
3.2 按键输入控制(输入书名或编号)
系统通过按键输入书名或编号,实现对书架单元的定位与控制。由于硬件按键输入字符的方式可能较复杂,实际工程中通常采用两类方案:
1)数字键盘输入编号 :用户输入 01~36 的编号即可控制对应单元;
2)字符输入书名:通过多按键实现类似"手机九宫格"的输入方式,或采用简化输入(拼音首字母、书名缩写等),最终映射到编号。
考虑到本系统电路规模较大,按键响应略慢,为了保证可操作性,系统设计上允许重复按键操作,并对输入采用"缓冲队列 + 去抖 + 超时确认"的方式进行处理。用户按键后,系统在一定时间内收集输入,输入完成后按确认键执行开闭动作。
3.3 电机驱动门锁(模拟开闭)
每个单元使用电机模拟书架门或锁的开启和关闭。常见执行机构包括:
- 直流电机 + 齿轮/连杆(成本低,驱动简单)
- 舵机(角度控制方便,适合模拟门)
- 步进电机(定位精确但成本较高)
- 电磁锁/电磁铁(适合"锁"动作,结构简单但需机械复位)
本设计重点强调"电机驱动门锁",通常可采用直流电机或舵机。为了适配 36 单元的大规模驱动,系统可采用分组驱动策略与输出扩展器,以降低单片机 IO 资源压力,并避免同时驱动过多电机导致电源压降和系统异常。
3.4 操作提示与容错机制(按键响应略慢)
由于 36 路电机控制、按键扫描、输入解析、驱动输出等任务并行执行,系统负载较大,在硬件资源有限或电路较复杂的情况下,可能出现按键响应延迟。系统在交互层应提供以下机制:
- 按键输入显示/提示(LED 闪烁、蜂鸣提示、数码管显示)
- 输入缓冲:允许短时间内输入多次按键后统一处理
- 重复按键容错:同一按键事件多次触发也不会导致逻辑错乱
- 超时自动确认:用户停止输入一段时间后自动进入确认环节
- 指令队列:电机动作执行期间可继续录入下一条指令,避免"卡死等待"
这些机制能显著提升在复杂电路环境下的可用性,使"多按几次就行"成为可接受的操作体验,而不是系统缺陷。
- 系统电路设计
4.1 电路总体结构与模块划分
本系统硬件电路规模较大,为实现 36 个单元控制,电路可划分为以下模块:
1)单片机主控模块
2)按键输入模块(矩阵键盘或独立按键)
3)显示与提示模块(可选)
4)36 路电机驱动模块
5)输出扩展模块(IO 扩展芯片/移位寄存器)
6)电源模块(多路供电与稳压)
7)保护与抗干扰模块(ESD、EMI、反灌电压保护)
8)状态反馈模块(可选:限位开关/霍尔/光电传感器)
9)书籍映射存储模块(EEPROM/Flash)
系统设计的核心难点在于:多路电机驱动与 IO 资源扩展。单片机本身 IO 数量有限,不可能直接控制 36 路电机,因此必须采用扩展与分层控制策略。
4.2 单片机主控模块
4.2.1 主控芯片选择与功能职责
单片机作为系统大脑,主要负责:
- 按键扫描与输入解析(书名/编号)
- 映射查表:输入内容 → 单元编号
- 生成电机控制命令:开/关/停止
- 控制输出扩展模块
- 处理电机动作队列与定时控制
- 保存书籍信息与状态
- 与显示/提示模块交互
芯片选型建议:
- IO 资源丰富:建议选 STM32、ESP32、Nuvoton、AVR 等
- 具备足够定时器与通信接口:SPI/I2C/UART
- 具备足够 Flash 以存储映射表与字符串索引
4.2.2 时钟、复位与看门狗电路
- 时钟:外部晶振保证稳定时间基准,利于电机控制定时精度
- 复位:按键复位 + 上电复位,保证上电状态一致
- 看门狗:防止电机动作与按键扫描中出现异常死机
4.2.3 调试与下载接口
由于系统复杂,必须预留调试接口:
- SWD/ISP 下载
- 串口日志输出(用于调试按键响应慢、指令队列拥堵等问题)
4.3 按键输入模块(矩阵键盘/独立按键)
4.3.1 按键输入形式
系统按键输入可采用:
1)4×4 或 4×3 矩阵键盘 :可实现数字与功能键输入
2)独立按键组合 :如"上下左右确认返回"模式
3)编码器旋钮 + 按键 :可快速选择编号
4)多键输入书名:类似 T9 输入法或拼音首字母
考虑到用户描述"按键输入书名或编号",最稳妥方案是:
- 默认编号输入
- 书名输入作为扩展(可通过多次按键选择书名列表)
4.3.2 矩阵键盘扫描电路特点
矩阵键盘优点是节省 IO:
- 例如 4×4 键盘只需 8 根 IO 就能实现 16 个键
缺点是需要扫描逻辑,并且在电路规模大时,扫描周期可能变长,导致响应慢。因此在软件上必须优化扫描频率与任务优先级。
4.3.3 按键去抖与抗干扰
由于系统含电机驱动,电机启停可能引入电磁干扰,导致按键误触发。应采取:
- 按键硬件上拉/下拉稳定电平
- RC 滤波(可选)
- 软件去抖(必需)
- 关键输入采用"按下确认 + 松开确认"的双状态判断
4.4 显示与提示模块(可选)
4.4.1 显示模块作用
显示模块不是必须,但对"书名/编号输入"和"电路复杂、响应慢"的场景很重要。它可以用于:
- 显示输入的编号或字符
- 显示当前选中的书籍名称
- 显示单元执行状态:Opening/Closing/Done/Error
- 提示用户"请稍候"或"重复按键即可"
4.4.2 可选显示方案
- 1602/2004 LCD:成本低,显示信息足够
- OLED:显示清晰,适合多行文字
- 数码管:适合编号显示(01~36)
- 指示灯组合:每次按键闪烁确认,提供最低成本提示
4.4.3 蜂鸣器提示(可选)
蜂鸣器在每次按键确认、动作完成、错误发生时发声提示,可以弥补视觉提示不足,提高交互体验。
4.5 36 路电机驱动模块
4.5.1 电机类型与驱动需求
若每个单元使用:
- 直流电机:需要 H 桥或单向驱动 + 机械回位
- 舵机:需要 PWM 控制信号(1~2ms 脉宽,周期 20ms)
- 步进电机:需要步进驱动器与脉冲输出
- 电磁锁:需要驱动电流较大的开关管,通电开锁断电锁闭或反之
在 36 路控制中,最常见且成本可控的方案是:
- 舵机用于门的开闭(角度可控)
- 电磁锁用于锁扣动作(结构简单)
- 或直流电机配合限位开关实现开关定位
4.5.2 分组驱动思想(避免同时驱动 36 路)
出于成本、供电与安全考虑,系统通常不允许 36 路电机同时动作,而是采取分组或队列执行:
- 同一时刻最多允许 1~2 路电机动作
- 多个请求进入队列按顺序执行
- 执行完成后进入下一条命令
这样可以:
- 降低电源瞬时电流需求
- 减少电机干扰导致的 MCU 重启
- 让控制逻辑更可靠
4.5.3 电机驱动电路(MOSFET/驱动芯片)
对于直流电机或电磁锁,通常采用:
- N 沟道 MOSFET 低端开关
- 三极管驱动(适合小电流)
- 驱动阵列芯片(如 ULN2003/ULN2803)
其中 ULN2803 提供 8 路达林顿驱动,适合多路电磁锁或小直流电机驱动,是多路系统非常典型的方案。
若需要正反转(开/关方向不同),可使用:
- L293D、TB6612FNG 等 H 桥驱动芯片
但 36 路 H 桥成本与体积较大,更适合采用"单向驱动 + 机械复位"或舵机方案。
4.5.4 续流保护与干扰抑制
电机属于感性负载,必须增加:
- 续流二极管(防止反向电压击穿 MOSFET)
- RC 吸收或 TVS(提升抗浪涌能力)
- 电源端大电容(抑制启动电流带来的压降)
4.6 输出扩展模块(解决 IO 不足问题)
4.6.1 为什么需要 IO 扩展
单片机 IO 不足是本系统的核心矛盾之一。
- 若每路电机需要 1 个控制信号,则至少需要 36 路 IO
- 若需要电机正反转或带状态反馈,则 IO 需求更高
因此必须使用输出扩展。
4.6.2 常用扩展方案
1)74HC595 移位寄存器级联
- 1 个 74HC595 提供 8 路输出
- 级联 5 个即可提供 40 路输出
- 只需单片机 3 根线(DATA、CLK、LATCH)
优点:成本低,扩展方便
缺点:输出驱动能力弱,需要配合 ULN2803 或 MOSFET
2)I2C IO 扩展芯片(如 PCF8574、MCP23017)
- I2C 两根线即可扩展 8/16 路 IO
- 可扩展多个地址
优点:电路简单,软件易读
缺点:I2C 在高干扰环境下需更谨慎设计
3)专用驱动阵列 + 扫描控制
例如:采用行列扫描方式控制电机驱动(类似 LED 点阵),但电机控制需要持续时间与较大电流,不适合简单扫描,因此更推荐移位寄存器或 IO 扩展芯片。
4.6.3 推荐组合结构
对于本系统,推荐组合:
- 74HC595(输出扩展) + ULN2803(电流驱动)
实现: - 单片机用 3 根线控制 5 个移位寄存器
- 寄存器输出进入 ULN2803
- ULN2803 输出驱动电磁锁或电机开关管
这样可以在硬件规模庞大时保持结构清晰,并减少 MCU IO 占用。
4.7 电源模块(多路供电与稳压)
4.7.1 供电需求分析
电机驱动是耗电大户,36 个单元即使不同时动作,也需要足够的电源容量与瞬态电流能力。电源一般分为两路:
- 控制电源:3.3V/5V(单片机、IO 扩展、显示)
- 动力电源:6V/12V(电机、舵机、电磁锁)
两路电源必须合理隔离与滤波,避免电机启动导致控制电源跌落。
4.7.2 稳压与滤波设计要点
- 动力电源入口放置大电容(470uF~2200uF)
- 控制电源采用 LDO 或 DC-DC 稳压,输入端加 LC 滤波
- 单片机与 IO 芯片附近布置 0.1uF 去耦电容
- 电机电源与控制地采用合理地线规划,避免地弹跳影响按键输入
4.7.3 电源保护
建议加入:
- 反接保护(肖特基二极管或 MOS 反接保护)
- 保险丝/自恢复保险丝
- TVS 防浪涌保护
这些措施对大规模电机系统尤为重要。
4.8 保护与抗干扰模块
4.8.1 电机干扰来源
电机启停会产生:
- 电磁辐射干扰
- 反电动势
- 电源纹波与压降
这些干扰可能导致按键误触发、显示异常、单片机复位等问题。
4.8.2 硬件抗干扰措施
- 电机端并联电容(例如 0.1uF)抑制火花干扰
- MOSFET 栅极串联电阻(抑制振荡)
- 关键走线远离高电流电机线
- 地线分区、单点汇接
- 信号线可加磁珠与 RC 滤波
4.8.3 软件抗干扰措施
- 按键扫描加去抖与重复确认
- 电机动作执行期间降低按键误判(提高阈值)
- 电源波动检测(可用 ADC 监测 5V/3.3V)
- 看门狗复位防止系统卡死
- 指令队列机制避免系统忙碌时丢失按键
4.9 状态反馈模块(可选)
4.9.1 为什么需要反馈
如果没有反馈,系统只能"假设"门/锁已经开或关,若机械卡住将无法识别。
可选加入:
- 限位开关:检测门到位
- 霍尔传感器:检测磁铁位置
- 光电开关:检测门状态
4.9.2 反馈信号处理
反馈信号同样需要去抖与抗干扰,尤其是限位开关会产生抖动,可采用:
- 硬件 RC
- 软件延时确认(例如到位后连续 50ms 保持稳定才确认)
- 程序设计
5.1 软件总体架构
5.1.1 模块化设计原则
为了应对复杂电路与较慢按键响应,软件必须做到:
- 任务分层:输入、控制、执行、提示分离
- 非阻塞运行:避免长时间 delay
- 状态机驱动:按键输入与电机动作均采用状态机
- 指令队列:避免输入丢失,提高可用性
- 统一调度:使用定时器节拍调度各任务
5.1.2 核心任务划分
1)系统初始化模块
2)按键扫描模块
3)输入缓冲与解析模块(编号/书名)
4)书籍映射管理模块
5)电机命令队列模块
6)电机执行控制模块(定时、开关、到位判断)
7)输出扩展驱动模块(74HC595/IO 扩展)
8)显示与提示模块
9)存储模块(保存映射与状态)
5.2 系统初始化模块
5.2.1 初始化内容
- GPIO 初始化:按键、提示灯、蜂鸣器
- 通信初始化:SPI/I2C(若使用扩展芯片或显示屏)
- 定时器初始化:1ms/10ms 节拍
- 输出扩展初始化:清零所有电机控制位
- 读取书籍映射表与门锁状态(可选)
- 显示欢迎界面或闪灯提示系统启动完成
5.2.2 安全默认状态
上电默认所有门锁处于"关闭"状态,所有电机输出为关闭,避免上电误动作。
5.3 按键扫描模块
5.3.1 扫描周期与去抖策略
建议每 10ms 扫描一次按键矩阵,去抖策略:
- 采样连续 3~5 次稳定才确认按下
- 确认按下后产生一次按键事件
- 松开也可确认,避免长按重复触发
5.3.2 按键响应慢的原因与软件优化
按键响应慢通常原因包括:
- 扫描频率低
- 同时处理电机动作导致 CPU 忙
- 显示刷新占用过多时间
- I2C/SPI 总线通信较慢
因此优化方向: - 扫描任务优先级提高
- 电机动作采用非阻塞状态机
- 显示刷新降低频率(例如 200ms 一次)
- 输入事件采用队列缓存,避免丢键
并且在交互提示中允许用户"重复按键"。
5.4 输入缓冲与解析模块(书名/编号)
5.4.1 编号输入方案
编号输入是最可靠的方式:
- 输入两位数:01~36
- 按确认键执行
系统维护输入缓冲区: - buf[0]、buf[1]
- 输入超时自动清空
- 输入不足两位时不执行
5.4.2 书名输入简化策略
考虑按键输入书名复杂,实际可采用:
- 书名缩写或首字母
- 书名列表循环选择(按上下键翻页,确认选中)
- 或仅支持编号输入,书名作为显示提示用数据表实现映射
即:用户输入编号后,系统显示该编号对应书名,完成"书名/编号"体验。
5.4.3 输入容错与重复按键
针对"电路过于庞大,按键反应有点慢",软件采取:
- 同一键短时间重复按下只认一次(防抖)
- 但允许用户重复输入(再次按确认也可执行)
- 若输入解析失败,提示"ERR"并清空缓冲
5.5 书籍映射管理模块
5.5.1 映射表结构设计
系统需要将:
- 书名(字符串)
- 书籍编号(1~36)
- 单元位置(行、列)
建立映射关系。
可用结构体: - id:1~36
- name:书名字符串
- row、col
- state:门锁状态(开/关)
5.5.2 存储方式
映射表可存储在:
- Flash 固定表(适合书籍固定不变)
- EEPROM 可修改表(适合可维护)
对于较长书名,建议固定表 + 索引列表,减少 EEPROM 写入与占用。
5.5.3 查找方式
- 编号输入:直接索引数组 book[id]
- 书名输入:字符串匹配或哈希匹配(可简化为前缀匹配)
5.6 电机命令队列模块
5.6.1 为什么需要队列
36 个单元控制意味着用户可能连续操作多本书。如果电机动作执行需要时间(例如 1~2 秒),系统如果阻塞等待电机完成,会导致按键扫描停顿,表现为"按键反应慢"。
因此必须用命令队列:
- 用户每次确认产生一条指令(开/关、目标单元)
- 指令进入队列
- 电机执行模块从队列取出指令逐个执行
这样即使电机在动作,按键输入仍可响应并缓存。
5.6.2 队列数据结构
队列元素可包含:
- target_id:1~36
- action:OPEN/CLOSE
- duration_ms:动作持续时间
- retry:失败重试次数
队列容量可设置为 8~16 条,足够一般使用。
5.7 电机执行控制模块
5.7.1 电机动作状态机
电机控制建议使用状态机:
1)IDLE:空闲,等待队列命令
2)START:启动电机输出
3)RUN:保持输出,计时运行
4)STOP:停止输出,更新状态
5)DONE:动作完成,取下一条命令
6)ERROR:超时或反馈异常(若有反馈)
整个过程非阻塞,每个 10ms 或 1ms 更新一次。
5.7.2 动作时间控制
如果没有反馈传感器,动作时间必须严格控制:
- 开启动作:例如 800ms
- 关闭动作:例如 800ms
执行完即停止,避免电机堵转发热。
如果采用舵机,则输出目标角度并等待稳定即可。
5.7.3 限流与保护策略
- 禁止同一单元在短时间内反复开关(加入冷却时间)
- 电机堵转保护:运行超过最大时长立即停止
- 多次失败可进入错误状态并提示用户
5.8 输出扩展驱动模块
5.8.1 74HC595 驱动逻辑
如果使用 74HC595 级联输出,软件需要维护一个输出位图:
- out_bits[40](或 5 个字节)
每次更新某一路电机输出,就修改位图,然后一次性移位刷新。
刷新过程: - 拉低 LATCH
- 依次移入 40 位数据
- 拉高 LATCH 锁存输出
5.8.2 避免频繁刷新导致卡顿
按键慢的另一个原因是输出刷新耗时。优化方法:
- 仅在输出变化时刷新
- 将刷新放到低频任务中(例如 1ms 或 10ms 触发,不要在大量循环中重复刷新)
- 或采用 DMA/SPI 硬件加速移位(高级 MCU 可用)
5.9 显示与提示模块
5.9.1 提示信息设计
显示/提示应覆盖以下场景:
- 输入过程中显示已输入数字
- 输入完成提示目标单元与书名
- 电机动作中显示"OPENING/CLOSING"
- 动作完成提示"DONE"
- 错误提示"ERR"
- 在按键响应慢时提示"按键可重复操作"或"请稍候"
5.9.2 提示灯与蜂鸣器节奏
- 每次按键有效:短鸣或 LED 闪一下
- 动作开始:长鸣一次或 LED 常亮
- 动作完成:两声短鸣
这些反馈能显著改善用户体验,让用户知道系统确实已接收按键,减少误认为"没反应"而重复按键造成混乱。
5.10 存储模块(参数与状态保存)
5.10.1 保存内容
可保存:
- 每个单元当前状态(开/关)
- 书籍映射表(若允许修改)
- 最近一次操作记录(可选)
5.10.2 写入策略与寿命保护
由于 EEPROM 写入次数有限:
- 仅在动作完成后写入状态
- 状态变化才写入
- 可采用批量写入或延时写入(例如 5 秒内多次变化只写一次)
- 程序示例代码(模块化示例)
c
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
/* =========================
宏定义与常量
========================= */
#define CELL_ROWS 4
#define CELL_COLS 9
#define CELL_NUM 36
#define QUEUE_SIZE 16
#define OPEN_TIME_MS 800
#define CLOSE_TIME_MS 800
#define MOTOR_COOLDOWN_MS 200
/* =========================
硬件抽象接口(需用户实现)
========================= */
// 读取按键事件(10ms 调用一次)
typedef enum {
KEY_NONE = 0,
KEY_0, KEY_1, KEY_2, KEY_3, KEY_4,
KEY_5, KEY_6, KEY_7, KEY_8, KEY_9,
KEY_OPEN, // 开门键
KEY_CLOSE, // 关门键
KEY_OK, // 确认键
KEY_CLR // 清除键
} key_event_t;
extern key_event_t Key_ScanEvent10ms(void);
// 输出扩展:设置某个单元电机输出(示例:单信号控制)
extern void Motor_OutputSet(uint8_t cell_id, bool on);
// 显示接口
extern void UI_ShowInput(const char *buf);
extern void UI_ShowStatus(const char *msg, uint8_t cell_id);
extern void UI_ShowBookName(uint8_t cell_id, const char *name);
// 蜂鸣器/LED 提示(可选)
extern void Beep_Short(void);
extern void Beep_Long(void);
/* =========================
书籍映射结构体
========================= */
typedef struct {
uint8_t id; // 1~36
uint8_t row; // 0~3
uint8_t col; // 0~8
char name[24]; // 书名(简化长度)
bool is_open; // 逻辑状态
} book_cell_t;
static book_cell_t g_cells[CELL_NUM];
/* =========================
命令队列结构体
========================= */
typedef enum { ACT_OPEN = 0, ACT_CLOSE } action_t;
typedef struct {
uint8_t cell_id; // 1~36
action_t act; // 开/关
uint16_t duration_ms; // 动作持续时间
} cmd_t;
static cmd_t g_queue[QUEUE_SIZE];
static uint8_t q_head = 0, q_tail = 0;
static bool q_full = false;
/* =========================
队列操作
========================= */
static bool Queue_IsEmpty(void)
{
return (!q_full && (q_head == q_tail));
}
static bool Queue_IsFull(void)
{
return q_full;
}
static bool Queue_Push(cmd_t cmd)
{
if (Queue_IsFull()) return false;
g_queue[q_tail] = cmd;
q_tail = (q_tail + 1) % QUEUE_SIZE;
if (q_tail == q_head) q_full = true;
return true;
}
static bool Queue_Pop(cmd_t *out)
{
if (Queue_IsEmpty()) return false;
*out = g_queue[q_head];
q_head = (q_head + 1) % QUEUE_SIZE;
q_full = false;
return true;
}
/* =========================
输入缓冲:两位编号输入
========================= */
static char input_buf[3] = {0}; // "01"~"36"
static uint8_t input_len = 0;
static action_t pending_act = ACT_OPEN;
static void Input_Clear(void)
{
memset(input_buf, 0, sizeof(input_buf));
input_len = 0;
UI_ShowInput("--");
}
static int Input_ToCellID(void)
{
if (input_len != 2) return -1;
int id = (input_buf[0] - '0') * 10 + (input_buf[1] - '0');
if (id < 1 || id > CELL_NUM) return -1;
return id;
}
/* =========================
初始化书籍映射表(示例)
========================= */
static void Cells_Init(void)
{
for (uint8_t i = 0; i < CELL_NUM; i++) {
g_cells[i].id = i + 1;
g_cells[i].row = i / CELL_COLS;
g_cells[i].col = i % CELL_COLS;
g_cells[i].is_open = false;
// 示例:默认书名可按编号生成或写死表
snprintf(g_cells[i].name, sizeof(g_cells[i].name), "BOOK_%02d", i + 1);
}
}
/* =========================
按键任务(10ms)
========================= */
static void KeyTask_10ms(void)
{
key_event_t ev = Key_ScanEvent10ms();
if (ev == KEY_NONE) return;
// 有效按键提示(减少"没反应"的感觉)
Beep_Short();
if (ev >= KEY_0 && ev <= KEY_9) {
if (input_len < 2) {
input_buf[input_len++] = (char)('0' + (ev - KEY_0));
input_buf[input_len] = '\0';
UI_ShowInput(input_buf);
} else {
// 超出两位,自动覆盖或忽略
Input_Clear();
input_buf[input_len++] = (char)('0' + (ev - KEY_0));
input_buf[input_len] = '\0';
UI_ShowInput(input_buf);
}
return;
}
if (ev == KEY_CLR) {
Input_Clear();
return;
}
if (ev == KEY_OPEN) { pending_act = ACT_OPEN; UI_ShowStatus("ACT=OPEN", 0); return; }
if (ev == KEY_CLOSE) { pending_act = ACT_CLOSE; UI_ShowStatus("ACT=CLOSE", 0); return; }
if (ev == KEY_OK) {
int id = Input_ToCellID();
if (id < 0) {
UI_ShowStatus("ERR INPUT", 0);
Beep_Long();
Input_Clear();
return;
}
// 显示书名提示
UI_ShowBookName((uint8_t)id, g_cells[id - 1].name);
cmd_t cmd;
cmd.cell_id = (uint8_t)id;
cmd.act = pending_act;
cmd.duration_ms = (pending_act == ACT_OPEN) ? OPEN_TIME_MS : CLOSE_TIME_MS;
if (!Queue_Push(cmd)) {
UI_ShowStatus("QUEUE FULL", (uint8_t)id);
Beep_Long();
} else {
UI_ShowStatus("CMD OK", (uint8_t)id);
}
Input_Clear();
return;
}
}
/* =========================
电机执行状态机(10ms 调用)
========================= */
typedef enum { M_IDLE=0, M_START, M_RUN, M_STOP, M_COOLDOWN } motor_state_t;
static motor_state_t m_state = M_IDLE;
static cmd_t cur_cmd;
static uint16_t m_timer = 0;
static uint16_t cooldown_timer = 0;
static void MotorTask_10ms(void)
{
switch (m_state) {
case M_IDLE:
if (Queue_Pop(&cur_cmd)) {
m_state = M_START;
}
break;
case M_START:
// 启动电机输出
Motor_OutputSet(cur_cmd.cell_id, true);
m_timer = 0;
UI_ShowStatus((cur_cmd.act == ACT_OPEN) ? "OPENING" : "CLOSING", cur_cmd.cell_id);
m_state = M_RUN;
break;
case M_RUN:
m_timer += 10;
if (m_timer >= cur_cmd.duration_ms) {
m_state = M_STOP;
}
break;
case M_STOP:
// 停止电机输出,更新逻辑状态
Motor_OutputSet(cur_cmd.cell_id, false);
if (cur_cmd.act == ACT_OPEN) g_cells[cur_cmd.cell_id - 1].is_open = true;
else g_cells[cur_cmd.cell_id - 1].is_open = false;
UI_ShowStatus("DONE", cur_cmd.cell_id);
Beep_Short();
Beep_Short();
// 冷却时间,避免频繁动作
cooldown_timer = 0;
m_state = M_COOLDOWN;
break;
case M_COOLDOWN:
cooldown_timer += 10;
if (cooldown_timer >= MOTOR_COOLDOWN_MS) {
m_state = M_IDLE;
}
break;
default:
m_state = M_IDLE;
break;
}
}
/* =========================
主循环示例
========================= */
// 由定时器中断置位 flag_10ms
extern volatile bool flag_10ms;
int main(void)
{
Cells_Init();
Input_Clear();
while (1) {
if (flag_10ms) {
flag_10ms = false;
KeyTask_10ms();
MotorTask_10ms();
}
// 可扩展:显示刷新任务、存储任务、日志任务等
}
}
- 系统性能与"按键响应慢"的工程分析
7.1 按键响应慢的真实原因
"按键响应慢"并不一定是按键硬件本身的问题,而往往由系统整体复杂度造成:
- 按键扫描周期被拉长(例如 50ms 才扫描一次)
- CPU 被大量 IO 刷新占用(例如频繁刷新移位寄存器)
- 电机动作使用阻塞延时(例如 delay 800ms)导致无法扫描按键
- 显示刷新或字符串处理耗时过多
- 电机干扰导致按键抖动,需要更严格的去抖判断
7.2 本设计的优化策略总结
为了在电路庞大的情况下仍可使用,本设计采用:
- 10ms 节拍扫描按键
- 输入事件队列化,避免丢键
- 电机动作状态机,避免阻塞
- 输出扩展"只在变化时刷新",减少时间开销
- 提示机制(蜂鸣器/显示),降低用户焦虑感
- 允许用户重复按键操作,系统不会因为重复输入而逻辑混乱
7.3 进一步提升响应速度的可行措施
若需要进一步提升性能,可考虑:
- 使用更高性能 MCU(主频更高、带 DMA 的 SPI)
- 使用硬件键盘编码芯片或中断按键输入
- 将显示刷新与输入解析分离成低优先级任务
- 电机驱动采用独立控制板或协处理器
- 将书名查找改为哈希表或编号索引减少字符串比较
- 总结
本系统基于单片机实现智能书架控制,将书架划分为 4 行 9 列共 36 个独立单元,通过按键输入书名或编号对指定单元进行开闭控制,并采用电机驱动模拟书架门或门锁动作,实现智能化存取。硬件设计通过输出扩展模块解决 IO 不足问题,并使用驱动阵列与保护电路实现安全可靠的多路执行控制;软件设计采用模块化结构、命令队列与状态机机制,使系统即便在电路规模较大、按键响应略慢的情况下仍能稳定运行,并支持用户重复按键完成控制。通过提示与容错机制、抗干扰设计、电源分区与负载分组执行策略,系统具备良好的可扩展性与工程实用性,可应用于图书馆资料管理、实验室文献柜、个人智能藏书等多种场景。