前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >源码系列:基于FPGA的PS2通信电路设计(附源码)

源码系列:基于FPGA的PS2通信电路设计(附源码)

作者头像
FPGA技术江湖
发布2020-12-30 14:33:27
6960
发布2020-12-30 14:33:27
举报
文章被收录于专栏:FPGA技术江湖
大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来基于FPGA的PS2通信电路设计,附源码,获取源码,请在“FPGA技术江湖”公众号内回复“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模块将相应的数据在数码管上显示。

设计代码

top顶层模块代码:

代码语言:javascript
复制
module top (clk, rst_n, ps2_sclk, ps2_sda, sel, seg);

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

  wire[7:0] out_data, tx_out;  
  
  /*****键盘扫描模块*****/
  ps2scan  ps2scan_inst(  
    .clk(clk),         
    .rst_n(rst_n),        
    .ps2_sclk(ps2_sclk),
    .ps2_sda(ps2_sda),
    .out_data(out_data)
  );

  /*****数据转ASCII码模块*****/
  ASCII  ASCII_inst(       
    .out_data(out_data),
    .tx_out(tx_out) 
  );
  
  /*****数码管显示模块*****/
  seg_num  seg_num_inst( 
    .num(tx_out),
    .clk(clk),
    .rst_n(rst_n),
    .sel(sel),
    .seg(seg) 
  );  
  
endmodule

ps2scan设计模块代码:

代码语言:javascript
复制
module ps2scan (clk, rst_n, ps2_sclk, ps2_sda, out_data);  

  //端口信号:模块的输入输出接口
  input clk;//系统时钟
  input rst_n;//低电平复位
  input  ps2_sclk;//ps2时钟信号(ps2设备自动产生,大约10KHz左右)
  input ps2_sda;//ps2数据信号
  output reg [7:0] out_data; //键值数据(采集ps2的一帧信号中间的8位有效位)
  
  //在发送时序中,数据在ps2_sclk的下降沿采集信号,以下为检测下降沿
  reg  in1,in2;  
  wire en;  
  
  always @ (posedge clk or negedge rst_n)
  begin
    if(!rst_n) 
      begin
        in1 <= 1'b0;
        in2 <= 1'b0;
      end
    else 
      begin                
        in1 <= ps2_sclk;
        in2 <= in1;
      end
  end

  assign en = (~in1) & in2;  //当出现ps2_sclk下降沿后拉高一个时钟,以进行数据的采集


  //采集ps2的一帧信号中间的8位有效位:每检测一个下降沿算一位数据位,在中间8位采集有效数据
  reg[7:0] temp_data;      
  reg[3:0] num;  
  
  always @ (posedge clk or negedge rst_n)
  begin
    if(!rst_n) 
      begin
        num <= 4'd0;
        temp_data <= 8'd0;
      end
    else if(en) 
      case (num)
        4'd0:  num <= num+1'b1;    //开始位
        4'd1:  begin
              num <= num+1'b1;
              temp_data[0] <= ps2_sda;  //bit0
            end
        4'd2:  begin
              num <= num+1'b1;
              temp_data[1] <= ps2_sda;  //bit1
            end
        4'd3:  begin
              num <= num+1'b1;
              temp_data[2] <= ps2_sda;  //bit2
            end
        4'd4:  begin
              num <= num+1'b1;
              temp_data[3] <= ps2_sda;  //bit3
            end
        4'd5:  begin
              num <= num+1'b1;
              temp_data[4] <= ps2_sda;  //bit4
            end
        4'd6:  begin
              num <= num+1'b1;
              temp_data[5] <= ps2_sda;  //bit5
            end
        4'd7:  begin
              num <= num+1'b1;
              temp_data[6] <= ps2_sda;  //bit6
            end
        4'd8:  begin
              num <= num+1'b1;
              temp_data[7] <= ps2_sda;  //bit7
            end
        4'd9:  num <= num+1'b1;  //结束位
        4'd10:num <= 4'd0;  
        default: ;
      endcase  
  end
  
  //判断是否有键按下:根据通码、断码的特性判断
  reg key;  
  
  always @ (posedge clk or negedge rst_n)
  begin
    if(!rst_n)
      key <= 1'b0;
    else if(num==4'd10)
      begin  
        if(temp_data == 8'hf0) 
            key <= 1'b1;
        else 
          begin
            if(!key) 
              out_data <= temp_data;  
            else 
              key <= 1'b0;
          end
      end
  end 
  
endmodule

ASCII模块代码:

代码语言:javascript
复制
module ASCII( out_data, tx_out);
  
  //端口信号:模块的输入输出接口
  input [7:0] out_data;   //键盘的扫描键值
  output reg [7:0] tx_out;//通过ASCII码转换之后的值
  
  //通过查找表的方式,对照ASCII码将键值转换为二进制数值
  always@(*) 
    case (out_data)    
      8'h1c: tx_out <= 8'h41;  //A
      8'h32: tx_out <= 8'h42;  //B
      8'h21: tx_out <= 8'h43;  //C
      8'h23: tx_out <= 8'h44;  //D
      8'h24: tx_out <= 8'h45;  //E
      8'h2b: tx_out <= 8'h46;  //F
      8'h34: tx_out <= 8'h47;  //G
      8'h33: tx_out <= 8'h48;  //H
      8'h43: tx_out <= 8'h49;  //I
      8'h3b: tx_out <= 8'h4a;  //J
      8'h42: tx_out <= 8'h4b;  //K
      8'h4b: tx_out <= 8'h4c;  //L
      8'h3a: tx_out <= 8'h4d;  //M
      8'h31: tx_out <= 8'h4e;  //N  
      8'h44: tx_out <= 8'h4f;  //O
      8'h4d: tx_out <= 8'h50;  //P
      8'h15: tx_out <= 8'h51;  //Q
      8'h2d: tx_out <= 8'h52;  //R
      8'h1b: tx_out <= 8'h53;  //S
      8'h2c: tx_out <= 8'h54;  //T
      8'h3c: tx_out <= 8'h55;  //U
      8'h2a: tx_out <= 8'h56;  //V
      8'h1d: tx_out <= 8'h57;  //W
      8'h22: tx_out <= 8'h58;  //X
      8'h35: tx_out <= 8'h59;  //Y
      8'h1a: tx_out <= 8'h5a;  //Z
      default: tx_out <= 8'h00;
    endcase
  
endmodule 

seg_num模块代码:

代码语言:javascript
复制
module seg_num (clk, rst_n, num, sel, seg);        

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


  //计数分频,通过选择cnt的相应位的变化来大致分频  
  reg [23:0]  cnt;
  wire        clk_r;
  
  always@(posedge clk or negedge rst_n)
  begin
    if(!rst_n)
      cnt <= 24'd0;
    else
      cnt <= cnt + 1'b1;
  end 
  
  assign clk_r = cnt[15] ; //通过计数cnt的第10位来分频计数,2^10/50M
  //通过查找表的方式将数据与相应的数码管显示一一对应  
  reg [3:0] data;
  
  always@(*)
    case(data)
      4'h0:  seg <= 8'hC0; //8'b1100_0000
      4'h1:  seg <= 8'hF9;  //8'b1111_1001
      4'h2:   seg <= 8'hA4;  //8'b1010_0100  
      4'h3:  seg <= 8'hB0;  //8'b1011_0000
      4'h4:  seg <= 8'h99;  //8'b1001_1001
      4'h5:  seg <= 8'h92;  //8'b1001_0010
      4'h6:  seg <= 8'h82;  //8'b1000_0010
      4'h7:  seg <= 8'hF8;  //8'b1111_1000
      4'h8:  seg <= 8'h80;  //8'b1000_0000
      4'h9:  seg <= 8'h90;  //8'b1001_0000
      4'hA:  seg <= 8'h88;
      4'hB:  seg <= 8'h83;
      4'hC:  seg <= 8'hC6;
      4'hD:  seg <= 8'hA1;
      4'hE:  seg <= 8'h86;
      4'hF:  seg <= 8'h8E;
      default:seg <= 8'hFF; //8'b1111_1111
    endcase

  //通过查找表的方式,在不同位选下,显示数据的相应位
  always@(*)
  begin
    case(sel)
      000:  data <= num[7:4]; 
      001:  data <= num[3:0];
      default:;
    endcase
  end
    
  always@(posedge clk_r or  negedge rst_n) 
  begin
    if(!rst_n)
      sel <= 3'd0;
    else  if(sel == 3'd1)
      sel <= 3'd0;  
    else
      sel <= sel + 1'b1;
  end 
  
endmodule

仿真测试

仿真测试模块top_tb代码如下:

代码语言:javascript
复制
`timescale 1ns/1ns

module top_tb;

  reg clk;
  reg rst_n;
  reg ps2_sda;
  reg ps2_sclk;
  wire [7:0] seg;
  wire [2:0] sel;
  
  top top_dut(
    .clk(clk),
    .rst_n(rst_n),
    .ps2_sclk(ps2_sclk),
    .ps2_sda(ps2_sda),
    .sel(sel),
    .seg(seg)
  );

  initial begin
    clk = 1;
    rst_n = 0;
    ps2_sda = 1;
    ps2_sclk = 1;
    
    #200;
    rst_n = 1;
    Key_Event(8'h1A);  /* Z */
    #40000;
    Key_Event(8'h22);  /* X */
    #80000;
    Key_Event(8'h44);  /* O */
    #13200;
    Key_Event(8'h4D);  /* P */
    #25600;
    Key_Event(8'h24);  /* E */
    #12300;
    Key_Event(8'h31);  /* N */
    
    #2000000;
    $stop;
  end  
  
/*---------生成工作时钟-----------*/
  always #10 clk = ~clk;
  
/*----任务:以PS2协议发送一个字节的数据-----*/
  task Send_data;
    input [7:0]data;
    begin
           ps2_sda = 0;  /*发送起始位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = data[0];/*发送第0位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = data[1];/*发送第1位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = data[2];/*发送第2位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = data[3];/*发送第3位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = data[4];/*发送第4位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = data[5];/*发送第5位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = data[6];/*发送第6位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = data[7];/*发送第7位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = 0;/*暂时忽略校验位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;
      
      #20000;ps2_sda = 1;/*停止位*/
      #20000;ps2_sclk = 0;
      #40000;ps2_sclk = 1;      
    end
  endtask

/*-----任务:模拟按下按键的操作------*/  
  task press_key;
    input [7:0]Key_Number;
    begin
      Send_data(Key_Number);
      #50000;    
    end
  endtask
  
/*-----任务:模拟释放按键的操作------*/    
  task release_key;
    input [7:0]Key_Number;
    begin
      Send_data(8'hF0);
      #50000;
      Send_data(Key_Number);
      #50000;
    end
  endtask

/*----任务:模拟一次短码的按下和释放操作-----*/  
  task Key_Event;
    input [7:0]Key_Number;
    begin
      press_key(Key_Number);
      #30000;
      release_key(Key_Number);
    end
  endtask

endmodule 

仿真图如下:

测试文件中发送的‘Z’、‘X’、‘O’、‘P’、‘E’、‘N’等字母,在仿真图中显示,通过与ASCII码表对应,得知是正确的。分配引脚,下板后,数码管也得到了与之对应的ASCII码值。

END

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FPGA技术江湖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档