项目三 电梯控制器设计(FPGA综合应用设计)

(一个很简陋的电梯控制器设计,但是应该可以过关了吧?‍️)

项目三 电梯控制器设计

  • 实验目的
  • 实验内容
  • 实验方法及原理介绍
  • 下面给出完整的代码实现:
    • scan_led_hex_disp模板:
    • debounce_button模板:
    • Elevator模板:
    • 引脚分配文件:
  • 上板实验效果:

实验目的

通过实验,巩固有限状态机设计方法,并设计实现一个电梯控制器。

实验内容

利用 BASYS 开发板资源设计一个 5 层楼的电梯控制器系统,并能在开发板上
模拟电梯运行状态,具体要求如下:

  1. 利用开发板的 5 个按键作为电梯控制器的呼叫按钮;
  2. 利用 led 灯分别显示楼层 1~5 的呼梯状态;
  3. 利用数码管显示电梯运行时电梯所在楼层;
  4. 利用时钟分频设计电梯控制器控制电梯每秒运行一层。

实验方法及原理介绍

  1. 电梯控制器系统控制流程图(电梯厢内视角)
    项目三 电梯控制器设计(FPGA综合应用设计)_第1张图片

  2. 系统输入/输出变量
    对于一个系统,首先需要一个时钟输入,设为 clk;按键输入,设为btn;数码管显示输出设为 seg;叫梯楼层状态灯输出,设为 nfloor。

  3. 按键设计

  • 本实验使用板上 5 个按键按钮模拟电梯的叫梯按键,1 层按键为 BTNU,2 层按键为 BTNL,3 层按键为 BTNC,4 层按键为 BTNR,5 层按键为 BTND。所以,定义一个 5 位按键寄存器 btn_pre_re,同时考虑到防抖(按键按下去和松开会产生抖动现象会影响到我们的操作),在对按键寄存器进行赋值的时候要注意时间延时。
  • 对于电梯按键,当没有叫梯时,按键相应的 LED 指示灯应处于熄灭状态;当有叫梯时,按键相应的 LED 指示灯应处于点亮状态;当在某一层已经叫梯,但是由于某种原因发现所叫梯不是自己想要的梯层时,能够取消此层的叫梯状态。
  • 防抖设计为每 200ms 读取一次叫梯按键信息,因此需要生成一个周期为 200ms 的时钟信号,程序代码如下:
parameter N=99_999999;
always@(posedge clk)
    begin
        clk_200ms<=0;
        if(count<N/5)
			count<=count+1;
		else 
		begin
			count<=0;
			clk_200ms<=1;
		end
	end
  • 叫梯按键赋值程序如下:
reg [4:0]btn_pre_re,btn_off;
always@(posedge clk_200ms)
	begin
		btn_pre_re=btn_pre_re^btn;
		btn_pre_re=btn_pre_re&btn_off;
	end
  • 需要注意的是,重复进行叫梯按键操作,可以进行叫梯或取消叫梯服务,因此使用了一个异或代码(请自行画出电路图)。
  1. 显示设计

电梯控制器包括两种显示,即数码管显示电梯所在楼层和 LED 灯显示所叫楼层服务。

下面给出完整的代码实现:

scan_led_hex_disp模板:

实现“电梯控制器包括两种显示,即数码管显示电梯所在楼层和 LED 灯显示所叫楼层服务”。

`timescale 1ns / 1ps

module scan_led_hex_disp(clk,reset,btn,an,sseg,current_floor,Floor_call_state_output);
    	parameter       n  =  5;  
        input clk;
    input reset;
    input [n-1:0] btn; //第一个数码管显示的数字:电梯叫梯楼层

    //input [3:0] dp_in, //小数点控制
    output reg [3:0] an;   //片选,使能端
    output reg [7:0] sseg; //段选

    output [n-1:0] current_floor;
     output  [n-1:0]   Floor_call_state_output;       //第一个数码管显示的数字:电梯所在楼层
     wire stop;

	localparam N = 18; //使用低16位对50Mhz的时钟进行分频(50MHZ/2^16)
	reg [N-1:0] regN; //高两位作为控制信号,低16位为计数器,对时钟进行分频
	reg [n-1:0] hex_in; //段选控制信号
	
    always@(posedge clk, posedge reset)
	begin
		if(reset)
			regN <= 0;
		else
			regN <= regN + 1;
	end
	
	reg dp; 
debounce_button u2(.clk(clk),.rst(reset),.key(btn),.key_pulse(),.current_floor(current_floor),.Floor_call_state_output(Floor_call_state_output),.stop(stop));

	always@ *
	begin
		case(regN[N-1:N-2])
		2'b00:begin
			an = 4'b0111; //选中第1个数码管
			hex_in = current_floor; //数码管显示的数字由hex_in控制,显示current_floor:电梯所在楼层
			dp = ~stop; //控制该数码管的小数点的亮灭
		end
		2'b01:begin
			an = 4'b0111; //选中第二个数码管
			hex_in = current_floor;//数码管显示的数字由hex_in控制,显示current_floor;电梯所在楼层
			dp = ~stop;
		end
		2'b10:begin
			an = 4'b1110;//选中第三个数码管
			hex_in = Floor_call_state_output;//数码管显示的数字由hex_in控制,显示Floor_call_state_output,也就是电梯叫梯的楼层;
			dp = 1;
		end
		default:begin
			an = 4'b1110;//选中第四个数码管
			hex_in =  Floor_call_state_output;//数码管显示的数字由hex_in控制,显示Floor_call_state_output,也就是电梯叫梯的楼层;
			dp = 1;
		end
		
		endcase
	
	end
	always@ *
	begin
		case(hex_in)
			5'b00000: sseg[6:0] = 7'b0000001;   //共阳极数码管
			5'b00001: sseg[6:0] = 7'b1001111;
			5'b00010: sseg[6:0] = 7'b0010010;
			5'b00100: sseg[6:0] = 7'b0000110;
			5'b01000: sseg[6:0] = 7'b1001100;
			5'b10000: sseg[6:0] = 7'b0100100;
			default: sseg[6:0] = 7'b1111111;
		endcase
		    sseg[7] = dp;   //控制小数点,小数点亮,代表电梯停止移动了;不亮,则是电梯上移动或者下移
	end
endmodule

debounce_button模板:

实现“按键消抖”。
本实验使用板上 5 个按键按钮模拟电梯的叫梯按键,1 层按键为 BTNU2 层按键为 BTNL3 层按键为 BTNC4 层按键为 BTNR5 层按键为 BTND。所以,定义一个 5 位按键寄存器 key_pulse,同时考虑到防抖(按键按下去和松开会产生抖动现象会影响到我们的操作),在对按键寄存器进行赋值的时候要注意时间延时。
最后,实现了“对于电梯按键,当没有叫梯时,按键相应的 LED 指示灯应处于熄灭状态;当有叫梯时,按键相应的 LED 指示灯应处于点亮状态;当在某一层已经叫梯,但是由于某种原因发现所叫梯不是自己想要的梯层时,能够取消此层的叫梯状态”。

`timescale 1ns / 1ps


module debounce_button (clk,rst,key,key_pulse,current_floor,Floor_call_state_output,stop);
 
        parameter       N  =  5;                      //要消除的按键的数量
 
	input             clk;
        input             rst;
        input 	[N-1:0]   key;                        //输入的按键					
	output  [N-1:0]   key_pulse;                  //按键动作产生的脉冲	
	       output [N-1:0]   current_floor;
	    output  [N-1:0]   Floor_call_state_output; 
	      output stop;
	

	reg [N-1:0]   key_pulse;  
 
        reg     [N-1:0]   key_rst_pre;                //定义一个寄存器型变量存储上一个触发时的按键值
        reg     [N-1:0]   key_rst;                    //定义一个寄存器变量储存储当前时刻触发的按键值
 
        wire    [N-1:0]   key_edge;                   //检测到按键由高到低变化是产生一个高脉冲
 
        //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中
        always @(posedge clk  or  negedge rst)
          begin
             if (rst) begin
                 key_rst <= {N{1'b1}};                //初始化时给key_rst赋值全为1,{}中表示N个1
                 key_rst_pre <= {N{1'b1}};
             end
             else begin
                 key_rst <= key;                     //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre
                 key_rst_pre <= key_rst;             //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值
             end    
           end
 
        assign  key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平
 
        reg	[17:0]	  cnt;                       //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器     
 
        //产生20ms延时,当检测到key_edge有效是计数器清零开始计数
        always @(posedge clk or negedge rst)
           begin
             if(rst)
                cnt <= 18'h0;
             else if(key_edge)
                cnt <= 18'h0;
             else
                cnt <= cnt + 1'h1;
             end  
 
        reg     [N-1:0]   key_sec_pre;                //延时后检测电平寄存器变量
        reg     [N-1:0]   key_sec;                    
 
 
        //延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效
        always @(posedge clk  or  negedge rst)
          begin
             if (rst) 
                 key_sec <= {N{1'b1}};                
             else if (cnt==18'h3ffff)
                 key_sec <= key;  
          end
       always @(posedge clk  or  negedge rst)
          begin
             if (rst)
                 key_sec_pre <= {N{1'b1}};
             else                   
                 key_sec_pre <= key_sec;             
         end      
       
       always @(posedge clk  or  negedge rst)
          begin
             if (rst)
                 key_pulse <= {N{1'b0}};
             else 
                 begin                                
       /*按键被按下后,存在一个时钟周期:key_sec_pre[x]=1和key_sec[x]=0。这时,key_pulse[x]<= ~key_pulse[x],反转一下。
                      实现要求中的“重复进行叫梯按键操作,可以进行叫梯或取消叫梯服务” */                  
                 if(~key_sec_pre[4] & key_sec[4])                 
                 key_pulse[4] <= ~key_pulse[4];    
                 if(~key_sec_pre[3] & key_sec[3])                  
                 key_pulse[3] <= ~key_pulse[3];   
                 if(~key_sec_pre[2] & key_sec[2])                  
                 key_pulse[2] <= ~key_pulse[2];    
                 if(~key_sec_pre[1] & key_sec[1])                  
                 key_pulse[1] <= ~key_pulse[1];  
                 if(~key_sec_pre[0] & key_sec[0])                  
                 key_pulse[0] <= ~key_pulse[0];
                 end  
         end  
       

       Elevator u1(
.clk(clk), .rst(rst),.Floor_call_state(key_pulse),.current_floor(current_floor),.Floor_call_state_output(Floor_call_state_output),.stop(stop)
    );
 
endmodule

Elevator模板:

实现“利用时钟分频设计电梯控制器控制电梯每秒运行一层”。

`timescale 1ns / 1ps

module Elevator(
clk, rst,Floor_call_state,current_floor,Floor_call_state_output,stop
    );
      	input             clk;
        input              rst;
        input 	 [4:0]   Floor_call_state;        //输入的按键	
        output reg [4:0]   current_floor;
        output reg [4:0]   Floor_call_state_output; 
        output reg stop;
        
        integer k;
        reg [30:0] cnt=30'b0;
        parameter M=100000000;             //1s=1000000000ns
        reg clk_out=0;
        
        initial begin
             stop=1;
             Floor_call_state_output=5'b00001;
             current_floor=5'b00001;
        end
        
       //时钟分频器:每过1s,clk_out=~clk_out。实现“每一秒钟,电梯移动一层”。
        always @(posedge clk)                
            begin
                if(rst)
                    begin
                        clk_out=0;
                    end
               //从0开始计时的,所以cnt==M-1,一个循环是 1s
                if (cnt==M-1)                   
                    begin
                        clk_out=~clk_out;
                        cnt=0;
                    end
                else 
                    begin
                        clk_out=0;             //每次重新开始,都使  clk_out=0。好在cnt==M-1时,clk_out=~clk_out,产生clk_out的上升沿,
                        cnt=cnt+1'd1;
                    end
            end
            
 //当Floor_call_state发证变化,或者rst发生变化时,将Floor_call_state赋给Floor_call_state_output。
       always @(*/*Floor_call_state,rst,current_floor,Floor_call_state_output*/)    
            begin
                 if(rst)
                     begin
                         Floor_call_state_output=5'b00001;
                     end
                 else
                     begin
                         Floor_call_state_output=Floor_call_state;
                     end
            end
              
        
       always @(posedge clk_out or posedge rst)
           begin
               if(rst)
                   begin
                       stop=1;
                       current_floor=5'b00001;
                   end
               else if(Floor_call_state_output!=5'b00000)
                   begin
                       stop=0;
                       if(current_floor<Floor_call_state_output)               //电梯所在楼层 低于 叫梯,就上升
                           current_floor={current_floor[3:0],current_floor[4]};
                       else if(current_floor>Floor_call_state_output)         //电梯所在楼层 高于 叫梯,就下降
                           current_floor={current_floor[0],current_floor[4:1]};
                       else
                           stop=1;
                   end
               else
                   begin
                       stop=1;
                   end
           end
           
endmodule

引脚分配文件:

set_property IOSTANDARD LVCMOS33 [get_ports {an[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {an[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {an[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {an[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {Floor_call_state_output[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {Floor_call_state_output[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {Floor_call_state_output[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {Floor_call_state_output[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {Floor_call_state_output[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {btn[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {btn[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {btn[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {btn[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {btn[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {current_floor[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {current_floor[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {current_floor[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {current_floor[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {current_floor[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sseg[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sseg[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sseg[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sseg[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sseg[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sseg[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sseg[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {sseg[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports reset]
set_property PACKAGE_PIN W4 [get_ports {an[3]}]
set_property PACKAGE_PIN V4 [get_ports {an[2]}]
set_property PACKAGE_PIN U4 [get_ports {an[1]}]
set_property PACKAGE_PIN U2 [get_ports {an[0]}]
set_property package_pin W18 [get_ports {Floor_call_state_output[4]}]
set_property PACKAGE_PIN U17 [get_ports {btn[4]}]
set_property package_pin V19 [get_ports {Floor_call_state_output[3]}]
set_property PACKAGE_PIN T17 [get_ports {btn[3]}]
set_property package_pin U19 [get_ports {Floor_call_state_output[2]}]
set_property PACKAGE_PIN U18 [get_ports {btn[2]}]
set_property package_pin E19 [get_ports {Floor_call_state_output[1]}]
set_property PACKAGE_PIN W19 [get_ports {btn[1]}]
set_property package_pin U16 [get_ports {Floor_call_state_output[0]}]
set_property PACKAGE_PIN T18 [get_ports {btn[0]}]
set_property PACKAGE_PIN U3 [get_ports {current_floor[0]}]
set_property PACKAGE_PIN P3 [get_ports {current_floor[1]}]
set_property PACKAGE_PIN N3 [get_ports {current_floor[2]}]
set_property PACKAGE_PIN P1 [get_ports {current_floor[3]}]
set_property PACKAGE_PIN L1 [get_ports {current_floor[4]}]
set_property PACKAGE_PIN W7 [get_ports {sseg[6]}]
set_property PACKAGE_PIN W6 [get_ports {sseg[5]}]
set_property PACKAGE_PIN U8 [get_ports {sseg[4]}]
set_property PACKAGE_PIN V8 [get_ports {sseg[3]}]
set_property PACKAGE_PIN U5 [get_ports {sseg[2]}]
set_property PACKAGE_PIN V5 [get_ports {sseg[1]}]
set_property PACKAGE_PIN U7 [get_ports {sseg[0]}]
set_property PACKAGE_PIN V7 [get_ports {sseg[7]}]
set_property PACKAGE_PIN W5 [get_ports clk]
set_property PACKAGE_PIN R2 [get_ports reset]
  • 上板子的逻辑可以这么想:最初电梯在1楼,叫梯为0楼(不叫梯);然后按下叫梯的楼层“2楼”,(2-1)s后,电梯运行到2楼,自动开门上人,然后再按下“2楼”,关门;接下来再按下想要去的楼层,重复以上操作。
  • 左边的七段数码管显示当前电梯所在楼层。
  • 右边的七段数码管显示当前叫梯楼层。(当前的代码,只能一层一层的叫梯,即叫完一梯后,要再按一次该梯,取消当前叫梯后,才能叫下一梯

上板实验效果:


当按下“5楼”叫梯,5s后:

你可能感兴趣的:(vivado实验,fpga开发,单片机,嵌入式硬件)