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 - 博客园