基于单片机的智能书架控制系统设计

  1. 基于单片机的智能书架控制系统设计

点击链接下载protues仿真设计资料:https://download.csdn.net/download/m0_51061483/92081526


  1. 系统概述

2.1 设计背景与应用意义

随着智能家居与自动化设备的普及,传统书架已经不再仅仅是被动存放书籍的家具,而逐渐向"可管理、可控制、可追踪"的智能终端演进。尤其是在图书馆、档案室、实验室文献柜、企业资料室以及个人高价值藏书环境中,书籍的管理需求往往不仅体现在"存放"上,还体现在"精准定位""权限控制""便捷取用""防止误拿"等方面。

因此,将单片机控制、矩阵化书籍单元管理、按键输入定位、电机驱动门锁等技术融合到书架系统中,可以显著提高书籍存取效率,并增强安全性与智能化水平。

本设计以单片机为控制核心,将书架划分为 4 行 9 列,共 36 个独立存放单元。每个单元对应一个"门/锁"结构,通过电机模拟开闭动作。用户可以通过按键输入书名或编号,系统识别后控制对应单元执行开启或关闭。由于整体电路规模较大,扫描按键与控制输出的任务较多,可能造成按键响应略慢,因此系统在交互逻辑上允许用户多次按键输入,并通过软件滤波、队列处理与提示机制提高可用性。

2.2 系统总体目标

本系统的总体设计目标如下:

1)构建 36 个独立书籍单元的可控书架系统,实现单元级开闭控制;

2)提供按键输入方式,支持输入书名或编号来定位对应书籍单元;

3)通过电机驱动模拟书架门或门锁的开关动作,实现智能化存取;

4)提供操作提示与容错机制,在电路复杂、响应略慢的情况下依然可用;

5)系统运行稳定,可扩展(可扩展显示屏、刷卡、联网等功能)。


  1. 系统功能设计

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 闪烁、蜂鸣提示、数码管显示)
  • 输入缓冲:允许短时间内输入多次按键后统一处理
  • 重复按键容错:同一按键事件多次触发也不会导致逻辑错乱
  • 超时自动确认:用户停止输入一段时间后自动进入确认环节
  • 指令队列:电机动作执行期间可继续录入下一条指令,避免"卡死等待"

这些机制能显著提升在复杂电路环境下的可用性,使"多按几次就行"成为可接受的操作体验,而不是系统缺陷。


  1. 系统电路设计

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 保持稳定才确认)

  1. 程序设计

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 秒内多次变化只写一次)

  1. 程序示例代码(模块化示例)
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();
        }

        // 可扩展:显示刷新任务、存储任务、日志任务等
    }
}

  1. 系统性能与"按键响应慢"的工程分析

7.1 按键响应慢的真实原因

"按键响应慢"并不一定是按键硬件本身的问题,而往往由系统整体复杂度造成:

  • 按键扫描周期被拉长(例如 50ms 才扫描一次)
  • CPU 被大量 IO 刷新占用(例如频繁刷新移位寄存器)
  • 电机动作使用阻塞延时(例如 delay 800ms)导致无法扫描按键
  • 显示刷新或字符串处理耗时过多
  • 电机干扰导致按键抖动,需要更严格的去抖判断

7.2 本设计的优化策略总结

为了在电路庞大的情况下仍可使用,本设计采用:

  • 10ms 节拍扫描按键
  • 输入事件队列化,避免丢键
  • 电机动作状态机,避免阻塞
  • 输出扩展"只在变化时刷新",减少时间开销
  • 提示机制(蜂鸣器/显示),降低用户焦虑感
  • 允许用户重复按键操作,系统不会因为重复输入而逻辑混乱

7.3 进一步提升响应速度的可行措施

若需要进一步提升性能,可考虑:

  • 使用更高性能 MCU(主频更高、带 DMA 的 SPI)
  • 使用硬件键盘编码芯片或中断按键输入
  • 将显示刷新与输入解析分离成低优先级任务
  • 电机驱动采用独立控制板或协处理器
  • 将书名查找改为哈希表或编号索引减少字符串比较

  1. 总结

本系统基于单片机实现智能书架控制,将书架划分为 4 行 9 列共 36 个独立单元,通过按键输入书名或编号对指定单元进行开闭控制,并采用电机驱动模拟书架门或门锁动作,实现智能化存取。硬件设计通过输出扩展模块解决 IO 不足问题,并使用驱动阵列与保护电路实现安全可靠的多路执行控制;软件设计采用模块化结构、命令队列与状态机机制,使系统即便在电路规模较大、按键响应略慢的情况下仍能稳定运行,并支持用户重复按键完成控制。通过提示与容错机制、抗干扰设计、电源分区与负载分组执行策略,系统具备良好的可扩展性与工程实用性,可应用于图书馆资料管理、实验室文献柜、个人智能藏书等多种场景。

相关推荐
KWTXX16 小时前
施密特触发器与高频电子线路的联系
单片机
余生皆假期-16 小时前
锁相环 (PLL) 的原理
单片机·嵌入式硬件·嵌入式
墨辰JC18 小时前
STM32串口通信DMA接收 + 空闲中断IDLE详解
stm32·单片机·嵌入式硬件·蓝桥杯·idle·dma
luoluoal18 小时前
基于python的语音识别与蓝牙通信的温控系统(源码+文档)
python·mysql·django·毕业设计·源码
d111111111d19 小时前
STM32如何通过寄存器直接禁止EXTI0中断
笔记·stm32·单片机·嵌入式硬件·学习
PN杰19 小时前
通过matlab处理Tek示波器导出的.tss波形文件
stm32·单片机·matlab
Sumerking20 小时前
DMM 高精度采样部分
单片机·mcu
点灯小铭20 小时前
基于单片机的蔬菜大棚温湿度远程测报系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
v先v关v住v获v取20 小时前
自动搬运车结构设计9张cad+三维图+设计说明书
科技·单片机·51单片机