基于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键,显示全部熄灭。