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原理电路图如下:

FPGA

PS2协议总共由两根线组成,从电路原理图中也可以看出,需要控制的只有PS2_CLK和PS2_SDA,即一根时钟线和一根数据线。PS2设备中的时钟和数据都是集电极开路的,平时都是高电平。本设计将采用PS2接口的键盘称为从机,将控制和解码PS2协议的一方称为主机(FPGA)。PS2协议的时钟线始终由从机(即键盘)产生的,PS2协议发送一个字节数据共有11位。时序如下图所示:

FPGA

1Bit起始位,总是0;8Bit数据位,低位在前;1Bit校验位,奇校验;1Bit停止位,总是1。

从机(键盘)按照这个时序发送数据,主机(FPGA)只需要实现该协议的解码,即可将其中的8Bit数据位提取出来。根据时序图可以看出,数据在PS2时钟的下降沿是保持稳定的,主机只需在检测到PS2时钟出现下降沿时,去读取数据线上的电平,就可得到正确的数据。

通过上述的内容,已经知道了PS2从机到主机的通信协议,接下来就需要知道从机发送过来的每个字节代表什么?这时就要对照键盘编码表进行查看。键盘上一个按键由按下到释放时,键盘是按照如下的规定向主机发送数据的:

只要一个按键被按下,这个键的通码(MAKE)就会被发送到主机,按键一被释放断码BREAK)也会被发送如果按键被按下不释放的话,键盘会以一定的频率发送那个按键的通码。每个按键都有自己唯一的通码和断码,从而组成键盘编码表,表上的码值为16进制:

FPGA


例如,如果键盘上‘A’键被按下时,键盘就会向主机发送‘A’键对应的通码‘1C’,直到按键被释放。在按键被释放后,键盘将会向主机发送‘A’键的断码,即首先发送‘F0’,然后下一个字节发送‘1C’。通过观察键盘编码表,可以发现按键的通码与断码存在一定的联系,多数断码的第一个字节是‘F0’,第二个字节则是这个键的通码。

如果按下键盘上的扩展按键时,如‘END’,当‘END’键被按下后,键盘会首先向主机发送‘E0’,然后发送‘F0’,最后再发送‘69’。 根据上述的分析可知,在主机(FPGA)解码一次数据后,还需要对这个数据进行分析判断,判断该数据是否为断码标志‘F0’以及扩展码标志‘E0’。


设计架构图: 

本设计将实现在PS2键盘上按下26个字母任一个,在数码管上显示其对应的ASCII码。架构图如下:

FPGA

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);        

1 

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; //数码管段选

8 

9 

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);

1 

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;    //数码管段选

9 

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码值。

FPGA



打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分