基于EP4CE6F17C8的FPGA矩阵键盘实例

一、电路模块

1、数码管

开发板板载了6个数码管,全部为共阳型,原理图如下图所示,段码端引脚为DIG[0]~DIG[7]共8位(包含小数点),位选端引脚为SEL[0]~SEL[5]共6位。端口均为低电平有效。

其实物图如下所示。

数码管引脚分配见下表。

2、时钟晶振

开发板板载了一个50MHz的有源晶振,为系统提供时钟。

其实物图如下所示。

时钟输出引脚分配见下表。

3、按键

开发板板载了4个独立按键,其中有3个用户按键(KEY1~KEY3),1个功能按键(RESET)。按键按下为低电平(0),释放为高电平(1),4个按键的原理图如下图所示。

其实物图如下所示。

按键的引脚分配见下表。

 

4、矩阵键盘

本例使用了开发板配套的矩阵键盘模块,共有16个独立按键,原理图如下图所示。

其实物图如下所示。

矩阵按键的引脚分配见下表。

二、实验代码

本例使用6个数码管依次显示按下按键的键值,每位显示的值可从0~F,对应16个矩阵按键。按键reset为复位键,代码使用Verilog编写,具体如下。

先编写数码管实现显示字形解码的程序,模块名称为seg_decode,文件名称为seg_decode.v,代码如下。

module seg_decode(
        input[4:0]         data,          //显示的字形,可显示0~F十六个字形,所以需要5位
        output reg[7:0]    seg7           //字形编码,包含小数点,共8位
);
always@(*)                                //敏感信号为所有输入量
begin
    case(data)
        5'd0:seg7 <= 8'b1100_0000;        //字形0的编码
        5'd1:seg7 <= 8'b1111_1001;        //字形1的编码
        5'd2:seg7 <= 8'b1010_0100;        //字形2的编码
        5'd3:seg7 <= 8'b1011_0000;        //字形3的编码
        5'd4:seg7 <= 8'b1001_1001;        //字形4的编码
        5'd5:seg7 <= 8'b1001_0010;        //字形5的编码
        5'd6:seg7 <= 8'b1000_0010;        //字形6的编码
        5'd7:seg7 <= 8'b1111_1000;        //字形7的编码
        5'd8:seg7 <= 8'b1000_0000;        //字形8的编码
        5'd9:seg7 <= 8'b1001_0000;        //字形9的编码
        5'ha:seg7 <= 8'b1000_1000;        //字形A的编码
        5'hb:seg7 <= 8'b1000_0011;        //字形B的编码
        5'hc:seg7 <= 8'b1100_0110;        //字形C的编码
        5'hd:seg7 <= 8'b1010_0001;        //字形D的编码
        5'he:seg7 <= 8'b1000_0110;        //字形E的编码
        5'hf:seg7 <= 8'b1000_1110;        //字形F的编码
        default:
             seg7 <= 8'b1111_1111;        //默认不显示
    endcase
end
endmodule

接下来编写矩阵键盘中的列扫描程序(包含按键消抖),模块名称为col_scan,文件名称为col_scan.v,代码如下。

module col_scan(
    input               clk,                //系统时钟
    input               rst_n,              //复位按键
    input [3:0]         key_col,            //4个列按键输入
    output [3:0]        col_value           //列按键按下的键值
);
//以下定义4个按键相与
wire    key = key_col[0] & key_col[1] & key_col[2] & key_col[3];         
reg[3:0]    keyr;                           //定义按键存储变量
always@(posedge clk or negedge rst_n)       //敏感信号为时钟上沿或复位下沿
begin
    if(!rst_n)                              //低电平复位
        keyr <= 4'b1111;                    //复位时按键值为全1
    else
      keyr <= {keyr[2:0], key};             //并位,相当于每个时钟之后用key值向左填充keyr
end
wire    key_neg = ~keyr[2] & keyr[3];       //按键按下之后判定有下降沿
wire    key_pos = keyr[2] & ~keyr[3];       //按键释放之后判定有上升沿
//定时计数20ms时间,用于对按键的消抖判断
reg[19:0]    cnt;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)                              //低电平复位时计数值清零
      cnt <= 20'd0;
    else if(key_pos || key_neg)             //如果有上升沿或下降沿发生,计数值清零
        cnt <= 20'd0;
    else if(cnt < 20'd999_999)              //如果未计到20ms,则继续加1计数
        cnt <= cnt + 1'b1;
    else
        cnt <= 20'd0;                       //到20ms,计数值清零        
end
//定时采集按键值
reg[3:0]    key_colvalue[1:0];              //定义两个健值存储变量
always@(posedge clk or negedge rst_n)       //敏感信号为时钟上沿或复位下沿
begin
    if(!rst_n)                              //低电平复位时健值变量全部置1
    begin
        key_colvalue[0] <= 4'b1111;
        key_colvalue[1] <= 4'b1111;
    end
    else
    begin
        key_colvalue[1] <= key_colvalue[0]; //两次键值相差一个时钟节拍,用于在不相同时产生一个变化脉冲
        if(cnt == 20'd999_999)
            key_colvalue[0] <= key_col;     //到20ms后,获取外部按键值
        else
            ;
    end
end
//按键值按下时产生一个变化脉冲
assign    col_value = key_colvalue[1] & ~key_colvalue[0];
endmodule

接下来编写矩阵键盘中的行扫描程序(使用了状态机),模块名称为row_scan,文件名称为row_scan.v,代码如下。

module row_scan(
    input               clk,                           //系统时钟
    input               rst_n,                         //复位按键
    input[3:0]          key_col,                       //4个列按键输入
    output reg[3:0]     key_row,                       //4个行按键输出
    output reg[3:0]     key_value,                     //新采样键值
    output reg          key_pressed                    //按键有效
);

wire[3:0]    col_value;                                //列按键按下键值
//例化按键消抖
col_scan u1(
    .clk(clk),
    .rst_n(rst_n),
    .key_col(key_col),
    .col_value(col_value)
);
//定义状态值
reg[2:0]    nstate,cstate;
parameter    K_IDLE = 4'd0;
parameter    K_H1OL = 4'd1;
parameter    K_H2OL = 4'd2;
parameter    K_H3OL = 4'd3;
parameter    K_H4OL = 4'd4;
parameter    K_CHCK = 4'd5;
//状态切换
always@(posedge clk or negedge rst_n)                  //敏感信号为时钟上沿或复位下沿
begin
    if(!rst_n)                                         //低电平复位,复位时现态为空闲
        cstate <= K_IDLE;
        else                                           //否则现态等于下一个状态
        cstate <= nstate;
end
//以下为状态机翻转
always@(cstate or col_value or key_col)                //敏感信号为当前状态变化或列键值变化或列按键变化(组合逻辑)
begin
    case(cstate)
        K_IDLE:
            if(col_value != 4'b0000)                   //如果按键有效,则进入下一状态,否则在空闲状态循环
                nstate <= K_H1OL;
            else
                nstate <= K_IDLE;
        K_H1OL:                                        //如果能到这里,说明有按键按下且已经过消抖判断(按键有效),在本状态下会在第1行输出低电平
            nstate <= K_H2OL;
        K_H2OL:                                        //如果第1行没有检测到按键按下,则进入下一状态继续体测,否则返回空闲状态等待下一次按键事件
            if(key_col == 4'b1111)
                nstate <= K_H3OL;
            else
                nstate <= K_IDLE;
        K_H3OL:                                        //如果第2行没有检测到按键按下,则进入下一状态继续体测,否则返回空闲状态等待下一次按键事件
            if(key_col == 4'b1111)
                nstate <= K_H4OL;
            else
                nstate <= K_IDLE;
        K_H4OL:                                        //如果第3行没有检测到按键按下,则进入下一状态继续体测,否则返回空闲状态等待下一次按键事件
            if(key_col == 4'b1111)
                nstate <= K_CHCK;
            else
                nstate <= K_IDLE;
        K_CHCK:                                        //如果第4行没有检测到按键按下,则返回到空闲状态等待下一次按键事件
            nstate <= K_IDLE;
        default:
            cstate <= K_IDLE;
        endcase        
end
//采样键值
always@(posedge clk or negedge rst_n)                  //敏感信号为时钟上沿或复位下沿
begin
    if(!rst_n)                                         //低电平复位
        begin
            key_row <= 4'b0000;                        //复位后4行全部输出低电平,用于检测是否有键按下
            key_value <= 4'd0;                         //复位后新采样键值为0
            key_pressed <= 1'b0;                       //复位后按键有效状态为无效(值为0)
        end
    else
        begin
        case(cstate)                                   //根据当前状态进行散转
            K_IDLE:                                    //空闲状态时,4行全部输出低电平,新采样键值为0,按键有效状态为无效
                begin
                    key_row <= 4'b0000;
                    key_value <= 4'd0;
                    key_pressed <= 1'b0;
                end
            K_H1OL:                                    //H1OL状态时,仅第1行输出低电平,新采样键值为0,按键有效状态为无效
                begin
                    key_row <= 4'b1110;
                    key_value <= 4'd0;
                    key_pressed <= 1'b0;
                end
            K_H2OL:                                     //H2OL状态时
                begin
                    case(key_col)                       //根据列电平散转
                    4'b1110:                            //第1行第1列按键,新采样键值编码为0,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd0;
                            key_pressed <= 1'b1;
                        end
                        4'b1101:                        //第1行第2列按键,新采样键值编码为1,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd1;
                            key_pressed <= 1'b1;
                        end
                        4'b1011:                        //第1行第3列按键,新采样键值编码为2,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd2;
                            key_pressed <= 1'b1;
                        end
                        4'b0111:                        //第1行第4列按键,新采样键值编码为3,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd3;
                            key_pressed <= 1'b1;
                        end
                        default:                        //本行4列都没按键按下,新采样键值为0,按键有效状态为无效,4行输出下一个状态的值,状态机切换到下一个状态
                        begin
                            key_row <= 4'b1101;
                            key_value <= 4'd0;
                            key_pressed <= 1'b0;
                        end
                    endcase
                end
            K_H3OL:                                     //H3OL状态时
                begin
                    case(key_col)                       //根据列电平散转
                        4'b1110:                        //第2行第1列按键,新采样键值编码为4,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd4;
                            key_pressed <= 1'b1;
                        end
                        4'b1101:                        //第2行第2列按键,新采样键值编码为5,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd5;
                            key_pressed <= 1'b1;
                        end
                        4'b1011:                        //第2行第3列按键,新采样键值编码为6,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd6;
                            key_pressed <= 1'b1;
                        end
                        4'b0111:                        //第2行第4列按键,新采样键值编码为7,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd7;
                            key_pressed <= 1'b1;
                        end
                        default:                        //本行4列都没按键按下,新采样键值为0,按键有效状态为无效,4行输出下一个状态的值,状态机切换到下一个状态
                        begin
                            key_row <= 4'b1011;
                            key_value <= 4'd0;
                            key_pressed <= 1'b0;
                        end
                    endcase
                end
            K_H4OL:                                     //H4OL状态时
                begin
                    case(key_col)                       //根据当前状态进行散转
                        4'b1110:                        //第3行第1列按键,新采样键值编码为8,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd8;
                            key_pressed <= 1'b1;
                        end
                        4'b1101:                        //第3行第2列按键,新采样键值编码为9,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd9;
                            key_pressed <= 1'b1;
                        end
                        4'b1011:                        //第3行第3列按键,新采样键值编码为10,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd10;
                            key_pressed <= 1'b1;
                        end
                        4'b0111:                        //第3行第4列按键,新采样键值编码为11,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd11;
                            key_pressed <= 1'b1;
                        end
                        default:                        //本行4列都没按键按下,新采样键值为0,按键有效状态为无效,4行输出下一个状态的值,状态机切换到下一个状态
                        begin
                            key_row <= 4'b0111;
                            key_value <= 4'd0;
                            key_pressed <= 1'b0;
                        end
                    endcase
                end
            K_CHCK:                                     //CHCK状态时
                begin
                    case(key_col)                       //根据当前状态进行散转
                        4'b1110:                        //第4行第1列按键,新采样键值编码为12,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd12;
                            key_pressed <= 1'b1;
                        end
                        4'b1101:                        //第4行第2列按键,新采样键值编码为13,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd13;
                            key_pressed <= 1'b1;
                        end
                        4'b1011:                        //第4行第3列按键,新采样键值编码为14,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd14;
                            key_pressed <= 1'b1;
                        end
                        4'b0111:                        //第4行第4列按键,新采样键值编码为15,按键有效状态为有效,之后4行全部输出低电平(同时上面的状态也恢复到空闲),等待下一次按键事件
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd15;
                            key_pressed <= 1'b1;
                        end
                        default:                        //本行4列都没按键按下,新采样键值为0,按键有效状态为无效,4行全部输出低电平,状态机切换到空闲状态
                        begin
                            key_row <= 4'b0000;
                            key_value <= 4'd0;
                            key_pressed <= 1'b0;
                        end
                    endcase
                end
            default:                                    //一般情况到不了这里,因为上面的状态机没有其他状态
                ;        
        endcase
        end
end
endmodule

然后编写数码管扫描程序,模块名称为seg_scan,文件名称为seg_scan.v,代码如下。

module seg_scan(
    input                clk,                   //系统时钟
    input                rst_n,                 //复位按键
    input[4:0]           count_data0,           //个位显示的数值
    input[4:0]           count_data1,           //十位显示的数值
    input[4:0]           count_data2,           //百位显示的数值
    input[4:0]           count_data3,           //千位显示的数值
    input[4:0]           count_data4,           //万位显示的数值
    input[4:0]           count_data5,           //十万位显示的数值
    output reg[7:0]      seg7,                  //段码端口
    output reg[5:0]      bit                    //位选端口
);
//下面定义6个数码管的字形码存储变量
wire [7:0] seg_0,seg_1,seg_2,seg_3,seg_4,seg_5;
//下面例化秒的个位字形解码单元
seg_decode seg0(.data(count_data0), .seg7(seg_0));
//下面例化秒的十位字形解码单元
seg_decode seg1(.data(count_data1), .seg7(seg_1));
//下面例化分的个位字形解码单元
seg_decode seg2(.data(count_data2), .seg7(seg_2));
//下面例化分的十位字形解码单元
seg_decode seg3(.data(count_data3), .seg7(seg_3));
//下面例化时的个位字形解码单元
seg_decode seg4(.data(count_data4), .seg7(seg_4));
//下面例化时的十位字形解码单元
seg_decode seg5(.data(count_data5), .seg7(seg_5));
reg[17:0]    time_cnt;                          //定义20位时钟计数器
reg[3:0]     scan_sel;                          //定义扫描位置计数器
//3.3毫秒循环计数
always@(posedge clk or negedge rst_n)           //敏感信号为时钟上沿或复位下沿
begin
    if(rst_n == 1'b0)                           //低电平复位时计数器全部清零
    begin
        time_cnt <= 18'd0;
        scan_sel <= 4'd0;
    end
    else if(time_cnt >= 18'd166_666)            //时钟计数器到达3.3毫秒时
    begin
        time_cnt <= 18'd0;                      //时钟计数器清零
        if(scan_sel == 4'd5)                    //如果扫描位置计数器已经到1则恢复0
            scan_sel <= 4'd0;
        else
            scan_sel <= scan_sel + 4'd1;        //否则扫描位置计数器加1,即每3.3ms加一次
    end
    else
        begin
            time_cnt <= time_cnt + 18'd1;       //否则时钟计数器加1,即来一次时钟脉冲加一次
        end
end
//数码管扫描显示
always@(posedge clk or negedge rst_n)           //敏感信号为时钟上沿或复位下沿
begin
    if(!rst_n)                                  //低电平复位时数码管全灭
    begin
        bit <= 6'b111111;
        seg7 <= 8'hff;
    end
    else 
        case(scan_sel)
            4'd0:                               //数码管0显示个位
            begin
                bit <= 6'b111110;
                seg7 <= seg_0;
            end
            4'd1:                               //数码管1显示十位
            begin
                bit <= 6'b111101;
                seg7 <= seg_1;
            end
            4'd2:                               //数码管2显示百位
            begin
                bit <= 6'b111011;
                seg7 <= seg_2;
            end
            4'd3:                               //数码管3显示千位
            begin
                bit <= 6'b110111;
                seg7 <= seg_3;
            end
            4'd4:                               //数码管4显示万位
            begin
                bit <= 6'b101111;
                seg7 <= seg_4;
            end
            4'd5:                               //数码管5显示十万位
            begin
                bit <= 6'b011111;
                seg7 <= seg_5;
            end
            default:                            //数码管全部熄灭
            begin
                bit <= 6'b111111;
                seg7 <= 8'hff;
            end
        endcase
end
endmodule

最后编写显示模块,并设置为顶层模块,模块名称为key_show,文件名称为key_show.v,代码如下。

module key_show(
    input               clk,                    //板载50HMz系统时钟
    input               rst_n,                  //复位按键
    input[3:0]          key_col,                //4个列按键输入
    output[3:0]         key_row,                //4个行按键输出
    output[7:0]         seg7,                   //段码端口
    output[5:0]         bit                     //位选端口
);
wire[3:0]    key_value;                         //定义键值存储变量
wire    key_pressed;                            //定义按键按下有效变量
//例化矩阵键盘扫描单元
row_scan u1(
    .clk(clk),
    .rst_n(rst_n),
    .key_col(key_col),
    .key_row(key_row),
    .key_value(key_value),
    .key_pressed(key_pressed)
);
reg[4:0] count_data0;                           //定义个位数值存储变量
reg[4:0] count_data1;                           //定义十位数值存储变量
reg[4:0] count_data2;                           //定义百位数值存储变量
reg[4:0] count_data3;                           //定义千位数值存储变量
reg[4:0] count_data4;                           //定义万位数值存储变量
reg[4:0] count_data5;                           //定义十万位数值存储变量
//例化数码管动态扫描单元
seg_scan u2(
    .clk(clk),
    .rst_n(rst_n),
    .count_data0(count_data0),
    .count_data1(count_data1),
    .count_data2(count_data2),
    .count_data3(count_data3),
    .count_data4(count_data4),
    .count_data5(count_data5),
    .seg7(seg7),
    .bit(bit)
);
//显示键值并向左移位
always@(posedge clk or negedge rst_n)           //敏感信号为时钟上沿或复位下沿
begin
   if(!rst_n)                                   //低电平复位,复位时数值存储变量全部置1
    begin
      count_data0 <= 5'b11111;
      count_data1 <= 5'b11111;
      count_data2 <= 5'b11111;
      count_data3 <= 5'b11111;
      count_data4 <= 5'b11111;
      count_data5 <= 5'b11111;  
   end        
   else
    begin
     if(key_pressed)                            //如果按键按下有效
     begin
         case(key_value)                        //判断键值并散转
              5'd0:                             //0号键值,显示左移一位,最低位显示0
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd0;
              end
              5'd1:                             //1号键值,显示左移一位,最低位显示1
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd1;
              end
              5'd2:                             //2号键值,显示左移一位,最低位显示2
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd2;
              end
              5'd3:                             //3号键值,显示左移一位,最低位显示3
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd3;
              end
              5'd4:                             //4号键值,显示左移一位,最低位显示4
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd4;
              end
              5'd5:                             //5号键值,显示左移一位,最低位显示5
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd5;
              end
              5'd6:                             //6号键值,显示左移一位,最低位显示6
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd6;
              end
              5'd7:                             //7号键值,显示左移一位,最低位显示7
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd7;
              end
              5'd8:                             //8号键值,显示左移一位,最低位显示8
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd8;
              end
              5'd9:                             //9号键值,显示左移一位,最低位显示9
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd9;
              end
              5'd10:                            //10号键值,显示左移一位,最低位显示A
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd10;
              end
              5'd11:                            //11号键值,显示左移一位,最低位显示b
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd11;
              end
              5'd12:                            //12号键值,显示左移一位,最低位显示C
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd12;
              end
              5'd13:                            //13号键值,显示左移一位,最低位显示d
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd13;
              end
              5'd14:                            //14号键值,显示左移一位,最低位显示E
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd14;
              end
              5'd15:                            //15号键值,显示左移一位,最低位显示F
              begin
                    count_data5 <= count_data4;
                    count_data4 <= count_data3;
                    count_data3 <= count_data2;
                    count_data2 <= count_data1;
                    count_data1 <= count_data0;
                    count_data0 <= 5'd15;
              end
              default:                          //没有键值匹配,保持原有状态
                    ;
         endcase
    end
    else                                        //未检测到有效按键时,保持原有状态
        ;
  end
end
endmodule

三、代码说明

1、本例主要讨论矩阵键盘的设计方法,数码管的扫描及字形解码可参看“基于EP4CE6F17C8的FPGA数码管动态显示实例”一文。
2、本例中使用的矩阵键盘为4X4型,共16个按键,行、列各引出4个引脚,共8个引脚。对矩阵键盘的扫描分为两个部分,先进行列扫描,再进行行扫描,最后输出一个键值信号和一个按键有效信号。
3、在列扫描中,先把行全部输出低电平,读取列看是否有键按下,若有则进行按键消抖操作(具体原理可参见“基于EP4CE6F17C8的FPGA键控灯实例”一文),最终输出一个列的键值col_value,它共有4位,高电平的位表明对应列有键被按下。注意,当col_value的值不为全0时,不仅表明有列键值输出,而且还经过了按键消抖判断,说明是有效的按键。同时也说明,此时可以通过扫描来获取有效按键值了。
4、在行扫描中,使用了状态机,一共定义了6个状态。K_IDLE为空闲状态,在此状态下行全部输出0,以提供列扫描是否有键按下,或有键按下,则切换到K_H1OL状态;K_H1OL状态让第1行输出低电平,其余行为高电平,然后切换到K_H2OL状态;K_H2OL状态进行第1行扫描,判断按下的键是否在该行,若在该行则返回键值,状态回到空闲状态,若没在则让第2行输出低电平,其余行为高电平,然后状态则切换到K_H3OL状态;K_H3OL状态进行第2行扫描,判断按下的键是否在该行,若在该行则返回键值,状态回到空闲状态,若没在则让第3行输出低电平,其余行为高电平,然后状态则切换到K_H4OL状态;K_H4OL状态进行第3行扫描,判断按下的键是否在该行,若在该行则返回键值,状态回到空闲状态,若没在则让第4行输出低电平,其余行为高电平,然后状态则切换到K_CHCK状态;K_CHCK状态进行第4行扫描,判断按下的键是否在该行,若在该行则返回键值,状态回到空闲状态,若没在也切换到K_IDLE状态。当有值键返回时,把按键有效位key_pressed置1,否则置0。状态的转换在时钟的同步下进行。
5、对矩阵键盘的扫描,先在空闲状态下判断是否有键按下,若有则进行按键消抖,之后再进入其他状态进行扫描,得到按下的键值。扫描完后对16个按键值进行编码,键值从0编到15。同时配套一个按键有效信号,该信号只存在一个时钟周期,这样可以有效消除按一次键形成多次动作的缺陷。
6、显示部分采用了向左移位的方式,即当前按下的键值只显示在最右边一位数码管上,其余的往左移一位。

四、实验步骤

FPGA开发的详细步骤请参见“基于EP4CE6F17C8的FPGA开发流程(以半加器为例)”一文,本例只对不同之处进行说明。

本例工程放在D:\EDA_FPGA\Exam_9文件夹下,工程名称为Exam_9。模块文件名称为key_show.v,并设置为顶层实体。其余步骤与“基于EP4CE6F17C8的FPGA开发流程”中的一样。

接下来看管脚约束,本例中6个数码管一共有14个引脚,矩阵键盘8个引脚,再加上时钟晶振和复位按钮,一共24个。具体的端口分配如下图所示。

对于未用到的引脚设置为三态输入方式,多用用途引脚全部做为普通I/O端口,电压设置为3.3-V LVTTL(与”基于EP4CE6F17C8的FPGA开发流程“中的一样)。需要注意,程序中的每个端口都必须为其分配管脚,如果系统中存在未分配的I/O,软件可能会进行随机分配,这将造成不可预料的后果,存在烧坏FPGA芯片的风险。

接下来对工程进行编译,编译完成后,可查看一下逻辑器件的消耗情况,如下图所示。

另外,还可以点击菜单Tools->Netlist Viewers->RTL Viewer,查看一下生成的RTL电路图。

最后进行程序下载,并查看结果。下图为初始状态,即没有键按下时的情况。

下图为按下矩阵键盘上的key1键,显示键值0。

下图为按下矩阵键盘上的key2键,显示键值1。

下图为按下矩阵键盘上的key3键,显示键值2。

下图为按下矩阵键盘上的key10键,显示键值9。

下图为按下矩阵键盘上的key13键,显示键值C。

下图为按下矩阵键盘上的key15键,显示键值E。

下图为按下开发板上的reset键,显示全部熄灭。

posted @ 2024-04-07 10:24  fxzq  阅读(257)  评论(0编辑  收藏  举报