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

  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
  • 按确认键执行
    系统维护输入缓冲区:
  • buf0、buf1
  • 输入超时自动清空
  • 输入不足两位时不执行

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 查找方式

  • 编号输入:直接索引数组 bookid
  • 书名输入:字符串匹配或哈希匹配(可简化为前缀匹配)

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_bits40(或 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 不足问题,并使用驱动阵列与保护电路实现安全可靠的多路执行控制;软件设计采用模块化结构、命令队列与状态机机制,使系统即便在电路规模较大、按键响应略慢的情况下仍能稳定运行,并支持用户重复按键完成控制。通过提示与容错机制、抗干扰设计、电源分区与负载分组执行策略,系统具备良好的可扩展性与工程实用性,可应用于图书馆资料管理、实验室文献柜、个人智能藏书等多种场景。

相关推荐
菜鸟的学习日记、14 小时前
GPIO的几种模式——以STM32为例
stm32·单片机·嵌入式硬件·gpio
辰哥单片机设计14 小时前
STM32智能睡眠检测系统
stm32·单片机·嵌入式硬件
隔窗听雨眠16 小时前
在STM32上跑通TinyML:从模型训练到推理优化的完整实战指南
stm32·单片机·嵌入式硬件
ryanuo718 小时前
Mac(M芯片)上进行嵌入式开发遇到的问题
嵌入式硬件·macos·开发板
机器视觉知识推荐、就业指导19 小时前
为什么同一个引脚不能同时做按键和串口
stm32·单片机·嵌入式硬件
崇山峻岭之间19 小时前
单片机基本定时器实验
单片机·嵌入式硬件
DS小龙哥19 小时前
基于ESP32设计的智能养蜂监测系统
stm32·单片机·嵌入式硬件·物联网·华为云
夜月yeyue20 小时前
STM32 DMA 双缓冲采样
linux·stm32·单片机·嵌入式硬件·系统架构
羊羊小栈20 小时前
Uplift营销供应链协同决策系统(基于Uplift因果推断与运筹优化算法)
前端·人工智能·算法·毕业设计·大作业
西城微科方案开发20 小时前
SIC8P370D2L-PLP16 8位OTP单片机 低功耗多功能MCU详解
单片机·嵌入式硬件