寫在前面的話
我們從小就開始接觸電腦,曾經多么羡慕那些在鍵盤上洋洋灑灑的人,手指輕柔的飛舞,刻畫出一章章美麗的篇幅…那么作為工程師的我們,同樣擁有着屬於我們的情懷。如果曾經的向往變成我們喜歡的玩具;如果曾經的神秘變成我們夜以繼日的痴迷。那么,一切又將如何?夢翼師兄攜手大家一起來欣賞、來品味。
項目需求
設計一個ps2鍵盤的接口驅動電路。
原理分析
ps2的接口如下圖所示:
其中,1是數據線DATA;
2是預留N/C;
3是GND;
4是VCC(+5V);
5是時鍾信號線CLK;
6是預留N/C;
數據傳輸的時序圖如下圖所示:

一般的ps2接口,都是ps2產生時鍾信號,而且是在上升沿的時候把數據發送出去,而在下降沿的時候數據被采樣,大多數的ps2設備發送數據的時鍾頻率是15Khz-20Khz。每一幀的數據有11位或者12位數據,其中包括:
1位起始位:總為邏輯0;
8位數據位:低位在前;
1位奇偶校驗位;
1位停止位:總為邏輯1;
1位答應位:僅用於主機對設備通信中(在本次鍵盤接口設計中不用)
當鍵盤的某一個按鍵被按下的時候,鍵盤會向外發送那一個按鍵的通碼,當按鍵松開的時候,鍵盤就會向外發送那一個按鍵的斷碼,需要注意的是,如果按着一個按鍵不放的話,鍵盤會以一定的頻率發送那一個按鍵的通碼。
右側小鍵盤的0-9的通碼與斷碼如下圖所示:

現在我們具體舉一個例子來說明ps2接口的工作原理,假設我現在按下小鍵盤中的0鍵,再按下按鍵9,然后把按鍵0松開,最后再松開按鍵9,ps2往FPGA發送的數據就會如下,先發0按鍵的通碼8’h70,再發9按鍵的通碼8’h7d,接着發0按鍵的斷碼8’hf0 8’h70,接着再發9按鍵的斷碼8’hf0 8’h7d,發送數據的順序如下: 8’h70→8’h7d→8’hf0→8’h70→8’hf0→8’h7d。
系統架構
當ps2_data_out信號有效的時候,valid會拉高一個周期(valid可用於同其他級聯模塊的握手)。

模塊功能介紹
| 模塊名 |
功能描述 |
| ps2_scan |
將ps2接口傳輸過來數據轉成通碼或者斷碼 |
頂層模塊端口描述
| 端口名 |
端口說明 |
| clk |
系統時鍾輸入 |
| rst_n |
系統復位 |
| Ps2_clk |
時鍾信號線 |
| Ps2_data_in |
數據線 |
| valid |
通、斷碼有效信號(高電平有效) |
| Ps2_data_out |
通、斷碼信號 |
用signaltap ii 分析波形
打開signaltap ii ,將采樣時鍾設置為clk,采樣深度為64K。將ps2_clk和ps2_data_in兩個輸入信號引出來,並將ps2_clk的的觸發條件改為下降沿(我們是在ps2_clk為下降沿的時候采集數據),之后進行全編譯,並將編譯好的sof文件下載到開發板中。

按下數字鍵“1”(數字小鍵盤),波形圖上出現如下波形:

在ps2_clk每個下降沿,我們進行讀數據,分別是:“01001011011”。第一位是起始位“0”,后面連續的8位是低位在前的有效數據:“10010110”,改成高位在前就是“01101001”,也就是我們的8‘h69(1的通碼就是8’h69)。第十位“1”為奇偶校驗位,第十一位“1”是停止位,這兩位不需要我們關心。
放開按鍵“1”,出現了如下的波形:

在ps2_clk每個下降沿,我們進行讀數據,分別是:“00000111111”。第一位是起始位“0”,后面連續的8位是低位在前的有效數據:“00001111”,改成高位在前就是“11110000”,也就是我們的8‘hf0(1的斷碼就是8‘hf0和8’h69 ,8’h69由於采樣深度的原因顯示不出來波形,不過我們也能分析出來它是怎么來的)。第十位“1”為奇偶校驗位,第十一位“1”是停止位,這兩位不需要我們關心。
根據ps2接口的原理和下板實測,我們得知了ps2接口傳輸數據的方式,那么就可以很容易地編寫出我們的代碼。
代碼解釋
Ps2_scan模塊代碼
| /**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function: 將ps2接口傳輸過來的數據譯成通、斷碼 *****************************************************/ 000 module ps2_scan ( 001 clk, //系統輸入時鍾 002 rst_n,//系統復位 003 ps2_data_in,//ps2的數據 004 ps2_data_out,//按鍵的通、斷碼 005 ps2_clk,//ps2的時鍾 006 valid//通、斷碼有效信號 007 ); 008 //系統輸入 009 input clk;//系統輸入時鍾 010 input rst_n;//系統復位 011 input ps2_clk;//ps2的時鍾 012 input ps2_data_in;//ps2的數據 013 //系統輸出 014 output reg [7:0] ps2_data_out;//按鍵的通、斷碼 015 output reg valid;//通、斷碼有效信號 016 017 wire neg;//下降沿標志線 018 reg ps2_clk_temp;//時鍾寄存器 019 020 always @ (posedge clk or negedge rst_n) 021 begin 022 if (!rst_n) 023 ps2_clk_temp <= 0; 024 else 025 ps2_clk_temp <= ps2_clk;//時鍾寄存器中的值永遠比ps2_clk晚一拍 026 end 027 028 029 //當寄存器里面是1,ps2_clk為0的時候 030 assign neg = ps2_clk_temp && (~ps2_clk);//正好就是ps2_clk為下降沿。 031 032 reg [3:0] num;//接收到數據線上數據的個數 033 034 always @ (posedge clk or negedge rst_n) 035 begin 036 if (!rst_n) 037 begin 038 num <= 0; 039 valid <= 0; 040 ps2_data_out <= 0; 041 end 042 else 043 begin 044 if (neg) 045 begin 046 case (num) 047 0 : num <= num + 1;//起始位 048 049 1 : begin//有效數據的第0位 050 num <= num + 1; 051 ps2_data_out[0] <= ps2_data_in; 052 end 053 054 2 : begin//有效數據的第1位 055 num <= num + 1; 056 ps2_data_out[1] <= ps2_data_in; 057 end 058 059 3 : begin//有效數據的第2位 060 num <= num + 1; 061 ps2_data_out[2] <= ps2_data_in; 062 end 063 064 4 : begin//有效數據的第3位 065 num <= num + 1; 066 ps2_data_out[3] <= ps2_data_in; 067 end 068 069 5 : begin//有效數據的第4位 070 num <= num + 1; 071 ps2_data_out[4] <= ps2_data_in; 072 end 073 074 6 : begin//有效數據的第5位 075 num <= num + 1; 076 ps2_data_out[5] <= ps2_data_in; 077 end 078 079 7 : begin//有效數據的第6位 080 num <= num + 1; 081 ps2_data_out[6] <= ps2_data_in; 082 end 083 084 8 : begin//有效數據的第7位 085 num <= num + 1; 086 ps2_data_out[7] <= ps2_data_in; 087 end 088 089 9 : begin//奇偶校驗位 090 num <= num + 1; 091 valid <= 1;//拉高通、斷碼有效標志 092 end 093 094 10 : num <= 0;//停止位 095 096 default : ; 097 endcase 098 end 099 else 100 begin 101 valid <= 0;//拉低通、斷碼有效標志 102 end 103 end 104 end 105 106 endmodule |
代碼中第47行,我們知道第一個是起始位是”0“,故並沒有判斷,而是直接跳轉到下一個狀態。
代碼中第89行,ps2_data_in發送的是奇偶校驗位,在此我們並沒有做出判斷,而是直接跳轉到下一個狀態。
代碼中第91行,直接拉高通、斷碼的有效標志信號。
代碼中第101行,直接拉低通、斷碼的有效標志信號。
代碼中第94行,ps2_data_in發送的是停止位“0”。
測試代碼
| /**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function: 測試ps2_scan模塊 *****************************************************/ 000 `timescale 1ns/1ps //定義時間單位和精度 001 002 module ps2_scan_tb; 003 //系統輸入 004 reg clk;//系統輸入時鍾 005 reg rst_n;//系統復位 006 reg ps2_clk;//ps2的時鍾 007 reg ps2_data_in;//ps2的數據 008 //系統輸出 009 wire [7:0] ps2_data_out;//按鍵的通、斷碼 010 wire valid;//通、斷碼有效信號 011 012 initial begin 013 clk = 1; 014 rst_n = 0; 015 ps2_clk = 1; 016 ps2_data_in = 1; 017 #200.1 018 rst_n = 1; 019 //數字“1”的通碼 020 ps2_data_in = 0;//起始位“0” 021 #60 022 ps2_clk = 0; 023 #120 024 ps2_clk = 1; 025 #60 026 ps2_data_in = 1;//“1” 027 #60 028 ps2_clk = 0; 029 #120 030 ps2_clk = 1; 031 #60 032 ps2_data_in = 0;//“0” 033 #60 034 ps2_clk = 0; 035 #120 036 ps2_clk = 1; 037 #60 038 ps2_data_in = 0;//“0” 039 #60 040 ps2_clk = 0; 041 #120 042 ps2_clk = 1; 043 #60 044 ps2_data_in = 1;//“1” 045 #60 046 ps2_clk = 0; 047 #120 048 ps2_clk = 1; 049 #60 050 ps2_data_in = 0;//“0” 051 #60 052 ps2_clk = 0; 053 #120 054 ps2_clk = 1; 055 #60 056 ps2_data_in = 1;//“1” 057 #60 058 ps2_clk = 0; 059 #120 060 ps2_clk = 1; 061 #60 062 ps2_data_in = 1;//“1” 063 #60 064 ps2_clk = 0; 065 #120 066 ps2_clk = 1; 067 #60 068 ps2_data_in = 0;//“0” 069 #60 070 ps2_clk = 0; 071 #120 072 ps2_clk = 1; 073 #60 074 ps2_data_in = 1;//奇偶校驗位“1” 075 #60 076 ps2_clk = 0; 077 #120 078 ps2_clk = 1; 079 #60 080 ps2_data_in = 1;//停止位“1” 081 #60 082 ps2_clk = 0; 083 #120 084 ps2_clk = 1; 085 #2000 086 //斷碼中“f0” 087 ps2_data_in = 0;//起始位“0” 088 #60 089 ps2_clk = 0; 090 #120 091 ps2_clk = 1; 092 #60 093 ps2_data_in = 0;//“0” 094 #60 095 ps2_clk = 0; 096 #120 097 ps2_clk = 1; 098 #60 099 ps2_data_in = 0;//“0” 100 #60 101 ps2_clk = 0; 102 #120 103 ps2_clk = 1; 104 #60 105 ps2_data_in = 0;//“0” 106 #60 107 ps2_clk = 0; 108 #120 109 ps2_clk = 1; 110 #60 111 ps2_data_in = 0;//“0” 112 #60 113 ps2_clk = 0; 114 #120 115 ps2_clk = 1; 116 #60 117 ps2_data_in = 1;//“1” 118 #60 119 ps2_clk = 0; 120 #120 121 ps2_clk = 1; 122 #60 123 ps2_data_in = 1;//“1” 124 #60 125 ps2_clk = 0; 126 #120 127 ps2_clk = 1; 128 #60 129 ps2_data_in = 1;//“1” 130 #60 131 ps2_clk = 0; 132 #120 133 ps2_clk = 1; 134 #60 135 ps2_data_in = 1;//“1” 136 #60 137 ps2_clk = 0; 138 #120 139 ps2_clk = 1; 140 #60 141 ps2_data_in = 1;//奇偶校驗位“1” 142 #60 143 ps2_clk = 0; 144 #120 145 ps2_clk = 1; 146 #60 147 ps2_data_in = 1;//停止位“1” 148 #60 149 ps2_clk = 0; 150 #120 151 ps2_clk = 1; 152 #2000 153 //數字“1”的通碼 154 ps2_data_in = 0;//起始位“0” 155 #60 156 ps2_clk = 0; 157 #120 158 ps2_clk = 1; 159 #60 160 ps2_data_in = 1;//“1” 161 #60 162 ps2_clk = 0; 163 #120 164 ps2_clk = 1; 165 #60 166 ps2_data_in = 0;//“0” 167 #60 168 ps2_clk = 0; 169 #120 170 ps2_clk = 1; 171 #60 172 ps2_data_in = 0;//“0” 173 #60 174 ps2_clk = 0; 175 #120 176 ps2_clk = 1; 177 #60 178 ps2_data_in = 1;//“1” 179 #60 180 ps2_clk = 0; 181 #120 182 ps2_clk = 1; 183 #60 184 ps2_data_in = 0;//“0” 185 #60 186 ps2_clk = 0; 187 #120 188 ps2_clk = 1; 189 #60 190 ps2_data_in = 1;//“1” 191 #60 192 ps2_clk = 0; 193 #120 194 ps2_clk = 1; 195 #60 196 ps2_data_in = 1;//“1” 197 #60 198 ps2_clk = 0; 199 #120 200 ps2_clk = 1; 201 #60 202 ps2_data_in = 0;//“0” 203 #60 204 ps2_clk = 0; 205 #120 206 ps2_clk = 1; 207 #60 208 ps2_data_in = 1;//奇偶校驗位“1” 209 #60 210 ps2_clk = 0; 211 #120 212 ps2_clk = 1; 213 #60 214 ps2_data_in = 1;//停止位“1” 215 #60 216 ps2_clk = 0; 217 #120 218 ps2_clk = 1; 219 end 220 221 always # 10 clk = ~clk;//50M的時鍾 222 223 ps2_scan ps2_scan ( 224 .clk(clk), //系統輸入時鍾 225 .rst_n(rst_n),//系統復位 226 .ps2_data_in(ps2_data_in),//ps2的數據 227 .ps2_data_out(ps2_data_out),//按鍵的通、斷碼 228 .ps2_clk(ps2_clk),//ps2的時鍾 229 .valid(valid)//通、斷碼有效信號 230 ); 231 232 endmodule |
測試中,我們發送了數字”1“的通碼,斷碼,模仿了數字鍵”1“的按下和抬起。
仿真分析

在仿真中,測試了數字 “1”的通、斷碼的接收和發送,每當檢測出一個八位有效數據的時候,valid都會出現一個時鍾周期的尖峰脈沖。
