目标:提供足够详细的算法描述,可直接翻译为 VHDL-93 代码(双进程状态机风格) 约束:Quartus II 9.1,VHDL-93,3 层楼,双进程 FSM
电梯系统共有 8 个请求输入按钮,经消抖后产生单周期脉冲,用于置位请求寄存器:
| 按钮信号 | 含义 | 楼层 | 方向/类型 |
|---|---|---|---|
up1 |
1 楼外呼上行 | 1 | 上行外呼 |
up2 |
2 楼外呼上行 | 2 | 上行外呼 |
down2 |
2 楼外呼下行 | 2 | 下行外呼 |
down3 |
3 楼外呼下行 | 3 | 下行外呼 |
stop1 |
轿厢内选 1 楼 | 1 | 内选停站 |
stop2 |
轿厢内选 2 楼 | 2 | 内选停站 |
stop3 |
轿厢内选 3 楼 | 3 | 内选停站 |
alarm_btn |
轿厢内报警 | -- | 特殊功能 |
门控按钮(不产生请求,直接影响门控状态机):
| 按钮信号 | 含义 |
|---|---|
open_btn |
手动开门 |
close_btn |
手动关门 |
使用 7 个独立的 1 位寄存器存储请求状态:
信号定义(全部为 std_logic,初始值 '0'):
req_up(1) -- 1 楼上行外呼请求
req_up(2) -- 2 楼上行外呼请求
req_down(2) -- 2 楼下行外呼请求
req_down(3) -- 3 楼下行外呼请求
req_stop(1) -- 1 楼停站(内选)请求
req_stop(2) -- 2 楼停站(内选)请求
req_stop(3) -- 3 楼停站(内选)请求
VHDL 信号声明:
signal req_up : std_logic_vector(3 downto 1); -- req_up(3) 恒为 '0',不使用
signal req_down : std_logic_vector(3 downto 1); -- req_down(1) 恒为 '0',不使用
signal req_stop : std_logic_vector(3 downto 1);请求寄存器在**时序进程(同步进程)**中更新。置位条件:消抖后的按钮信号出现上升沿(单周期脉冲为 '1')。
伪代码 -- 请求置位(在时钟上升沿内执行):
IF up1_pulse = '1' THEN req_up(1) <= '1'
IF up2_pulse = '1' THEN req_up(2) <= '1'
IF down2_pulse = '1' THEN req_down(2) <= '1'
IF down3_pulse = '1' THEN req_down(3) <= '1'
IF stop1_pulse = '1' THEN req_stop(1) <= '1'
IF stop2_pulse = '1' THEN req_stop(2) <= '1'
IF stop3_pulse = '1' THEN req_stop(3) <= '1'
注意:置位和清除在同一时序进程中完成。若同一拍同时发生置位和清除,清除优先(先清除再置位,或清除覆盖置位),确保已到达楼层的请求不会因新按键误保留。
请求在电梯到达该楼层并开门时清除。清除发生在状态机进入 DOOR_OPENING 状态的那一拍。
伪代码 -- 请求清除(在时钟上升沿内执行):
当状态机转入 DOOR_OPENING 且 current_floor = F 时:
req_stop(F) <= '0' -- 始终清除该层停站请求
IF direction = UP THEN req_up(F) <= '0' -- 上行到达,清除上行外呼
IF direction = DOWN THEN req_down(F) <= '0' -- 下行到达,清除下行外呼
-- 特殊情况:若该方向无更多请求即将换向,同时清除反向外呼
IF 即将换向 THEN
req_up(F) <= '0'
req_down(F) <= '0'
END IF
清除策略的核心原则:
- 停站请求(
req_stop):到达即清除,不论方向 - 外呼请求(
req_up/req_down):仅清除与当前运行方向一致的外呼 - 换向时例外:若电梯到达后即将换向(同方向无更多请求),同时清除两个方向的外呼
硬件层面强制约束无效请求永远不会被置位:
硬约束(在请求置位逻辑中实现):
req_up(3) <= '0' 恒成立 -- 3 楼不可能有上行外呼(顶楼)
req_down(1) <= '0' 恒成立 -- 1 楼不可能有下行外呼(底楼)
实现方式:在 VHDL 中,req_up(3) 和 req_down(1) 不接入任何置位逻辑,复位后保持 '0'。或者更简单地,req_up 仅使用索引 1 和 2,req_down 仅使用索引 2 和 3。
为简化调度逻辑,定义以下聚合信号(组合逻辑):
-- 某一楼层是否有任何请求(不区分方向)
req_any(F) = req_up(F) OR req_down(F) OR req_stop(F) -- F = 1,2,3
-- 当前楼层是否需要停靠(考虑方向)
need_stop_here =
req_stop(current_floor) = '1' -- 内选该层
OR (direction = UP AND req_up(current_floor) = '1') -- 同方向外呼
OR (direction = DOWN AND req_down(current_floor) = '1')
-- 当前方向上方是否有请求
req_above =
(current_floor < 3) AND (存在 F > current_floor 使得 req_any(F) = '1')
-- 当前方向下方是否有请求
req_below =
(current_floor > 1) AND (存在 F < current_floor 使得 req_any(F) = '1')
-- 全局是否有任何请求
req_exist = req_any(1) OR req_any(2) OR req_any(3)
VHDL 中 req_above 和 req_below 的具体展开(current_floor 为 integer range 1 to 3):
-- req_above:当前楼层上方是否有请求
req_above <= '1' when (current_floor = 1 and (req_any(2) = '1' or req_any(3) = '1'))
or (current_floor = 2 and req_any(3) = '1')
else '0';
-- req_below:当前楼层下方是否有请求
req_below <= '1' when (current_floor = 3 and (req_any(2) = '1' or req_any(1) = '1'))
or (current_floor = 2 and req_any(1) = '1')
else '0';请求本身无固定优先级排序。优先级完全由 SCAN 调度算法的方向策略决定:
- 方向内近优先:同方向上,距离当前楼层最近的请求先响应
- 同方向优先于反方向:先服务完当前方向的所有请求,再换向
- 内选等同外呼:
req_stop与req_up/req_down在调度中地位相同,不区分优先级 - 报警最高优先级:报警按钮立即中断一切正常调度(见第 6 节)
type direction_type is (DIR_UP, DIR_DOWN, DIR_IDLE);
signal direction : direction_type;DIR_UP:电梯当前处于"上行模式",优先向上搜索请求DIR_DOWN:电梯当前处于"下行模式",优先向下搜索请求DIR_IDLE:电梯空闲,无确定方向
与调度相关的状态(完整状态机含门控、报警等,此处仅列调度核心):
IDLE -- 电梯空闲,等待请求
MOVING_UP -- 电梯上行中(每个慢时钟周期移动一层)
MOVING_DOWN -- 电梯下行中
ARRIVED -- 到达目标楼层,准备开门
DOOR_OPENING / DOOR_OPEN / DOOR_CLOSING -- 门控序列(见第 3 节)
CHECK_REQ -- 关门后检查请求,决定下一步动作
RETURNING -- 空闲返回 1 楼
当电梯处于 IDLE 状态(停在某层,门已关闭,无方向)时:
伪代码 -- IDLE 状态的请求选择:
IF req_exist = '0' THEN
-- 无请求,保持 IDLE
保持 IDLE 状态
空闲计时器递增(见第 5 节)
ELSIF req_any(current_floor) = '1' THEN
-- 当前楼层有请求,直接开门
direction <= DIR_IDLE -- 暂不确定方向
转入 DOOR_OPENING
ELSIF req_above = '1' AND req_below = '0' THEN
-- 仅上方有请求
direction <= DIR_UP
转入 MOVING_UP
ELSIF req_below = '1' AND req_above = '0' THEN
-- 仅下方有请求
direction <= DIR_DOWN
转入 MOVING_DOWN
ELSIF req_above = '1' AND req_below = '1' THEN
-- 上下均有请求,默认先上行
direction <= DIR_UP
转入 MOVING_UP
END IF
关键决策:上下均有请求时默认先上行(DIR_UP)。这是一个简单的约定,确保行为确定性。
当电梯处于 MOVING_UP 状态或在 CHECK_REQ 中 direction = DIR_UP 时:
伪代码 -- 上行模式运动逻辑:
MOVING_UP 状态下,每个慢时钟周期:
current_floor <= current_floor + 1
到达新楼层后,检查是否需要停靠:
IF need_stop_here = '1' THEN
-- 该楼层有同方向请求或内选请求,停靠
转入 ARRIVED → DOOR_OPENING
ELSE
-- 该楼层无需停靠
IF current_floor = 3 THEN
-- 已到顶楼,必须停止(即使无请求也不能继续上行)
-- 检查是否有下方请求,决定换向
转入 CHECK_REQ
ELSE
-- 未到顶楼,继续上行
保持 MOVING_UP
END IF
END IF
need_stop_here 在上行模式下的展开:
need_stop_here(上行模式) =
req_stop(current_floor) -- 内选该层
OR req_up(current_floor) -- 同方向(上行)外呼
-- 注意:不停靠反方向外呼(req_down),除非是顶楼
OR (current_floor = 3 AND req_down(3)) -- 顶楼的下行外呼视为需要停靠
伪代码 -- 下行模式运动逻辑:
MOVING_DOWN 状态下,每个慢时钟周期:
current_floor <= current_floor - 1
到达新楼层后,检查是否需要停靠:
IF need_stop_here = '1' THEN
转入 ARRIVED → DOOR_OPENING
ELSE
IF current_floor = 1 THEN
-- 已到底楼
转入 CHECK_REQ
ELSE
保持 MOVING_DOWN
END IF
END IF
need_stop_here 在下行模式下的展开:
need_stop_here(下行模式) =
req_stop(current_floor) -- 内选该层
OR req_down(current_floor) -- 同方向(下行)外呼
OR (current_floor = 1 AND req_up(1)) -- 底楼的上行外呼视为需要停靠
方向切换发生在 CHECK_REQ 状态中,即关门完成后或到达终端楼层后:
伪代码 -- CHECK_REQ 状态的方向决策:
IF req_exist = '0' THEN
-- 无任何请求
direction <= DIR_IDLE
转入 IDLE
ELSIF direction = DIR_UP THEN
IF req_above = '1' THEN
-- 上方仍有请求,继续上行
保持 direction = DIR_UP
转入 MOVING_UP
ELSIF req_below = '1' THEN
-- 上方无请求,下方有请求,换向下行
direction <= DIR_DOWN
转入 MOVING_DOWN
ELSE
-- 仅当前层有请求(理论上已在 ARRIVED 处理)
转入 DOOR_OPENING
END IF
ELSIF direction = DIR_DOWN THEN
IF req_below = '1' THEN
-- 下方仍有请求,继续下行
保持 direction = DIR_DOWN
转入 MOVING_DOWN
ELSIF req_above = '1' THEN
-- 下方无请求,上方有请求,换向上行
direction <= DIR_UP
转入 MOVING_UP
ELSE
转入 DOOR_OPENING
END IF
ELSIF direction = DIR_IDLE THEN
-- 从 IDLE 进入(当前层开门后关门,检查新请求)
IF req_above = '1' THEN
direction <= DIR_UP
转入 MOVING_UP
ELSIF req_below = '1' THEN
direction <= DIR_DOWN
转入 MOVING_DOWN
ELSE
direction <= DIR_IDLE
转入 IDLE
END IF
END IF
以电梯从 1 楼空闲开始为例:
初始:current_floor=1, direction=DIR_IDLE, 所有请求='0'
时刻 T0: up1='1', down3='1' 同时按下
→ req_up(1)='1', req_down(3)='1'
→ IDLE 检测到 req_any(1)='1'(当前层有请求)
→ 转入 DOOR_OPENING,清除 req_up(1)
→ direction 暂为 DIR_IDLE
时刻 T1: 开门→等待→关门完成
→ 转入 CHECK_REQ
→ direction=DIR_IDLE, req_above='1'(3楼有请求)
→ direction <= DIR_UP, 转入 MOVING_UP
时刻 T2: current_floor=2, 检查 need_stop_here
→ req_stop(2)='0', req_up(2)='0' → 不停靠
→ 继续 MOVING_UP
时刻 T3: current_floor=3, 检查 need_stop_here
→ req_down(3)='1', 且 current_floor=3(顶楼下行外呼视为需停靠)
→ 转入 ARRIVED → DOOR_OPENING,清除 req_down(3)
时刻 T4: 开门→等待→关门
→ CHECK_REQ: req_exist='0'
→ direction <= DIR_IDLE, 转入 IDLE
→ 空闲计时器开始计数
电梯在 MOVING_UP 或 MOVING_DOWN 状态中,请求寄存器仍在接收新按键:
规则:
1. 运动过程中,请求寄存器可以在任意时刻被置位
2. 每到达一个新楼层,重新计算 need_stop_here
3. 新请求在方向上若为同向且在前方,会被自动纳入路径
4. 新请求在反方向,会在当前方向无更多请求后换向时处理
5. 新请求恰好是当前正在经过的楼层 -- 不会停靠(因为判断发生在到达时刻)
但下一个到达楼层开始,正常判断
门控逻辑作为主状态机的子序列,包含以下状态:
DOOR_OPENING -- 开门动作(模拟用时 1 秒)
DOOR_OPEN -- 门完全打开,开始 10 秒倒计时等待
DOOR_CLOSING -- 关门动作(模拟用时 1 秒)
使用一个整数计数器实现,在 1Hz 慢时钟(或主时钟分频使能脉冲)下递增:
signal door_timer : integer range 0 to 10; -- 0~10,计到 10 表示 10 秒到定时器行为:
伪代码 -- door_timer 控制逻辑:
在时序进程中:
CASE current_state IS
WHEN DOOR_OPENING =>
door_timer <= 0 -- 重置计时器
-- 开门动作持续 1 秒(1 个慢时钟周期)
-- 1 秒后转入 DOOR_OPEN
WHEN DOOR_OPEN =>
IF close_btn = '1' THEN
door_timer <= 0 -- 手动关门,立即跳过等待
-- 转入 DOOR_CLOSING
ELSIF door_timer >= 9 THEN
door_timer <= 0 -- 10 秒到(计数 0~9 共 10 拍)
-- 转入 DOOR_CLOSING
ELSE
door_timer <= door_timer + 1 -- 继续计时
-- 保持 DOOR_OPEN
END IF
WHEN DOOR_CLOSING =>
door_timer <= 0 -- 复位
IF open_btn = '1' THEN
-- 关门中按开门 → 重新开门
-- 转入 DOOR_OPENING
ELSE
-- 关门动作持续 1 秒后
-- 转入 CHECK_REQ
END IF
WHEN OTHERS =>
door_timer <= 0
END CASE
计数范围说明:door_timer 从 0 计到 9,经过 10 个慢时钟周期(每周期 1 秒),总计 10 秒。当 door_timer = 9 时(即第 10 个周期结束),下一拍转入 DOOR_CLOSING。
开门动作本身需要时间模拟(真实电梯开门约 2~3 秒,本设计简化为 1 秒):
方案 A(简单):DOOR_OPENING 状态持续 1 个慢时钟周期后自动转入 DOOR_OPEN
方案 B(更真实):使用额外计数器 door_action_timer
DOOR_OPENING: door_action_timer 从 0 计到 OPEN_TIME-1 后转入 DOOR_OPEN
DOOR_CLOSING: door_action_timer 从 0 计到 CLOSE_TIME-1 后转入 CHECK_REQ
推荐方案 A(简单),因为课程设计中门的开关速度不是重点。
open_btn 的作用:在关门过程中可以中断关门,重新回到开门状态。
伪代码 -- open_btn 处理:
仅在 DOOR_CLOSING 状态下有效:
IF open_btn = '1' THEN
转入 DOOR_OPENING -- 重新开门
door_timer <= 0 -- 10 秒计时器重置
END IF
在 DOOR_OPEN 状态下:
IF open_btn = '1' THEN
door_timer <= 0 -- 重置 10 秒计时器(延长等待)
END IF
在其他状态下(运动、空闲):
open_btn 无效,忽略
close_btn 的作用:在门打开等待期间可以提前关门。
伪代码 -- close_btn 处理:
仅在 DOOR_OPEN 状态下有效:
IF close_btn = '1' THEN
door_timer <= 0 -- 清除计时
转入 DOOR_CLOSING -- 立即开始关门
END IF
在 DOOR_OPENING 状态下:
close_btn 无效(开门未完成不能关门)
在其他状态下:
close_btn 无效
当电梯到达目标楼层(ARRIVED 状态)时,触发开门序列:
时序图(每行代表 1 个慢时钟周期 = 1 秒):
周期 0: ARRIVED → 清除当前层请求 → 转入 DOOR_OPENING
周期 1: DOOR_OPENING → 门开启中(door_open_sig <= '1')→ 转入 DOOR_OPEN
周期 2: DOOR_OPEN, door_timer=0
周期 3: DOOR_OPEN, door_timer=1
...
周期 11: DOOR_OPEN, door_timer=9 → 10秒到 → 转入 DOOR_CLOSING
周期 12: DOOR_CLOSING → 门关闭中 → 转入 CHECK_REQ
周期 13: CHECK_REQ → 检查请求 → 决定下一步动作
ARRIVED
│
▼
DOOR_OPENING ──(1 秒后)──▶ DOOR_OPEN
│
├── close_btn='1' ──▶ DOOR_CLOSING
├── open_btn='1' ──▶ 重置 door_timer,保持 DOOR_OPEN
├── door_timer=9 ──▶ DOOR_CLOSING
└── 否则 ──▶ door_timer+1,保持 DOOR_OPEN
│
DOOR_CLOSING ◀────────────────────────────────────────────┘
│
├── open_btn='1' ──▶ DOOR_OPENING(重新开门,计时重置)
└── 1 秒后 ──▶ CHECK_REQ
│
├── req_exist='0' ──▶ IDLE
├── 同方向有请求 ──▶ MOVING_UP 或 MOVING_DOWN
└── 仅反方向有请求 ──▶ 换向 ──▶ MOVING_UP 或 MOVING_DOWN
-- 门状态输出
door_open_sig <= '1' when (current_state = DOOR_OPENING
or current_state = DOOR_OPEN) else '0';
door_close_sig <= '1' when (current_state = DOOR_CLOSING) else '0';
-- 门状态 LED 指示
door_led_green <= door_open_sig; -- 开门时绿灯亮
door_led_red <= door_close_sig; -- 关门时红灯亮前提:电梯刚离开 2 楼,direction = DIR_UP,current_floor 即将变为 3。
状态分析:
current_floor = 2(正在上行)
direction = DIR_UP
req_down(1) = '0'(1 楼不存在下行按钮,此请求无效)
-- 重新理解:1 楼有请求 → req_stop(1)='1' 或 req_up(1)='1'
-- 3 楼有请求 → req_stop(3)='1' 或 req_down(3)='1'
-- 注意:3 楼不存在上行按钮,req_up(3)恒='0'
处理流程:
T0: MOVING_UP, current_floor 从 2 变为 3
T1: 到达 3 楼,检查 need_stop_here
→ req_stop(3)='1' 或 req_down(3)='1'(3 楼有请求,且在顶楼)
→ need_stop_here = '1'
→ 转入 ARRIVED → DOOR_OPENING,清除 3 楼请求
T2~T12: 开门→等待 10 秒→关门
T13: CHECK_REQ
→ req_above = '0'(已在顶楼)
→ req_below = '1'(1 楼有请求)
→ direction <= DIR_DOWN
→ 转入 MOVING_DOWN
T14: current_floor = 2,检查 need_stop_here → '0'(2 楼无请求)
→ 继续 MOVING_DOWN
T15: current_floor = 1,检查 need_stop_here
→ req_stop(1)='1' 或 req_up(1)='1'
→ 停靠,开门,清除请求
结论:先去 3 楼(同方向优先),再回 1 楼。
状态分析:
current_state = DOOR_OPEN
current_floor = 2
door_timer 正在计数中
此时 req_stop(3) 或 req_down(3) 被置位
处理流程:
门控序列不受新请求影响,继续执行:
1. door_timer 继续计到 9(或乘客按 close_btn 提前关门)
2. 转入 DOOR_CLOSING
3. 关门完成后转入 CHECK_REQ
4. 在 CHECK_REQ 中:
- 若之前 direction = DIR_UP:req_above='1'(3 楼有请求),继续上行
- 若之前 direction = DIR_DOWN:req_below='0'(1 楼无请求),req_above='1',换向上行
- 若之前 direction = DIR_IDLE:req_above='1',direction <= DIR_UP,上行
结论:等关门后上行去 3 楼。门控序列不被新请求中断。
状态分析:
current_state = MOVING_UP
current_floor 刚从 2 变为 3(或正处于 2→3 的运动中)
req_stop(2) 被置位
关键时序分析:
电梯运动采用慢时钟(1Hz),每个时钟周期移动一层。
current_floor 的更新发生在时钟上升沿。
情况 1:req_stop(2) 在 current_floor 已经变为 3 之后才置位
→ 电梯已不在 2 楼,2 楼请求不影响当前运动
→ 3 楼判断 need_stop_here 时不看 req_stop(2)
→ 2 楼请求将在换向下行时处理
情况 2:req_stop(2) 在 current_floor 还是 2 时的同一时钟周期置位
→ 电梯在该周期到达 2 楼(从 1 楼上来),need_stop_here 判断已在组合逻辑中完成
→ 若 need_stop_here 在该拍 = '1'(取决于时序),会停靠
→ 若按钮按下稍晚(在 need_stop_here 判断之后),则不会停靠
设计决策:
运动状态下,楼层移动和停靠判断在同一时钟沿完成。
按钮请求的置位也在同一时钟沿完成。
由于双进程 FSM 中组合逻辑先于时序逻辑求值,
实际行为取决于按钮消抖后脉冲和楼层变化的相对时序。
简化处理:如果电梯正在移动经过某楼层,且在移动的同一拍该楼层按钮被按下,
按保守策略不停靠(已过)。请求保留,待换向时处理。
结论:不停(已过该楼层),请求保留,后续换向时处理。
状态分析:
假设电梯在 2 楼 IDLE 状态,同一时刻:
req_up(1), req_up(2), req_down(2), req_down(3), req_stop(1), req_stop(2), req_stop(3) 全部置位
处理流程:
T0: IDLE 状态检测
→ req_any(current_floor=2) = '1'(req_up(2) 和 req_down(2) 和 req_stop(2) 都是 '1')
→ 当前层有请求,先开门
→ 转入 DOOR_OPENING,清除 req_stop(2)
→ direction 暂为 DIR_IDLE
T1~T11: 开门→等待→关门
T12: CHECK_REQ
→ direction = DIR_IDLE
→ req_above = '1'(3 楼有请求)
→ direction <= DIR_UP(默认先上行)
→ 转入 MOVING_UP
T13: current_floor=3, need_stop_here='1'
→ 停靠,清除 req_down(3), req_stop(3)
T14~T24: 开门→等待→关门
T25: CHECK_REQ
→ direction = DIR_UP, req_above='0'
→ req_below='1'(1 楼有请求,2 楼可能仍有 req_up(2) 或 req_down(2))
→ direction <= DIR_DOWN
→ 转入 MOVING_DOWN
T26: current_floor=2, need_stop_here
→ req_down(2)='1'(同方向下行外呼)→ 停靠,清除 req_down(2)
T27~T37: 开门→等待→关门
T38: CHECK_REQ
→ direction=DIR_DOWN, req_below='1'(1 楼有请求)
→ 转入 MOVING_DOWN
T39: current_floor=1, need_stop_here
→ req_stop(1)='1' 或 req_up(1)='1' → 停靠,清除
继续类推,直到所有请求清空。
结论:按 SCAN 方向优先原则处理 -- 先当前层,再上行方向,再换向下行。
上行途中遇到的上行外呼会被顺路响应。
具体清除顺序注意:每次开门只清除与当前方向一致的外呼。
2 楼的 req_up(2) 在上行途中经过时已被清除(同方向外呼)。
状态分析:
current_state = MOVING_UP 或 MOVING_DOWN
alarm_btn 被按下(消抖后脉冲='1')
处理流程:
报警按钮具有最高优先级,在任何状态下(除 IDLE 在 1 楼外)均可触发。
T0: 检测到 alarm_btn_pulse = '1'
→ 立即转入 ALARM_STATE
→ 保存当前状态和楼层(alarm_saved_state, alarm_saved_floor)
→ 电机停止(motor_up <= '0', motor_down <= '0')
→ 所有请求寄存器保持不变(不清除)
ALARM_STATE 下:
→ 电梯不响应任何楼层请求
→ 门控按钮无效
→ 报警 LED 闪烁,蜂鸣器报警(见第 6 节)
→ 等待报警解除信号
报警解除后:
→ 回到报警前的状态继续执行
→ 或保守策略:回到当前楼层的 IDLE 状态,重新调度
推荐实现(简单可靠):
报警解除后转入 IDLE 状态(不恢复之前的运动状态)
请求寄存器保留,IDLE 会重新调度
结论:立即停止运动,进入报警状态,报警解除后重新调度。
状态分析:
current_state = DOOR_OPEN
door_timer = 5(例如已等了 5 秒)
close_btn 被按下
处理流程:
T0: DOOR_OPEN 状态检测到 close_btn = '1'
→ door_timer <= 0(重置计时器)
→ 转入 DOOR_CLOSING
T1: DOOR_CLOSING → 1 秒后 → 转入 CHECK_REQ
结论:立即跳过剩余等待时间,开始关门。
从按下关门到门完全关闭约 1 秒(关门动作时间)。
状态分析:
current_state = DOOR_CLOSING
open_btn 被按下
处理流程:
T0: DOOR_CLOSING 状态检测到 open_btn = '1'
→ 转入 DOOR_OPENING
→ door_timer <= 0(重置,重新开始 10 秒计时)
T1: DOOR_OPENING → 1 秒后 → 转入 DOOR_OPEN
T2: DOOR_OPEN, door_timer=0(重新开始 10 秒等待)
结论:中断关门动作,重新完整执行开门→等待→关门序列。
10 秒计时器重新从 0 开始。
状态分析:
current_state = IDLE
current_floor ≠ 1(不在 1 楼)
req_exist = '0' 持续 5 秒
处理流程:见第 5 节"空闲返回 1 楼逻辑"。
简述:
idle_timer 从 0 计到 4(5 个慢时钟周期 = 5 秒)
计满后:
direction <= DIR_DOWN
转入 MOVING_DOWN(或 RETURNING 状态)
逐层下降直到 current_floor = 1
到达 1 楼后不开门,直接转入 IDLE
结论:5 秒无请求后自动下行返回 1 楼。
signal idle_timer : integer range 0 to 5; -- 0~5,计到 5 时触发返回伪代码 -- idle_timer 逻辑(在时序进程中):
IF current_state = IDLE THEN
IF req_exist = '1' THEN
idle_timer <= 0 -- 有请求,重置计时
ELSIF current_floor = 1 THEN
idle_timer <= 0 -- 已在 1 楼,无需返回
ELSIF idle_timer >= 4 THEN
-- 5 秒到(计数 0~4 共 5 拍),触发返回
idle_timer <= 0
-- 转入 RETURNING 状态
ELSE
idle_timer <= idle_timer + 1
END IF
ELSE
idle_timer <= 0 -- 非 IDLE 状态,重置计时
END IF
返回 1 楼使用独立的 RETURNING 状态,行为类似 MOVING_DOWN 但有以下区别:
伪代码 -- RETURNING 状态:
RETURNING:
direction <= DIR_DOWN -- 方向灯显示下行
每个慢时钟周期:
current_floor <= current_floor - 1
到达新楼层后:
IF req_exist = '1' THEN
-- 返回途中出现新请求,中断返回,转入正常调度
转入 CHECK_REQ
ELSIF current_floor = 1 THEN
-- 到达 1 楼
direction <= DIR_IDLE
转入 IDLE
-- 不开门(无乘客等待)
ELSE
-- 继续下降
保持 RETURNING
END IF
| 问题 | 决策 | 理由 |
|---|---|---|
| 返回 1 楼时需要开门吗? | 不开门 | 返回是因为无人使用,开门无意义 |
| 方向灯如何显示? | 显示下行灯 | 让外部观察者知道电梯在移动 |
| 返回途中有新请求怎么办? | 中断返回,转入正常调度 | 响应用户请求优先于空闲返回 |
| 已在 1 楼时怎么处理? | 保持 IDLE,不触发返回 | idle_timer 不计数,保持 0 |
| 2 楼空闲,返回到 1 楼的过程 | 2 楼不开门 → 下行到 1 楼 → 不开门 → IDLE | 全程无门动作 |
伪代码 -- RETURNING 中途检查:
RETURNING 状态,到达中间楼层(仅可能是 2 楼,因为从 3 楼返回):
IF req_any(current_floor) = '1' THEN
-- 当前层有请求,停下来服务
转入 ARRIVED → DOOR_OPENING
-- 方向由 CHECK_REQ 重新决定
ELSIF req_exist = '1' THEN
-- 其他层有请求,转入正常调度
转入 CHECK_REQ
ELSE
-- 继续返回
current_floor <= current_floor - 1
END IF
进入 ALARM_STATE 的条件:
alarm_btn_pulse = '1'(消抖后的按钮单周期脉冲)
AND current_state ≠ ALARM_STATE(避免重复进入)
可从以下状态进入报警:
IDLE, MOVING_UP, MOVING_DOWN, DOOR_OPENING, DOOR_OPEN,
DOOR_CLOSING, CHECK_REQ, ARRIVED, RETURNING
即:从任何非报警状态均可进入。
方案选择:
方案 A(简单):再次按下 alarm_btn 退出
退出条件:alarm_btn_pulse = '1' AND current_state = ALARM_STATE
退出后:转入 IDLE 状态,保留所有请求
方案 B(安全):使用独立的 reset 按钮退出
退出条件:系统复位信号 rst_n = '0'
退出后:所有状态归零,回到初始状态
推荐方案 A,因为:
- reset 是全局复位,会清除所有请求和状态
- alarm 更像一个可恢复的暂停
- 真实电梯也允许解除报警后继续运行
ALARM_STATE 下各输出信号:
motor_up <= '0' -- 电机停止
motor_down <= '0' -- 电机停止
door_open_sig <= '0' -- 门不动作(保持报警前状态)
door_close_sig<= '0' -- 门不动作
dir_up_led <= '0' -- 方向灯全灭
dir_down_led <= '0'
alarm_led <= alarm_blink -- 1Hz 闪烁
buzzer <= buzzer_pattern -- 间歇报警音
floor_display <= current_floor -- 继续显示当前楼层(供救援参考)
-- 请求寄存器:保持不变(不清除,也不接受新请求)
-- 报警期间新按下的请求按钮被忽略(可选策略)
报警 LED 以 1Hz 频率闪烁(亮 0.5 秒,灭 0.5 秒):
-- 使用慢时钟(1Hz)直接翻转
signal alarm_blink : std_logic;
-- 在时序进程中:
IF current_state = ALARM_STATE THEN
alarm_blink <= NOT alarm_blink; -- 每秒翻转一次 → 1Hz 闪烁(0.5s亮/0.5s灭)
ELSE
alarm_blink <= '0';
END IF;
-- 报警 LED 输出
alarm_led <= alarm_blink when current_state = ALARM_STATE else '0';若需要更快的闪烁(2Hz),可使用单独的分频计数器或更高频率的使能信号。
蜂鸣器产生间歇性报警音("嘟-嘟-嘟"模式):
报警音模式:响 0.5 秒 → 停 0.5 秒 → 响 0.5 秒 → 停 0.5 秒 ...
实现方式:
蜂鸣器驱动 = 报警使能信号 AND 蜂鸣器载波
报警使能信号 = alarm_blink(1Hz 方波,与 LED 同步)
蜂鸣器载波 = 约 1kHz~4kHz 方波(产生可听见的"嘟"声)
-- 蜂鸣器载波产生(假设主时钟 50MHz,产生 2kHz 方波)
-- 50MHz / (2 * 2000) = 12500
signal buzzer_cnt : integer range 0 to 12499;
signal buzzer_carrier : std_logic;
process(clk, rst_n)
begin
if rst_n = '0' then
buzzer_cnt <= 0;
buzzer_carrier <= '0';
elsif rising_edge(clk) then
if buzzer_cnt >= 12499 then
buzzer_cnt <= 0;
buzzer_carrier <= not buzzer_carrier;
else
buzzer_cnt <= buzzer_cnt + 1;
end if;
end if;
end process;
-- 蜂鸣器输出
-- 报警模式:间歇性响(1Hz 调制的 2kHz 载波)
-- 到站提示:短响一声(0.5 秒的 2kHz 载波)
buzzer <= buzzer_carrier AND alarm_blink when current_state = ALARM_STATE
else buzzer_carrier AND arrival_beep when current_state = ARRIVED
else '0';电梯到达楼层时短响一声(约 0.5 秒):
信号:arrival_beep : std_logic
在时序进程中:
WHEN ARRIVED =>
arrival_beep <= '1' -- 使能蜂鸣器
-- 持续时间由 ARRIVED 状态的持续时间决定(1 个慢时钟周期 = 1 秒)
-- 若需更短可用额外计数器
WHEN OTHERS =>
arrival_beep <= '0'
ALARM_STATE 下的输入处理策略:
alarm_btn: 有效 -- 再次按下用于退出报警
open_btn: 无效 -- 忽略
close_btn: 无效 -- 忽略
up1~up2: 无效 -- 忽略(请求不置位)
down2~down3: 无效 -- 忽略
stop1~stop3: 无效 -- 忽略
rst_n: 有效 -- 全局复位始终有效
实现方式:在请求置位逻辑中增加条件
IF current_state /= ALARM_STATE THEN
-- 正常置位逻辑
IF up1_pulse = '1' THEN req_up(1) <= '1'; END IF;
...
END IF;
状态 | 转移条件 | 目标状态 | 动作
----------------|---------------------------------------|----------------|---------------------------
IDLE | alarm_btn_pulse='1' | ALARM_STATE | 保存状态
IDLE | req_any(current_floor)='1' | DOOR_OPENING | 清除当前层请求
IDLE | req_above='1' (only) | MOVING_UP | direction<=DIR_UP
IDLE | req_below='1' (only) | MOVING_DOWN | direction<=DIR_DOWN
IDLE | req_above='1' AND req_below='1' | MOVING_UP | direction<=DIR_UP
IDLE | idle_timer>=4 AND floor≠1 | RETURNING | direction<=DIR_DOWN
| | |
MOVING_UP | alarm_btn_pulse='1' | ALARM_STATE | 电机停
MOVING_UP | need_stop_here='1' | ARRIVED | --
MOVING_UP | current_floor=3 AND need_stop='0' | CHECK_REQ | --
MOVING_UP | 其他 | MOVING_UP | floor+1
| | |
MOVING_DOWN | alarm_btn_pulse='1' | ALARM_STATE | 电机停
MOVING_DOWN | need_stop_here='1' | ARRIVED | --
MOVING_DOWN | current_floor=1 AND need_stop='0' | CHECK_REQ | --
MOVING_DOWN | 其他 | MOVING_DOWN | floor-1
| | |
ARRIVED | 无条件(1拍过渡) | DOOR_OPENING | 清除请求, beep
| | |
DOOR_OPENING | alarm_btn_pulse='1' | ALARM_STATE | --
DOOR_OPENING | 1秒到 | DOOR_OPEN | timer<=0
| | |
DOOR_OPEN | alarm_btn_pulse='1' | ALARM_STATE | --
DOOR_OPEN | close_btn='1' | DOOR_CLOSING | timer<=0
DOOR_OPEN | open_btn='1' | DOOR_OPEN | timer<=0 (重置)
DOOR_OPEN | door_timer>=9 | DOOR_CLOSING | timer<=0
DOOR_OPEN | 其他 | DOOR_OPEN | timer+1
| | |
DOOR_CLOSING | alarm_btn_pulse='1' | ALARM_STATE | --
DOOR_CLOSING | open_btn='1' | DOOR_OPENING | timer<=0
DOOR_CLOSING | 1秒到 | CHECK_REQ | --
| | |
CHECK_REQ | req_exist='0' | IDLE | direction<=DIR_IDLE
CHECK_REQ | 同方向有请求 | MOVING_UP/DOWN | 保持方向
CHECK_REQ | 仅反向有请求 | MOVING_UP/DOWN | 换向
CHECK_REQ | 仅当前层有请求 | DOOR_OPENING | 清除请求
| | |
RETURNING | alarm_btn_pulse='1' | ALARM_STATE | 电机停
RETURNING | current_floor=1 | IDLE | direction<=DIR_IDLE
RETURNING | req_exist='1' | CHECK_REQ | 中断返回
RETURNING | 其他 | RETURNING | floor-1
| | |
ALARM_STATE | alarm_btn_pulse='1' | IDLE | 解除报警
ALARM_STATE | 其他 | ALARM_STATE | LED闪烁,蜂鸣器响
-- ======== 输入信号(经消抖后) ========
signal up1_pulse, up2_pulse : std_logic; -- 上行外呼脉冲
signal down2_pulse, down3_pulse : std_logic; -- 下行外呼脉冲
signal stop1_pulse, stop2_pulse, stop3_pulse : std_logic; -- 内选停站脉冲
signal open_btn, close_btn : std_logic; -- 门控按钮(电平有效)
signal alarm_btn_pulse : std_logic; -- 报警按钮脉冲
-- ======== 请求寄存器 ========
signal req_up : std_logic_vector(3 downto 1); -- (3)恒='0'
signal req_down : std_logic_vector(3 downto 1); -- (1)恒='0'
signal req_stop : std_logic_vector(3 downto 1);
-- ======== 聚合信号(组合逻辑) ========
signal req_any : std_logic_vector(3 downto 1);
signal req_above : std_logic;
signal req_below : std_logic;
signal req_exist : std_logic;
signal need_stop_here : std_logic;
-- ======== 状态机 ========
type state_type is (
IDLE, MOVING_UP, MOVING_DOWN, ARRIVED,
DOOR_OPENING, DOOR_OPEN, DOOR_CLOSING,
CHECK_REQ, RETURNING, ALARM_STATE
);
signal current_state, next_state : state_type;
-- ======== 运行状态 ========
type direction_type is (DIR_UP, DIR_DOWN, DIR_IDLE);
signal direction : direction_type;
signal current_floor : integer range 1 to 3;
-- ======== 计时器 ========
signal door_timer : integer range 0 to 10;
signal idle_timer : integer range 0 to 5;
-- ======== 输出信号 ========
signal motor_up, motor_down : std_logic; -- 电机方向控制
signal door_open_sig : std_logic; -- 门开启信号
signal door_close_sig : std_logic; -- 门关闭信号
signal dir_up_led, dir_down_led : std_logic; -- 方向指示灯
signal door_led_green : std_logic; -- 开门指示(绿)
signal door_led_red : std_logic; -- 关门指示(红)
signal alarm_led : std_logic; -- 报警灯
signal alarm_blink : std_logic; -- 报警闪烁信号
signal buzzer : std_logic; -- 蜂鸣器
signal arrival_beep : std_logic; -- 到站提示
signal seg7 : std_logic_vector(6 downto 0); -- 七段数码管
signal floor_leds : std_logic_vector(3 downto 1); -- 楼层指示灯主时钟 (clk, 50MHz)
│
├── 消抖模块:直接使用主时钟,内部计数 20ms
│
├── 分频产生 1Hz 使能脉冲 (clk_1hz_en)
│ │
│ ├── 状态机状态转移
│ ├── 楼层位置更新 (current_floor +/- 1)
│ ├── 门控计时器 (door_timer)
│ ├── 空闲计时器 (idle_timer)
│ └── 报警 LED 闪烁 (alarm_blink)
│
└── 分频产生 2kHz 方波 (buzzer_carrier)
│
└── 蜂鸣器驱动
注意:所有逻辑在同一主时钟域内工作。
1Hz 使能脉冲不作为门控时钟使用,而是作为时钟使能 (clock enable)。
即在 rising_edge(clk) 内部用 if clk_1hz_en = '1' then ... 来控制慢速逻辑。
这避免了门控时钟带来的时序问题。
使能脉冲方式的 VHDL 模板:
-- 分频计数器,产生 1Hz 使能脉冲
signal div_cnt : integer range 0 to 49999999; -- 50M-1
signal clk_1hz_en : std_logic;
process(clk, rst_n)
begin
if rst_n = '0' then
div_cnt <= 0;
clk_1hz_en <= '0';
elsif rising_edge(clk) then
if div_cnt >= 49999999 then
div_cnt <= 0;
clk_1hz_en <= '1'; -- 单周期脉冲
else
div_cnt <= div_cnt + 1;
clk_1hz_en <= '0';
end if;
end if;
end process;
-- 状态机时序进程中使用使能脉冲
process(clk, rst_n)
begin
if rst_n = '0' then
current_state <= IDLE;
current_floor <= 1;
-- ... 初始化
elsif rising_edge(clk) then
if clk_1hz_en = '1' then
current_state <= next_state;
-- ... 慢速逻辑更新
end if;
-- 请求置位逻辑不受使能脉冲限制(按钮响应需要快速)
-- ... 请求寄存器更新
end if;
end process;