差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
book_excise_keyscan_7seg [2021/08/18 16:55] zili |
book_excise_keyscan_7seg [2021/08/18 17:22] (当前版本) zili |
||
---|---|---|---|
行 23: | 行 23: | ||
</WRAP> | </WRAP> | ||
- | #### 2.1 八段数码管显示原理 | + | #### 2.2 八段数码管显示原理 |
{{ :图10-3.jpg |图10-3 数码管连接}} | {{ :图10-3.jpg |图10-3 数码管连接}} | ||
<WRAP centeralign> | <WRAP centeralign> | ||
行 39: | 行 39: | ||
### 3. 程序设计 | ### 3. 程序设计 | ||
+ | #### 3.1 总体架构 | ||
+ | 整个程序由5个模块组成(图10-4):\\ | ||
+ | 1. 顶层模块Key_scan将各子模块实例化并将各接口连接起来;\\ | ||
+ | 2. scan_clk模块产生扫描键盘所要求的周期为4ms的脉冲信号;\\ | ||
+ | 3. scan模块负责键盘扫描,识别按键;\\ | ||
+ | 4. data_2_disp模块负责输入键值的译码,因为要分别对行值和列值进行译码,因此用两个data_2_disp模块,一个是行地址显示的译码disp1,一个是列地址显示的译码disp2;\\ | ||
+ | 5. disp_scan实现两个数码管的扫描显示。\\ | ||
+ | {{ :图10-4.png |图10-4 程序总体架构}} | ||
+ | <WRAP centeralign> | ||
+ | **图10-4 程序总体架构** | ||
+ | </WRAP> | ||
+ | #### 3.2 key_scan模块(key_scan.v) | ||
+ | Key_san是顶层模块,实例化各模块。scan_clk输出的时钟scan_clk送给scan模块,作为控制键盘扫描周期的时钟;scan模块输出的行地址r_reg和列地址c_reg作为data_2_disp模块的输入,经译码输出Hex[1:0]。 disp_scan扫描显示HEX[0:1]的值,分别表示行值和列值。 | ||
+ | Key_scan输入输出接口如下: | ||
- | \\ | + | <code verilog> |
- | \\ | + | input clk,reset; //50MHz系统时钟。 |
- | ### 4. 仿真结果 | + | input [3:0]kr; //行线输出接口。 |
+ | output [3:0]kc; //列线输入接口。 | ||
+ | output [7:0]HEX_sel; //数码管选择输出,为高表示相应数码管被选中。 | ||
+ | output [7:0]HEX_seg; //数码管的显示值,8段数码管由8为控制,为高表示显示 | ||
+ | </code> | ||
+ | #### 3.3 scan_clk模块(scan_clk.v) | ||
+ | scan_clk模板生成周期为4ms的脉冲信号作为键盘扫描时钟,周期为1us的脉冲信号作为Signaltap逻辑分析仪参考时钟。 | ||
+ | <code verilog> | ||
+ | scan_clk输入输出接口如下: | ||
+ | <code verilog> | ||
+ | input clk,reset; //50MHz时钟和复位。 | ||
+ | output scan_clk; //周期是4ms的键盘扫描时钟 | ||
+ | output sig_clk; //为signaltap逻辑分析仪时钟 | ||
+ | </code> | ||
+ | |||
+ | 键盘扫锚时钟scan_clk周期为4ms,脉宽为一个时钟周期(图10-5)。 | ||
+ | {{ :图10-5.png |图10-5 扫描时钟波形}} | ||
+ | <WRAP centeralign> | ||
+ | **图10-5 扫描时钟波形** | ||
+ | </WRAP> | ||
+ | |||
+ | #### 3.4 scan模块(scan.v) | ||
+ | scan模块扫描键盘,获取按下的键的行值和列值。 | ||
+ | |||
+ | Scan模块输入输出接口如下: | ||
+ | <code verilog> | ||
+ | input clk,reset; //50MHz时钟和复位。 | ||
+ | input [3:0]kr; //行线输出接口 | ||
+ | input scan_clk; //周期是4ms的键盘扫描时钟 | ||
+ | output [3:0]kc; //列线输出接口 | ||
+ | output [3:0]add_r; //有键按下时行线的地址 | ||
+ | output [3:0]add_c; //有键按下时列线的地址 | ||
+ | output keyout_en //按键值输出使能 | ||
+ | |||
+ | </code> | ||
+ | |||
+ | kr、kc和scan_clk用于实现对键盘按键的扫描;add_r和add_c输出按键的行值和列值;keyout_en是按键输出使能,当keyout_en为高时,表示add_r和add_c有效,keyout_en宽度为一个时钟周期,如图10-6所示。 | ||
+ | {{ :图10-6.png |图10-6 Scan模块行列值输出接口时序}} | ||
+ | <WRAP centeralign> | ||
+ | **图10-6 Scan模块行列值输出接口时序** | ||
+ | </WRAP> | ||
+ | Scan的主状态机如图10 7所示。 | ||
+ | {{ :图10-7.png |图10-7 Scan模块的状态机设计}} | ||
+ | <WRAP centeralign> | ||
+ | **图10-7 Scan模块的状态机设计** | ||
+ | </WRAP> | ||
+ | 主状态机State有四个状态:Waiting、Anti-jitter、scanning和Ending状态。waiting状态表示无按键被按下,等待按键;anti-jitter状态表示已经检测到按键被按下,但是处于消抖状态;scanning状态指在消抖工作完成后,扫描键盘列线,获取按键值;Ending 状态指在扫描结束后,延时200ms后再进入waiting状态,这样保证每隔200ms扫描一次键盘,防止两次扫描时间过短。 | ||
+ | <code verilog> | ||
+ | |||
+ | Waiting: // Waiting状态,等待按键按下 | ||
+ | begin | ||
+ | add_r <= 4'b1111 | ||
+ | add_c <= 4'b1111; | ||
+ | kc <= 4'b1111; //每行列线上都输出高电平 | ||
+ | delay <= 1'b0; | ||
+ | keyout_en <= 1'b0; | ||
+ | |||
+ | if(kr > 4'b0000) begin //如果行线中有键按下 | ||
+ | state <= anti_jitter; //下个周期进入消抖状态 | ||
+ | kr_last <= kr; | ||
+ | keyout_en <= 1'b0; | ||
+ | end else | ||
+ | state <= waiting; | ||
+ | end | ||
+ | |||
+ | anti_jitter: //anti_jitter状态:进入消抖状态。 | ||
+ | begin | ||
+ | add_r <= 4'b1111; | ||
+ | add_c <= 4'b1111; | ||
+ | kr_last <= kr; | ||
+ | kc <= 4'b1111; | ||
+ | if(delay == 3'b111) begin | ||
+ | state <= scaning; //如果同一行线的为高电平时间超过八个周期则进入scanning状态 | ||
+ | delay <= 1'b0; | ||
+ | kc <= 4'b1000;//从最左边的列线开始扫描 | ||
+ | end | ||
+ | else begin | ||
+ | if(kr_last == kr) | ||
+ | delay <= delay + 1'b1; //本次行线电平和上次一样则delay+1 | ||
+ | else if(kr == 4'b0000) | ||
+ | state <= waiting;//如果是0,则进入waiting状态 | ||
+ | else | ||
+ | delay <= 1'b0; //如果Kr输入不为0,则重新进行消抖过程。 | ||
+ | end | ||
+ | end | ||
+ | |||
+ | scaning: //scaning状态,对列线进行扫描 | ||
+ | begin | ||
+ | kc <= {kc[0],kc[3:1]}; //从最左边开始扫描列线 | ||
+ | hold <= 6'b0; | ||
+ | if(kc == 4'b0001 ) //扫描到最右边的列线 | ||
+ | state <= ending; | ||
+ | |||
+ | if((temp_r != 4'b0000)&&(temp_c != 4'b0000)) begin //扫描到键盘 | ||
+ | add_r <= temp_r; //行地址赋值 | ||
+ | add_c <= temp_c; //列地址赋值 | ||
+ | keyout_en <= 1'b1; //键盘扫描输出使能信号 | ||
+ | end | ||
+ | else | ||
+ | keyout_en <= 1'b0; | ||
+ | end | ||
+ | |||
+ | ending: ending状态,键盘扫描完成后延时200ms | ||
+ | begin | ||
+ | keyout_en <= 1'b0; | ||
+ | if(hold == 6'b11_0001) //时间间隔为 200ms | ||
+ | begin | ||
+ | state <= waiting; | ||
+ | hold <= 6'b0; | ||
+ | end | ||
+ | else | ||
+ | hold <= hold + 1'b1; | ||
+ | end | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 当扫描到键盘后需要对键盘行地址和列地址进行译码。以下是行地址译码的程序,列地址译码与此相同。 | ||
+ | <code verilog> | ||
+ | always @(kr) | ||
+ | begin | ||
+ | case(kr) | ||
+ | 4'b1000:temp_r = 4'b0100; //第四条行线地址 | ||
+ | 4'b0100:temp_r = 4'b0011; //第三条行线地址 | ||
+ | 4'b0010:temp_r = 4'b0010; //第二条行线 地址 | ||
+ | 4'b0001:temp_r = 4'b0001; //第一条行线地址 | ||
+ | default: temp_r = 4'b0000; | ||
+ | endcase | ||
+ | end | ||
+ | |||
+ | </code> | ||
+ | |||
+ | #### 3.5 data_2_disp模块(data_2_disp.v) | ||
+ | 由于scan模块输出的按键行值和列值都是4比特二进制数。但八段数码管的输入为八位,其中七位输入用于显示一位阿拉伯数字,每一位控制数码管的一段,另一位用于显示小数点。因此需要将输入的4bits数据译码为8bits二进制数据,控制八段数码管各段的显示。图10-8为八段数码管显示示意图。 | ||
+ | {{ :图10-8.png |图10-8 八段数码管示意图}} | ||
+ | <WRAP centeralign> | ||
+ | **图10-8 八段数码管示意图** | ||
+ | </WRAP> | ||
+ | 在data_2_disp定义了一个8位寄存器segs[7:0]。寄存器每一位分别对应着数码管的{H,G,F,E,D,C,B,A},如果某一位寄存器输出为低电平时,对应的那一段数码管亮起。\\ | ||
+ | data_2_disp的输入输出接口为: | ||
+ | <code verilog> | ||
+ | input [3:0] data; //输入位宽为4的二进制数 | ||
+ | output [7:0] segs; //译码输出,变为8bit二进制数 | ||
+ | |||
+ | </code> | ||
+ | |||
+ | 程序说明: | ||
+ | <code verilog> | ||
+ | always @(data) | ||
+ | begin | ||
+ | case(data) | ||
+ | 4'h0: segs = 8'b11000000; //输入为0时,ABCDEF段亮起 | ||
+ | 4'h1: segs = 8'b11111001; //输入为1时,BC段亮起 | ||
+ | 4'h2: segs = 8'b10100100; //输入为2时,ABEFG段亮起 | ||
+ | 4'h3: segs = 8'b10110000; //输入为3时,ABCDG段亮起 | ||
+ | 4'h4: segs = 8'b10011001; //输入为4时,BCFG段亮起 | ||
+ | 4'h5: segs = 8'b10010010; //输入为5时,ACDFG段亮起 | ||
+ | 4'h6: segs = 8'b10000010; //输入为6时,ACDEFG段亮起 | ||
+ | 4'h7: segs = 8'b11111000; //输入为7时,ABC段亮起 | ||
+ | 4'h8: segs = 8'b10000000; //输入为8时,ABCDEFG段亮起 | ||
+ | 4'h9: segs = 8'b10010000; //输入为9时,ABCDFG段亮起 | ||
+ | default: segs = 8'b11111111; | ||
+ | endcase | ||
+ | end | ||
+ | |||
+ | </code> | ||
+ | |||
+ | #### 3.6 disp_scan模块(disp_scan.v) | ||
+ | disp_scan对两个数码管进行扫描显示。用八位HEX_sel寄存器对两个数码管选通。这里只需要显示两位数码管,所以只用HEX_sel低两位进行选通即可。\\ | ||
+ | disp_scan端口说明: | ||
+ | <code verilog> | ||
+ | input clk,reset; //时钟和复位信号 | ||
+ | input scan_clk; //周期是4ms的键盘扫描时钟 | ||
+ | input [7:0] Hex1; //行值 | ||
+ | input [7:0] Hex0; //列值 | ||
+ | output [7:0] HEX_sel; //数码管选通信号 | ||
+ | output [7:0] HEX_seg; //数码管显示数字 | ||
+ | always @(posedge clk or negedge reset) | ||
+ | begin | ||
+ | if( !reset ) | ||
+ | HEX_sel <= 8'b1111_1101; | ||
+ | else | ||
+ | begin | ||
+ | count <= count+1'b1; | ||
+ | if(count == 18'b1) //每五毫秒循环一次 | ||
+ | HEX_sel <= {HEX_sel[7:2],HEX_sel[0],HEX_sel[1]}; | ||
+ | end | ||
+ | end | ||
+ | |||
+ | always @(HEX_sel) | ||
+ | begin | ||
+ | case(HEX_sel) | ||
+ | 8'b1111_1101: HEX_seg = Hex0; //当HEX_sel[1]为低时,显示行地址; | ||
+ | 8'b1111_1110: HEX_seg = Hex1; //当HEX_sel[0]为低时,显示列地址; | ||
+ | default: HEX_seg = 8'b1111_1111; | ||
+ | endcase | ||
+ | end | ||
+ | |||
+ | </code> | ||
\\ | \\ | ||
\\ | \\ | ||
+ | ### 4. 仿真结果 | ||
+ | {{ :图10-9.png |图10-9 仿真结果}} | ||
+ | <WRAP centeralign> | ||
+ | **图10-9 仿真结果** | ||
+ | </WRAP> | ||
+ | 本实验的 Testbench为test_key.v,主要用于对scan模块进行仿真。test_key.v的时钟与系统时钟一样为50MHz,由延时语句输入一系列kr的值,调用scan.v扫描程序观察相应的kc输出时各变量的状态。 | ||
- | ### 5. 演示程序文件说明 | + | 图10-9是键盘扫描的仿真波形,整个扫描过程由状态机来控制,系统复位的状态是wating=4’h1,在waiting状态下输出为4’b1111。如果检测到输入不全为低电平则转到去抖动状态anti_jitter=4’h2。此时继续检测输入端口,如果连续8次(delay的值)检测输入值都相同,则说明有键按下,进入下一个状态scaning=4’h4, 否则认为是毛刺。在scaning状态下kc依次输出4’b1000、4’b0100、4’b0010、4’b0001,然后根据输入kr的值来确定具体是哪一个键按下。在图中可以看到当add_r=4’h4 ,add_c=4’h2时,此时kr=4’h8,kc=4’h1确定第4行第1列的键——S18按下。扫描结束后进入ending=4’h8状态,经过4ms*8=32ms之后,又进入下一轮扫描过程。 |
+ | 在anti_jitter状态下修改参数delay的变化情况可以控制去抖动的时间;在ending状态下修改delay的变化范围可以改变连续两次扫描之间的时间间隔,这些值可以根据实际情况进行调整。 | ||
+ | \\ | ||
+ | \\ | ||
+ | ### 5. 运行结果 | ||
+ | 在signaltap里选择要观察的信号,如图10-10。其中将Keyout_en设置为上边沿触发,这样当键盘扫描减速,keyout_en拉高后,signaltap会暂停。在设定好观察的信号后,对程序进行编译。将程序下载到开发系统上,选择signaltap的Autorun Analysis。然后按下任意一个按键,如按下行地址为2,列地址为4的键。则会看到如下结果,add_c和add_r分别键盘显示列地址和行地址,在数码管上可以看到行值和列值的显示。 | ||
+ | {{ :图10-10.png |图10-10 程序运行结果}} | ||
+ | <WRAP centeralign> | ||
+ | **图10-10 程序运行结果** | ||
+ | </WRAP> | ||
\\ | \\ | ||
\\ | \\ | ||
+ | ### 6. 演示程序文件说明 | ||
+ | |文件名|功能| | ||
+ | |Keyscan.v|顶层模块。| | ||
+ | |scan_clk.v|产生扫描和逻辑分析仪所要求的时钟。| | ||
+ | |data_2_display.v|按键值译码。| | ||
+ | |scan.v|扫描键盘。| | ||
+ | |disp_scan.v|显示按键值。| | ||
+ | |Test_key.v|用于Modelsim仿真的测试程序,对scan.v进行仿真。| | ||
+ | |||
+ | ### 7. 演示程序使用 | ||
+ | 演示设备:核心板、扩展板。 | ||
+ | 演示方法:把程序下载到开发板上之后,按下键盘区任意一个键,可以观察到数码管L7和L8分别显示当前所按键的行值和列值。 | ||
+ | |||
- | ### 6. 演示程序使用 |