FPGA学习系列:25. PS2通信电路的设计

设计背景: 
ps2接口是一种pc兼容型电脑系统上的接口,可以用来链接键盘及鼠标。ps2的命名来自于1987年时ibm所推出的个人电脑:ps/2系列。ps2的键盘和鼠标在电气特性上十分类似,主要差别在于键盘接口需要双向的沟通。ps2接口不支持热插拔,使用时需要关机插上,目前已逐渐被usb所替代,只有少部分台式机仍然提供ps2接口。
设计原理: 
ps2的接口如下图所示:
上图中,1是数据线data;2是预留n/c;3是gnd;4是vcc(+5v);5是时钟信号线clk;6是预留n/c。
ps2原理电路图如下:
ps2协议总共由两根线组成,从电路原理图中也可以看出,需要控制的只有ps2_clk和ps2_sda,即一根时钟线和一根数据线。ps2设备中的时钟和数据都是集电极开路的,平时都是高电平。本设计将采用ps2接口的键盘称为从机,将控制和解码ps2协议的一方称为主机(fpga)。ps2协议的时钟线始终由从机(即键盘)产生的,ps2协议发送一个字节数据共有11位。时序如下图所示:
1bit起始位,总是0;8bit数据位,低位在前;1bit校验位,奇校验;1bit停止位,总是1。
从机(键盘)按照这个时序发送数据,主机(fpga)只需要实现该协议的解码,即可将其中的8bit数据位提取出来。根据时序图可以看出,数据在ps2时钟的下降沿是保持稳定的,主机只需在检测到ps2时钟出现下降沿时,去读取数据线上的电平,就可得到正确的数据。
通过上述的内容,已经知道了ps2从机到主机的通信协议,接下来就需要知道从机发送过来的每个字节代表什么?这时就要对照键盘编码表进行查看。键盘上一个按键由按下到释放时,键盘是按照如下的规定向主机发送数据的:
只要一个按键被按下,这个键的通码(make)就会被发送到主机,按键一被释放,断码(break)也会被发送,如果按键被按下不释放的话,键盘会以一定的频率发送那个按键的通码。每个按键都有自己唯一的通码和断码,从而组成键盘编码表,表上的码值为16进制:
例如,如果键盘上‘a’键被按下时,键盘就会向主机发送‘a’键对应的通码‘1c’,直到按键被释放。在按键被释放后,键盘将会向主机发送‘a’键的断码,即首先发送‘f0’,然后下一个字节发送‘1c’。通过观察键盘编码表,可以发现按键的通码与断码存在一定的联系,多数断码的第一个字节是‘f0’,第二个字节则是这个键的通码。
如果按下键盘上的扩展按键时,如‘end’,当‘end’键被按下后,键盘会首先向主机发送‘e0’,然后发送‘f0’,最后再发送‘69’。 根据上述的分析可知,在主机(fpga)解码一次数据后,还需要对这个数据进行分析判断,判断该数据是否为断码标志‘f0’以及扩展码标志‘e0’。
设计架构图: 
本设计将实现在ps2键盘上按下26个字母任一个,在数码管上显示其对应的ascii码。架构图如下:
ps2scan模块是根据ps2的时序协议,将键盘的按键值译成一个8位的数据(out_data)输出;ascii模块,根据ascii表,将数据与字母一一对应;seg_num模块将相应的数据在数码管上显示。
设计代码: 
ps2scan模块代码:
0   module ps2scan (clk, rst_n, ps2_sclk, ps2_sda, out_data);  
1  
2       //端口信号:模块的输入输出接口
3       input clk;//系统时钟
4       input rst_n;//低电平复位
5       input   ps2_sclk;//ps2时钟信号(ps2设备自动产生,大约10khz左右)
6       input ps2_sda;//ps2数据信号
7       output reg [7:0] out_data; //键值数据(采集ps2的一帧信号中间的8位有效位)
8       
9       //在发送时序中,数据在ps2_sclk的下降沿采集信号,以下为检测下降沿
10      reg  in1,in2;   
11      wire en;    
12      
13      always @ (posedge clk or negedge rst_n)
14      begin
15          if(!rst_n) 
16              begin
17                  in1 <= 1'b0;
18                  in2 <= 1'b0;
19              end
20          else 
21              begin                               
22                  in1 <= ps2_sclk;
23                  in2 <= in1;
24              end
25      end
26 
27      assign en = (~in1) & in2;   //当出现ps2_sclk下降沿后拉高一个时钟,以进行数据的采集
28 
29 
30      //采集ps2的一帧信号中间的8位有效位:每检测一个下降沿算一位数据位,在中间8位采集有效数据
31      reg[7:0] temp_data;         
32      reg[3:0] num;   
33      
34      always @ (posedge clk or negedge rst_n)
35      begin
36          if(!rst_n) 
37              begin
38                  num <= 4'd0;
39                  temp_data <= 8'd0;
40              end
41          else if(en) 
42              case (num)
43                  4'd0:   num <= num+1'b1;    //开始位
44                  4'd1:   begin
45                              num <= num+1'b1;
46                              temp_data[0] <= ps2_sda;    //bit0
47                          end
48                  4'd2:   begin
49                              num <= num+1'b1;
50                              temp_data[1] <= ps2_sda;    //bit1
51                          end
52                  4'd3:   begin
53                              num <= num+1'b1;
54                              temp_data[2] <= ps2_sda;    //bit2
55                          end
56                  4'd4:   begin
57                              num <= num+1'b1;
58                              temp_data[3] <= ps2_sda;    //bit3
59                          end
60                  4'd5:   begin
61                              num <= num+1'b1;
62                              temp_data[4] <= ps2_sda;    //bit4
63                          end
64                  4'd6:   begin
65                              num <= num+1'b1;
66                              temp_data[5] <= ps2_sda;    //bit5
67                          end
68                  4'd7:   begin
69                              num <= num+1'b1;
70                              temp_data[6] <= ps2_sda;    //bit6
71                          end
72                  4'd8:   begin
73                              num <= num+1'b1;
74                              temp_data[7] <= ps2_sda;    //bit7
75                          end
76                  4'd9:   num <= num+1'b1;    //结束位
77                  4'd10:num <= 4'd0;  
78                  default: ;
79              endcase 
80      end
81      
82      //判断是否有键按下:根据通码、断码的特性判断
83      reg key;    
84      
85      always @ (posedge clk or negedge rst_n)
86      begin
87          if(!rst_n)
88              key <= 1'b0;
89          else if(num==4'd10)
90              begin   
91                  if(temp_data == 8'hf0) 
92                          key <= 1'b1;
93                  else 
94                      begin
95                          if(!key) 
96                              out_data <= temp_data;  
97                          else 
98                              key <= 1'b0;
99                      end
100             end
101     end 
102     
103 endmodule
ascii模块代码:
0   module ascii( out_data, tx_out);
1     
2       //端口信号:模块的输入输出接口
3       input [7:0] out_data;   //键盘的扫描键值
4       output reg [7:0] tx_out;//通过ascii码转换之后的值
5       
6       //通过查找表的方式,对照ascii码将键值转换为二进制数值
7       always@(*) 
8           case (out_data)     
9               8'h1c: tx_out <= 8'h41; //a
10              8'h32: tx_out <= 8'h42; //b
11              8'h21: tx_out <= 8'h43; //c
12              8'h23: tx_out <= 8'h44; //d
13              8'h24: tx_out <= 8'h45; //e
14              8'h2b: tx_out <= 8'h46; //f
15              8'h34: tx_out <= 8'h47; //g
16              8'h33: tx_out <= 8'h48; //h
17              8'h43: tx_out <= 8'h49; //i
18              8'h3b: tx_out <= 8'h4a; //j
19              8'h42: tx_out <= 8'h4b; //k
20              8'h4b: tx_out <= 8'h4c; //l
21              8'h3a: tx_out <= 8'h4d; //m
22              8'h31: tx_out <= 8'h4e; //n
23              8'h44: tx_out <= 8'h4f; //o
24              8'h4d: tx_out <= 8'h50; //p
25              8'h15: tx_out <= 8'h51; //q
26              8'h2d: tx_out <= 8'h52; //r
27              8'h1b: tx_out <= 8'h53; //s
28              8'h2c: tx_out <= 8'h54; //t
29              8'h3c: tx_out <= 8'h55; //u
30              8'h2a: tx_out <= 8'h56; //v
31              8'h1d: tx_out <= 8'h57; //w
32              8'h22: tx_out <= 8'h58; //x
33              8'h35: tx_out <= 8'h59; //y
34              8'h1a: tx_out <= 8'h5a; //z
35              default: tx_out <= 8'h00;
36          endcase
37      
38  endmodule
seg_num模块代码:
0   module seg_num (clk, rst_n, num, sel, seg);        

2       //端口信号:模块的输入输出接口
3       input clk;               //系统时钟50mhz
4       input rst_n;             //低电平复位
5       input [7:0] num;         //输入的数据
6       output reg [2:0] sel; //数码管位选
7       output reg [7:0] seg; //数码管段选


10      //计数分频,通过选择cnt的相应位的变化来大致分频    
11      reg [23:0]  cnt;
12      wire        clk_r;
13      
14      always@(posedge clk or negedge rst_n)
15      begin
16          if(!rst_n)
17              cnt <= 24'd0;
18          else
19              cnt <= cnt + 1'b1;
20      end 
21      
22      assign clk_r = cnt[15] ; //通过计数cnt的第10位来分频计数,2^10/50m
23      //通过查找表的方式将数据与相应的数码管显示一一对应  
24      reg [3:0] data;
25      
26      always@(*)
27          case(data)
28              4'h0:  seg <= 8'hc0; //8'b1100_0000
29              4'h1:  seg <= 8'hf9;    //8'b1111_1001
30              4'h2:    seg <= 8'ha4;  //8'b1010_0100  
31              4'h3:  seg <= 8'hb0;    //8'b1011_0000
32              4'h4:  seg <= 8'h99;    //8'b1001_1001
33              4'h5:  seg <= 8'h92;    //8'b1001_0010
34              4'h6:  seg <= 8'h82;    //8'b1000_0010
35              4'h7:  seg <= 8'hf8;    //8'b1111_1000
36              4'h8:  seg <= 8'h80;    //8'b1000_0000
37              4'h9:  seg <= 8'h90;    //8'b1001_0000
38              4'ha:  seg <= 8'h88;
39              4'hb:  seg <= 8'h83;
40              4'hc:  seg <= 8'hc6;
41              4'hd:  seg <= 8'ha1;
42              4'he:  seg <= 8'h86;
43              4'hf:  seg <= 8'h8e;
44              default:seg <= 8'hff; //8'b1111_1111
45          endcase
46
47      //通过查找表的方式,在不同位选下,显示数据的相应位
48      always@(*)
49      begin
50          case(sel)
51              000:  data <= num[7:4]; 
52              001:  data <= num[3:0];
53              default:;
54          endcase
55      end
56          
57      always@(posedge clk_r or  negedge rst_n) 
58      begin
59          if(!rst_n)
60              sel <= 3'd0;
61          else    if(sel == 3'd1)
62              sel <= 3'd0;    
63          else
64              sel <= sel + 1'b1;
65      end 
66      
67  endmodule
top顶层模块代码:
0   module top (clk, rst_n, ps2_sclk, ps2_sda, sel, seg);

2       //外部接口
3       input clk;      //系统时钟50mhz
4       input rst_n;    //低电平复位
5       input ps2_sclk; //ps2时钟
6       input ps2_sda;  //ps2数据
7       output [2:0] sel;    //数码管位选
8       output [7:0] seg;    //数码管段选

10      wire[7:0] out_data, tx_out; 
11      
12      /*****键盘扫描模块*****/
13      ps2scan ps2scan_inst(   
14          .clk(clk),              
15          .rst_n(rst_n),              
16          .ps2_sclk(ps2_sclk),
17          .ps2_sda(ps2_sda),
18          .out_data(out_data)
19      );
20
21      /*****数据转ascii码模块*****/
22      ascii  ascii_inst(       
23          .out_data(out_data),
24          .tx_out(tx_out) 
25      );
26      
27      /*****数码管显示模块*****/
28      seg_num  seg_num_inst( 
29          .num(tx_out),
30          .clk(clk),
31          .rst_n(rst_n),
32          .sel(sel),
33          .seg(seg) 
34      );  
35      
36  endmodule
top_tb顶层测试代码:
0   `timescale 1ns/1ns
1  
2   module top_tb;
3  
4       reg clk;
5       reg rst_n;
6       reg ps2_sda;
7       reg ps2_sclk;
8       wire [7:0] seg;
9       wire [2:0] sel;
10      
11      top top_dut(
12          .clk(clk),
13          .rst_n(rst_n),
14          .ps2_sclk(ps2_sclk),
15          .ps2_sda(ps2_sda),
16          .sel(sel),
17          .seg(seg)
18      );
19 
20      initial begin
21          clk = 1;
22          rst_n = 0;
23          ps2_sda = 1;
24          ps2_sclk = 1;
25          
26          #200;
27          rst_n = 1;
28          key_event(8'h1a);   /* z */
29          #40000;
30          key_event(8'h22);   /* x */
31          #80000;
32          key_event(8'h44);   /* o */
33          #13200;
34          key_event(8'h4d);   /* p */
35          #25600;
36          key_event(8'h24);   /* e */
37          #12300;
38          key_event(8'h31);   /* n */
39          
40          #2000000;
41          $stop;
42      end 
43      
44  /*---------生成工作时钟-----------*/
45      always #10 clk = ~clk;
46      
47  /*----任务:以ps2协议发送一个字节的数据-----*/
48      task send_data;
49          input [7:0]data;
50          begin
51                       ps2_sda = 0;   /*发送起始位*/
52              #20000;ps2_sclk = 0;
53              #40000;ps2_sclk = 1;
54              
55              #20000;ps2_sda = data[0];/*发送第0位*/
56              #20000;ps2_sclk = 0;
57              #40000;ps2_sclk = 1;
58              
59              #20000;ps2_sda = data[1];/*发送第1位*/
60              #20000;ps2_sclk = 0;
61              #40000;ps2_sclk = 1;
62              
63              #20000;ps2_sda = data[2];/*发送第2位*/
64              #20000;ps2_sclk = 0;
65              #40000;ps2_sclk = 1;
66              
67              #20000;ps2_sda = data[3];/*发送第3位*/
68              #20000;ps2_sclk = 0;
69              #40000;ps2_sclk = 1;
70              
71              #20000;ps2_sda = data[4];/*发送第4位*/
72              #20000;ps2_sclk = 0;
73              #40000;ps2_sclk = 1;
74              
75              #20000;ps2_sda = data[5];/*发送第5位*/
76              #20000;ps2_sclk = 0;
77              #40000;ps2_sclk = 1;
78              
79              #20000;ps2_sda = data[6];/*发送第6位*/
80              #20000;ps2_sclk = 0;
81              #40000;ps2_sclk = 1;
82              
83              #20000;ps2_sda = data[7];/*发送第7位*/
84              #20000;ps2_sclk = 0;
85              #40000;ps2_sclk = 1;
86              
87              #20000;ps2_sda = 0;/*暂时忽略校验位*/
88              #20000;ps2_sclk = 0;
89              #40000;ps2_sclk = 1;
90              
91              #20000;ps2_sda = 1;/*停止位*/
92              #20000;ps2_sclk = 0;
93              #40000;ps2_sclk = 1;            
94          end
95      endtask
96 
97  /*-----任务:模拟按下按键的操作------*/ 
98      task press_key;
99          input [7:0]key_number;
100         begin
101             send_data(key_number);
102             #50000;     
103         end
104     endtask
105     
106 /*-----任务:模拟释放按键的操作------*/     
107     task release_key;
108         input [7:0]key_number;
109         begin
110             send_data(8'hf0);
111             #50000;
112             send_data(key_number);
113             #50000;
114         end
115     endtask
116
117 /*----任务:模拟一次短码的按下和释放操作-----*/    
118     task key_event;
119         input [7:0]key_number;
120         begin
121             press_key(key_number);
122             #30000;
123             release_key(key_number);
124         end
125     endtask
126
127 endmodule
仿真图: 
仿真结果如下图:
测试文件中发送的‘z’、‘x’、‘o’、‘p’、‘e’、‘n’等字母,在仿真图中显示,通过与ascii码表对应,得知是正确的。分配引脚,下板后,数码管也得到了与之对应的ascii码值。

爆料称Intel将推出特殊CPU Kaby Lake+AMD Radeon核显
阶梯式PK恒流式化成工艺对电池的SEI、电化学性能的影响
宁德时代曾毓群成香港首富 SpaceX星际飞船原型SN15成功着陆
工信部开展互联网应用适老化及无障碍改造专项行动
美制裁中兴的本质:大国博弈下最大的牺牲品
FPGA学习系列:25. PS2通信电路的设计
麻省大学阿默斯特分校开发了一种新型医学设备
电力系统通信网存在的主要问题有哪些
STM32处理器A/D转换输入电阻与采样时间的分析
地表水在线监测微型水质自动监测站
爱立信远程竞速赛车颠覆式电竞新体验
LED灯与白炽灯相比有什么优势
浅谈5G、边缘计算及其对政府机构的意义
便携式迷你音箱的续航时间长,适用于多种生活场景
iPhone 12s Pro Max外观渲染图和配置信息曝光
一分钟了解C++迭代器
国内LED行业龙头企业国星光电亮相AWE
科技新潮,4G路由器如何助你畅享网络顺畅
飞利浦电动剃须刀RQ311拆解教程
MAXQ610 16-Bit MAXQ® Micro