本文為明德揚原創及錄用文章,轉載請注明出處!
1.1 總體設計
1.1.1 概述
在鍵盤中按鍵數量較多時,為了減少I/O口的占用,通常將按鍵排列成矩陣形式。在矩陣式鍵盤中,每條水平線和垂直線在交叉處不直接連通,而是通過一個按鍵加以連接。這樣,一個端口(如P1口)就可以構成4*4=16個按鍵,比之直接將端口線用于鍵盤多出了一倍,而且線數越多,區別越明顯,比如再多加一條線就可以構成20鍵的鍵盤,而直接用端口線則只能多出一鍵(9鍵)。由此可見,在需要的鍵數比較多時,采用矩陣法來做鍵盤是合理的。
1.1.2 設計目標
完成矩陣鍵盤的掃描檢測程序,具體功能要求如下:
1. 運用逐行掃描的方法進行按鍵監測;
2. 每行掃描的時間不少于 20ms,濾除抖動;
3. 檢測到有按鍵按下之后,消抖時間 20ms;
4. 輸出信號 key_vld 持續一拍即可;
5. 輸出信號key_out表示16 個按鍵,并在數碼管上顯示對應數值;
1.1.3 系統結構框圖
系統結構框圖如下圖一所示:

圖一
1.1.4 模塊功能
? 矩陣鍵盤模塊實現功能
1、將外來異步信號打兩拍處理,將異步信號同步化。
2、實現20ms按鍵消抖功能。
3、實現矩陣鍵盤的按鍵檢測功能,并輸出有效按鍵信號。
? 數碼管顯示模塊實現功能
1、 對接收到的按鍵數據進行譯碼并顯示。
1.1.5 頂層信號

1.1.6 參考代碼
下面是使用工程的頂層代碼:
1. module top(
2. clk ,
3. rst_n ,
4. key_col ,
5. key_row ,
6. segment ,
7. seg_sel
8. );
9.
10. parameter DATA_W = 4;
11.
12. input clk ;
13. input rst_n ;
14. input [3:0] key_col;
15.
16. output [3:0] key_row;
17. output [7:0] segment;
18. output [1:0] seg_sel;
19.
20. wire [3:0] key_row;
21. wire [7:0] segment;
22. wire [1:0] seg_sel;
23.
24. wire [DATA_W-1:0] key_out ;
25. wire key_vld ;
26. wire [DATA_W-1:0] segment_data ;
27.
28. key_scan u1(
29. .clk ( clk ),
30. .rst_n ( rst_n ),
31. .key_col ( key_col ),
32. .key_row ( key_row ),
33. .key_out ( key_out ),
34. .key_vld ( key_vld )
35. );
36.
37. seg_disp u3(
38. .clk ( clk ),
39. .rst_n ( rst_n ),
40. .data_in ( key_out ),
41. .key_en ( key_vld ),
42. .segment ( segment ),
43. .seg_sel ( seg_sel )
44. );
45.
46. endmodule
1.2 矩陣鍵盤模塊設計
1.2.1 接口信號

1.2.2 設計思路
? 行掃描法原理
開發板上為 4*4 矩陣鍵盤:默認 4 條列線上來高電平,4 條行線默認接高電平。列線 KEY_C1 ~ KEY_C4分別接有4個上拉電阻到正電源 +3.3 V,并把列線 KEY_C1~KEY_C4設置為輸入線,行線 KEY_R1~KEY_R4設置為輸出線。4根行線和4根列線形成16個相交點。
如下圖所示:

確認矩陣鍵盤上哪個按鍵被按下有多同方法,其中行掃描法又稱為逐行(或列)掃描查詢法,是一種最常用的按鍵識別方法。
1. 判斷鍵盤中有無鍵按下:將全部行線 KEY_R1~KEY_R4 置低電平,然后檢測列線 KEY_C1~KEY_C4 的狀態。只要有一列的電平為低,則表示鍵盤中有鍵被按下,而且閉合的鍵位于低電平線與 4根行線相交叉的 4 個按鍵之中。若所有列線均為高電平,則鍵盤中無鍵按下。
2. 判斷閉合鍵所在的位置:在確認有鍵按下后,即可進入確定具體閉合鍵的過程。其方法是:依次將行線置為低電平,即在置某根行線為低電平時,其它線為高電平。在確定某根行線位置為低電平后,再逐行檢測各列線的電平狀態。若某列為低,則該列線與置為低電平的行線交叉處的按鍵就是閉合的按鍵。
? 打拍操作
輸入的key_col是異步信號,所以要進行打兩拍操作,將異步信號key_col同步化,并防止亞穩態。
設計代碼如下:
1. input [3:0] key_col ;
2.
3. reg [3:0] key_col_ff0 ;
4. reg [3:0] key_col_ff1 ;
5.
6. always @(posedge clk or negedge rst_n)begin
7. if(rst_n==1'b0)begin
8. key_col_ff0 <= 4'b1111;
9. key_col_ff1 <= 4'b1111;
10. end
11. else begin
12. key_col_ff0 <= key_col ;
13. key_col_ff1 <= key_col_ff0;
14. end
15. end
? 按鍵消抖
對于按鍵和觸摸屏等機械設備來說,都存在一個固有問題,那就是“抖動”,按鍵從最初接通到穩定接通要經過數毫秒,其間可能發生多次“接通-斷開”這樣的毛刺。如果不進行處理,會使系統識別到抖動信號而進行不必要的反應,導致模塊功能不正常,為了避免這種現象的產生,需要進行按鍵消抖的操作。
軟件方法消抖,即檢測出鍵閉合后執行一個延時程序,抖動時間的長短由按鍵的機械特性決定,一般為5ms~20ms,讓前沿抖動消失后再一次檢測鍵的狀態,如果仍保持閉合狀態電平,則確認按下按鍵操作有效。當檢測到按鍵釋放后,也要給5ms~20ms的延時,待后沿抖動消失后才能轉入該鍵的處理程序。

由于按鍵按下去的時間一般都會大于20ms,為了達到不管按鍵按下多久,都視為按下一次的效果,提出以下計數器架構,如下圖所示:

消抖計數器shake_cnt:用于計算20ms的時間,加一條件為key_col_ff1 != 4'hf || key_row_check==1,表示當某個按鍵按下或者進行行掃描時就開始計數;數到1,000,000下,表示數到20ms就結束。
行掃描計數器row_index:用于區分掃描的行,加一條件為key_row_check && end_shake_cnt,表示當處于行掃描狀態并且每行消抖20ms后,開始掃描下一行;數到4下,表示4行按鍵掃描完了。
按鍵:表示有無按鍵按下,沒被按下時為高電平,按下后為低電平。
行掃描指示信號key_row_check:該信號為高電平,指示當前處于行掃描狀態。
矩陣鍵盤列信號key_col_ff1:4bit位寬的矩陣鍵盤列信號,最高位表示矩陣鍵盤往右數第四列,默認信號為key_col_ff1 = 4'hf,否則表示該信號低電平對應位的列有按鍵按下。
1.2.3 參考代碼
16. module key_scan(
17. clk ,
18. rst_n ,
19. key_col,
20. key_row,
21. key_out,
22. key_vld
23. );
24.
25. parameter DATA_W = 4 ;
26. parameter TIME_20MS = 1_000_000 ;
27.
28. input clk ;
29. input rst_n ;
30. input [3:0] key_col ;
31.
32. output [3:0] key_row ;
33. output key_vld ;
34. output [DATA_W-1:0] key_out ;
35.
36. reg [3:0] key_row ;
37. reg key_vld ;
38. reg [DATA_W-1:0] key_out ;
39.
40. reg [3:0] key_col_ff0 ;
41. reg [3:0] key_col_ff1 ;
42. reg key_col_check ;
43. reg [21:0] shake_cnt ;
44. wire add_shake_cnt ;
45. wire end_shake_cnt ;
46. reg [1:0] key_col_get ;
47. reg key_row_check ;
48. reg [1:0] row_index ;
49. wire add_row_index ;
50. wire end_row_index ;
51.
52.
53. always @(posedge clk or negedge rst_n)begin
54. if(rst_n==1'b0)begin
55. key_col_ff0 <= 4'b1111;
56. key_col_ff1 <= 4'b1111;
57. end
58. else begin
59. key_col_ff0 <= key_col ;
60. key_col_ff1 <= key_col_ff0;
61. end
62. end
63.
64. always @(posedge clk or negedge rst_n)begin
65. if(rst_n==1'b0)begin
66. key_col_check <= 1'b0;
67. end
68. else if(key_col_ff1 !=4'hf && end_shake_cnt)begin
69. key_col_check <= 1'b1;
70. end
71. else if(key_col_ff1==4'hf)begin
72. key_col_check <= 1'b0;
73. end
74. end
75.
76.
77. always @(posedge clk or negedge rst_n) begin
78. if (rst_n==0) begin
79. shake_cnt <= 0;
80. end
81. else if(add_shake_cnt) begin
82. if(end_shake_cnt)
83. shake_cnt <= 0;
84. else
85. shake_cnt <= shake_cnt+1 ;
86. end
87. end
88. assign add_shake_cnt = key_col_ff1 !=4'hf || key_row_check==1;
89. assign end_shake_cnt = add_shake_cnt && shake_cnt == TIME_20MS-1 ;
90.
91.
92. always @(posedge clk or negedge rst_n)begin
93. if(rst_n==1'b0)begin
94. key_col_get <= 0;
95. end
96. else if(key_col_check) begin
97. if(key_col_ff1==4'b1110)
98. key_col_get <= 0;
99. else if(key_col_ff1==4'b1101)
100. key_col_get <= 1;
101. else if(key_col_ff1==4'b1011)
102. key_col_get <= 2;
103. else if(key_col_ff1==4'b0111)
104. key_col_get <= 3;
105. end
106. end
107.
108. always @(posedge clk or negedge rst_n)begin
109. if(rst_n==1'b0)begin
110. key_row_check <= 0;
111. end
112. else if(key_col_check)begin
113. key_row_check <= 1;
114. end
115. else if(key_vld)begin
116. key_row_check <= 0;
117. end
118. end
119.
120.
121. always @(posedge clk or negedge rst_n) begin
122. if (rst_n==0) begin
123. row_index <= 0;
124. end
125. else if(add_row_index) begin
126. if(end_row_index)
127. row_index <= 0;
128. else
129. row_index <= row_index+1 ;
130. end
131. end
132. assign add_row_index = key_row_check && end_shake_cnt;
133. assign end_row_index = add_row_index && row_index == 4-1 ;
134.
135.
136. always @(posedge clk or negedge rst_n)begin
137. if(rst_n==1'b0)begin
138. key_row = 4'b0;
139. end
140. else if(key_row_check)begin
141. key_row = ~(4'b0001 << row_index);
142. end
143. else begin
144. key_row = 4'b0;
145. end
146. end
147.
148.
149. always @(posedge clk or negedge rst_n)begin
150. if(rst_n==1'b0)begin
151. key_vld <= 1'b0;
152. end
153. else if(key_row_check && key_col_ff1[key_col_get]==1'b0 && key_col_check==0 )begin
154. key_vld <= 1'b1;
155. end
156. else begin
157. key_vld <= 1'b0;
158. end
159. end
160.
161.
162. always @(posedge clk or negedge rst_n)begin
163. if(rst_n==1'b0)begin
164. key_out <= 4'd0;
165. end
166. else if(key_vld)begin
167. key_out <= {row_index,key_col_get};
168. end
169. else begin
170. key_out <= 4'd0;
171. end
172. end
173.
174. endmodule
1.3 數碼管顯示模塊設計
1.3.1 接口信號

1.3.2 設計思路
在前面的案例中已經有數碼管顯示的介紹,所以這里不在過多介紹,詳細介紹請看下方鏈接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=399
1.3.3 參考代碼
1. module seg_disp(
2. clk ,
3. rst_n ,
4. data_in ,
5. key_en ,
6. segment ,
7. seg_sel
8. );
9.
10. parameter ZERO = 8'b0000_0011 ;
11. parameter ONE = 8'b1001_1111 ;
12. parameter TWO = 8'b0010_0101 ;
13. parameter THREE = 8'b0000_1101 ;
14. parameter FOUR = 8'b1001_1001 ;
15. parameter FIVE = 8'b0100_1001 ;
16. parameter SIX = 8'b0100_0001 ;
17. parameter SEVEN = 8'b0001_1111 ;
18. parameter EIGHT = 8'b0000_0001 ;
19. parameter NINE = 8'b0000_1001 ;
20.
21. input clk ;
22. input rst_n ;
23. input [3:0] data_in ;
24. input key_en ;
25. output [7:0 ] segment ;
26. output [1:0 ] seg_sel ;
27.
28. reg [7:0 ] segment ;
29. reg [1:0 ] seg_sel ;
30. reg [10:0] delay ;
31. reg [1:0 ] delay_time ;
32. wire add_delay_time ;
33. wire end_delay_time ;
34. wire add_delay ;
35. wire end_delay ;
36. reg [4:0 ] segment_tmp ;
37. reg [3:0 ] segment_data ;
38.
39.
40. always @(posedge clk or negedge rst_n) begin
41. if (rst_n==0) begin
42. delay <= 0;
43. end
44. else if(add_delay) begin
45. if(end_delay)
46. delay <= 0;
47. else
48. delay <= delay+1 ;
49. end
50. end
51. assign add_delay = 1;
52. assign end_delay = add_delay && delay == 2000-1 ;
53.
54.
55. always @(posedge clk or negedge rst_n) begin
56. if (rst_n==0) begin
57. delay_time <= 0;
58. end
59. else if(add_delay_time) begin
60. if(end_delay_time)
61. delay_time <= 0;
62. else
63. delay_time <= delay_time+1 ;
64. end
65. end
66. assign add_delay_time = end_delay;
67. assign end_delay_time = add_delay_time && delay_time == 2-1 ;
68.
69.
70. always @(posedge clk or negedge rst_n)begin
71. if(rst_n==1'b0)begin
72. segment_data <= 4'd0;
73. end
74. else if(key_en)begin
75. segment_data <= data_in;
76. end
77. end
78.
79. always @(posedge clk or negedge rst_n)begin
80. if(rst_n==1'b0)begin
81. segment_tmp <= 4'd0;
82. end
83. else if(add_delay_time && delay_time == 1-1)begin
84. segment_tmp <= (segment_data+1)%10;
85. end
86. else if(end_delay_time)begin
87. segment_tmp <= (segment_data+1)/10;
88. end
89. end
90.
91.
92. always @(posedge clk or negedge rst_n)begin
93. if(rst_n==1'b0)begin
94. segment <= ZERO;
95. end
96. else begin
97. case(segment_tmp)
98. 4'd0:segment <= ZERO;
99. 4'd1:segment <= ONE ;
100. 4'd2:segment <= TWO ;
101. 4'd3:segment <= THREE;
102. 4'd4:segment <= FOUR ;
103. 4'd5:segment <= FIVE ;
104. 4'd6:segment <= SIX ;
105. 4'd7:segment <= SEVEN;
106. 4'd8:segment <= EIGHT;
107. 4'd9:segment <= NINE ;
108. default:begin
109. segment <= segment;
110. end
111. endcase
112. end
113. end
114.
115.
116. always @(posedge clk or negedge rst_n)begin
117. if(rst_n==1'b0)begin
118. seg_sel <= 2'b11;
119. end
120. else begin
121. seg_sel <= ~(2'b1<<delay_time);
122. end
123. end
124.
125.
126. endmodule
1.4 效果和總結
? 下圖是該工程在db603開發板上的現象——按下按鍵s7

? 下圖是該工程在ms980試驗箱上的現象——按下按鍵s13

由于該項目的上板現象是按下矩陣按鍵,數碼管顯示對應的按鍵號,想觀看完整現象的朋友可以看一下現象演示的視頻。
設計視頻教程、工程源代碼請移步明德揚論壇觀看下載。
感興趣的朋友也可以訪問明德揚論壇(www.fpgabbs.cn)進行FPGA相關工程設計學習,也歡迎大家在評論進行討論!
溫馨提示:明德揚2023推出了全新課程——邏輯設計基本功修煉課,降低學習FPGA門檻的同時,增加了學習的趣味性,并組織了考試贏積分活動
http://www.cqqtmy.cn/ffkc/415.html
(點擊→了解課程詳情?)感興趣請聯系易老師:13112063618(微信同步)