?0 故事背景
見過這種接口的朋友們,大概都已經成家立業了吧。不過今天我們不討論這種接口的歷史,只講講這種接口的設計。(如果還沒有成家的朋友也別生氣,做自己想做的事情就對了!)
1 時序分析
數據幀格式如圖所示,起始位為低電平,停止位為高電平,應答位僅用在主機對設備的通訊中使用。如果數據位中1的個數為偶數,校驗位就為1;如果數據位中1的個數為奇數,校驗位就為0;總之,數據位中1的個數加上校驗位中1的個數總為奇數,因此總進行奇校驗。(是不是發現它的數據傳輸和串口很像呢!)[1]
(為了簡化)當一個鍵(A~Z)被按下或按住,就發送通碼(都是f0);當一個鍵(A~Z)被釋放,就發送斷碼。
鍵盤掃描碼(實用于標準PC的101、102和104 鍵的鍵盤),按下發送通碼,彈起發送斷碼。[2]了解即可。
2 接口定義
信號名稱 | 方向 | 接口描述信息 |
clk | input | 時鐘信號,50MHz |
rst_n | input | 復位信號,低電平有效 |
ps2k_clk | input | PS/2接口時鐘信號 |
ps2k_data | input | PS/2接口數據信號 |
rs232_tx | input | RS232發送數據信號 |
3 RTL視圖
4 整體代碼
Top層代碼:
`timescale 1ns / 1ps// Company:
// Engineer:
//
// Create Date: 21:21:41 08/07/08
// Design Name:
// Module Name: ps2_key
// Project Name:
// Target Device:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 歡迎加入EDN的FPGA/CPLD助學小組一起討論:http://group.ednchina.com/1375/module ps2_key(clk,rst_n,ps2k_clk,ps2k_data,rs232_tx);input clk; //50M時鐘信號
input rst_n; //復位信號
input ps2k_clk; //PS2接口時鐘信號
input ps2k_data; //PS2接口數據信號
output rs232_tx; // RS232發送數據信號wire[7:0] ps2_byte; // 1byte鍵值
wire ps2_state; //按鍵狀態標志位wire bps_start; //接收到數據后,波特率時鐘啟動信號置位
wire clk_bps; // clk_bps的高電平為接收或者發送數據位的中間采樣點 ps2scan ps2scan( .clk(clk), //按鍵掃描模塊.rst_n(rst_n), .ps2k_clk(ps2k_clk),.ps2k_data(ps2k_data),.ps2_byte(ps2_byte),.ps2_state(ps2_state));speed_select speed_select( .clk(clk),.rst_n(rst_n),.bps_start(bps_start),.clk_bps(clk_bps));my_uart_tx my_uart_tx( .clk(clk),.rst_n(rst_n),.clk_bps(clk_bps),.rx_data(ps2_byte),.rx_int(ps2_state),.rs232_tx(rs232_tx),.bps_start(bps_start));endmodule
ps2scan代碼
`timescale 1ns / 1ps// Company:
// Engineer:
//
// Create Date: 21:25:06 08/07/08
// Design Name:
// Module Name: ps2scan
// Project Name:
// Target Device:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// module ps2scan(clk,rst_n,ps2k_clk,ps2k_data,ps2_byte,ps2_state);input clk; //50M時鐘信號
input rst_n; //復位信號
input ps2k_clk; //PS2接口時鐘信號
input ps2k_data; //PS2接口數據信號
output[7:0] ps2_byte; // 1byte鍵值,只做簡單的按鍵掃描
output ps2_state; //鍵盤當前狀態,ps2_state=1表示有鍵被按下 //------------------------------------------
reg ps2k_clk_r0,ps2k_clk_r1,ps2k_clk_r2; //ps2k_clk狀態寄存器//wire pos_ps2k_clk; // ps2k_clk上升沿標志位
wire neg_ps2k_clk; // ps2k_clk下降沿標志位always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginps2k_clk_r0 <= 1'b0;ps2k_clk_r1 <= 1'b0;ps2k_clk_r2 <= 1'b0;endelse begin //鎖存狀態,進行濾波ps2k_clk_r0 <= ps2k_clk;ps2k_clk_r1 <= ps2k_clk_r0;ps2k_clk_r2 <= ps2k_clk_r1;end
endassign neg_ps2k_clk = ~ps2k_clk_r1 & ps2k_clk_r2; //下降沿//------------------------------------------
reg[7:0] ps2_byte_r; //PC接收來自PS2的一個字節數據存儲器
reg[7:0] temp_data; //當前接收數據寄存器
reg[3:0] num; //計數寄存器always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginnum <= 4'd0;temp_data <= 8'd0;endelse if(neg_ps2k_clk) begin //檢測到ps2k_clk的下降沿case (num)4'd0: num <= num+1'b1;4'd1: beginnum <= num+1'b1;temp_data[0] <= ps2k_data; //bit0end4'd2: beginnum <= num+1'b1;temp_data[1] <= ps2k_data; //bit1end4'd3: beginnum <= num+1'b1;temp_data[2] <= ps2k_data; //bit2end4'd4: beginnum <= num+1'b1;temp_data[3] <= ps2k_data; //bit3end4'd5: beginnum <= num+1'b1;temp_data[4] <= ps2k_data; //bit4end4'd6: beginnum <= num+1'b1;temp_data[5] <= ps2k_data; //bit5end4'd7: beginnum <= num+1'b1;temp_data[6] <= ps2k_data; //bit6end4'd8: beginnum <= num+1'b1;temp_data[7] <= ps2k_data; //bit7end4'd9: beginnum <= num+1'b1; //奇偶校驗位,不做處理end4'd10: beginnum <= 4'd0; // num清零enddefault: ;endcaseend
endreg key_f0; // 松鍵標志位,置1表示接收到數據8'hf0,再接收到下一個數據后清零
reg ps2_state_r; // 鍵盤當前狀態,ps2_state_r=1表示有鍵被按下 always @ (posedge clk or negedge rst_n) begin //接收數據的相應處理,這里只對1byte的鍵值進行處理if(!rst_n) beginkey_f0 <= 1'b0;ps2_state_r <= 1'b0;endelse if(num==4'd10) begin //剛傳送完一個字節數據if(temp_data == 8'hf0) key_f0 <= 1'b1;else beginif(!key_f0) begin //說明有鍵按下ps2_state_r <= 1'b1;ps2_byte_r <= temp_data; //鎖存當前鍵值endelse beginps2_state_r <= 1'b0;key_f0 <= 1'b0;endendend
endreg[7:0] ps2_asci; //接收數據的相應ASCII碼always @ (ps2_byte_r) begincase (ps2_byte_r) //鍵值轉換為ASCII碼,這里做的比較簡單,只處理字母8'h15: ps2_asci <= 8'h51; //Q8'h1d: ps2_asci <= 8'h57; //W8'h24: ps2_asci <= 8'h45; //E8'h2d: ps2_asci <= 8'h52; //R8'h2c: ps2_asci <= 8'h54; //T8'h35: ps2_asci <= 8'h59; //Y8'h3c: ps2_asci <= 8'h55; //U8'h43: ps2_asci <= 8'h49; //I8'h44: ps2_asci <= 8'h4f; //O8'h4d: ps2_asci <= 8'h50; //P 8'h1c: ps2_asci <= 8'h41; //A8'h1b: ps2_asci <= 8'h53; //S8'h23: ps2_asci <= 8'h44; //D8'h2b: ps2_asci <= 8'h46; //F8'h34: ps2_asci <= 8'h47; //G8'h33: ps2_asci <= 8'h48; //H8'h3b: ps2_asci <= 8'h4a; //J8'h42: ps2_asci <= 8'h4b; //K8'h4b: ps2_asci <= 8'h4c; //L8'h1a: ps2_asci <= 8'h5a; //Z8'h22: ps2_asci <= 8'h58; //X8'h21: ps2_asci <= 8'h43; //C8'h2a: ps2_asci <= 8'h56; //V8'h32: ps2_asci <= 8'h42; //B8'h31: ps2_asci <= 8'h4e; //N8'h3a: ps2_asci <= 8'h4d; //Mdefault: ;endcase
endassign ps2_byte = ps2_asci;
assign ps2_state = ps2_state_r;endmodule
speed_select代碼
`timescale 1ns / 1ps// Company:
// Engineer:
//
// Create Date: 17:27:40 08/28/08
// Design Name:
// Module Name: speed_select
// Project Name:
// Target Device:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// module speed_select(clk,rst_n,bps_start,clk_bps);input clk; // 50MHz主時鐘
input rst_n; //低電平復位信號
input bps_start; //接收到數據后,波特率時鐘啟動信號置位
output clk_bps; // clk_bps的高電平為接收或者發送數據位的中間采樣點 /*
parameter bps9600 = 5207, //波特率為9600bpsbps19200 = 2603, //波特率為19200bpsbps38400 = 1301, //波特率為38400bpsbps57600 = 867, //波特率為57600bpsbps115200 = 433; //波特率為115200bpsparameter bps9600_2 = 2603,bps19200_2 = 1301,bps38400_2 = 650,bps57600_2 = 433,bps115200_2 = 216;
*///以下波特率分頻計數值可參照上面的參數進行更改
`define BPS_PARA 5207 //波特率為9600時的分頻計數值
`define BPS_PARA_2 2603 //波特率為9600時的分頻計數值的一半,用于數據采樣reg[12:0] cnt; //分頻計數
reg clk_bps_r; //波特率時鐘寄存器//----------------------------------------------------------
reg[2:0] uart_ctrl; // uart波特率選擇寄存器
//----------------------------------------------------------always @ (posedge clk or negedge rst_n)if(!rst_n) cnt <= 13'd0;else if((cnt == `BPS_PARA) || !bps_start) cnt <= 13'd0; //波特率計數清零else cnt <= cnt+1'b1; //波特率時鐘計數啟動always @ (posedge clk or negedge rst_n)if(!rst_n) clk_bps_r <= 1'b0;else if(cnt == `BPS_PARA_2) clk_bps_r <= 1'b1; // clk_bps_r高電平為接收數據位的中間采樣點,同時也作為發送數據的數據改變點else clk_bps_r <= 1'b0;assign clk_bps = clk_bps_r;endmodule
my_uart_tx代碼
`timescale 1ns / 1ps// Company:
// Engineer:
//
// Create Date: 17:11:32 08/28/08
// Design Name:
// Module Name: my_uart_rx
// Project Name:
// Target Device:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// module my_uart_tx(clk,rst_n,clk_bps,rx_data,rx_int,rs232_tx,bps_start);input clk; // 50MHz主時鐘
input rst_n; //低電平復位信號
input clk_bps; // clk_bps的高電平為接收或者發送數據位的中間采樣點
input[7:0] rx_data; //接收數據寄存器
input rx_int; //接收數據中斷信號,接收到數據期間始終為高電平,在此利用它的上升沿來啟動發送數據
output rs232_tx; // RS232發送數據信號
output bps_start; //接收或者要發送數據,波特率時鐘啟動信號置位//---------------------------------------------------------
reg rx_int0,rx_int1,rx_int2; //rx_int信號寄存器,捕捉下降沿濾波用
wire pos_rx_int; // rx_int下降沿標志位always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginrx_int0 <= 1'b0;rx_int1 <= 1'b0;rx_int2 <= 1'b0;endelse beginrx_int0 <= rx_int;rx_int1 <= rx_int0;rx_int2 <= rx_int1;end
endassign pos_rx_int = rx_int1 & ~rx_int2; //捕捉到上升沿后,neg_rx_int拉地保持一個主時鐘周期//---------------------------------------------------------
reg[7:0] tx_data; //待發送數據的寄存器
//---------------------------------------------------------
reg bps_start_r;
reg tx_en; //發送數據使能信號,高有效
reg[3:0] num;always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginbps_start_r <= 1'bz;tx_en <= 1'b0;tx_data <= 8'd0;endelse if(pos_rx_int) begin //接收數據完畢,準備把接收到的數據發出去bps_start_r <= 1'b1;tx_data <= rx_data; //把接收到的數據存入發送數據寄存器tx_en <= 1'b1; //進入發送數據狀態中endelse if(num==4'd11) begin //數據發送完成,復位bps_start_r <= 1'b0;tx_en <= 1'b0;end
endassign bps_start = bps_start_r;//---------------------------------------------------------
reg rs232_tx_r;always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginnum <= 4'd0;rs232_tx_r <= 1'b1;endelse if(tx_en) beginif(clk_bps) beginnum <= num+1'b1;case (num)4'd0: rs232_tx_r <= 1'b0; //發送起始位4'd1: rs232_tx_r <= tx_data[0]; //發送bit04'd2: rs232_tx_r <= tx_data[1]; //發送bit14'd3: rs232_tx_r <= tx_data[2]; //發送bit24'd4: rs232_tx_r <= tx_data[3]; //發送bit34'd5: rs232_tx_r <= tx_data[4]; //發送bit44'd6: rs232_tx_r <= tx_data[5]; //發送bit54'd7: rs232_tx_r <= tx_data[6]; //發送bit64'd8: rs232_tx_r <= tx_data[7]; //發送bit74'd9: rs232_tx_r <= 1'b1; //發送結束位default: rs232_tx_r <= 1'b1;endcaseendelse if(num==4'd11) num <= 4'd0; //復位end
endassign rs232_tx = rs232_tx_r;endmodule
5 總結
代碼中有詳細的解釋,有問題隨時討論。
知識是相互貫通的,夯實基礎,才能筑高樓。歡迎大家批評指正!
參考文獻
[1]特權FPGA PS2鍵盤解碼實驗
[2]PS2鍵盤掃描碼:通碼與斷碼 - JustXIII - 博客園