FPGA 解析PS2游戏手柄

FPGA开发之解析SONY-PS2手柄SPI协议

在嵌入式开发过程中,常用到的通讯方式之一就是SPI协议,SPI(Serial Peripheral Interface–串行外设接口)总线系统是一种同步串行外设接口,通常通过四根线即可实现通讯

  • 使用对象:PS2游戏手柄
  • 使用环境:ISE14.7和BASYS2开发板

1.PS2游戏手柄及其协议介绍

自从PS2手柄被破解后,许多玩家喜欢用它作为遥控器做DIY小机器人,它使用的是SPI通信协议,且两个摇杆可以输出四路模拟量
FPGA 解析PS2游戏手柄_第1张图片
接收器的接口定义为:
FPGA 解析PS2游戏手柄_第2张图片

DI/DAT:手柄接收器发送给主机(本实验中为FPGA)的信号,此信号是一个8bit 的串行数据,同步传送于时钟的下降沿。
DO/CMD: 主机发送给手柄接收器的信号,信号是一个8bit 的串行数据,
同步传送于时钟的下降沿。
NC:空端口;
GND:电源地;
VDD: 接收器工作电源,电源范围3~5V;
CS/SEL :用于提供手柄触发信号。在通讯期间,处于低电平,即主机拉低CS信号线表明开始通讯;
CLK: 时钟信号,由主机发出,用于保持数据同步;
NC: 空端口;
ACK: 从手柄到主机的应答信号。此信号在每个8bits 数据发送的最后一个周期变低并且CS一直保持低电平,如果CS 信号不变低,约60 微秒PS 主机会试另一个外设。本次未使用ACK 端口。

信号的读取在时钟由高到低的变化过程中完成。通讯过程如图所示
FPGA 解析PS2游戏手柄_第3张图片
其中CLK周期为12us,在通讯过程中,cs信号在一串数据(九个字节,每个8位)发送完毕后才会拉高,而不是每个字节都拉高,在CLK时钟的下降沿完成数据的读取和发送,通讯过程如下
FPGA 解析PS2游戏手柄_第4张图片

**首先FPGA拉低CS片选信号线,然后在每个CLK的下降沿读一个bit,每读八个bit(即一个byte)CLK拉高一小段时间,一共读九组bit。第一个byte是FPGA发给接收器命令“0X01”,手臂会在第二个byte回复它的ID,0x41=绿灯模式,0x73=红灯模式(摇杆有模拟量),同时第二个byte时主机发给接收器一个0x42请求数据,第三个byte接收器会给主机发送0x5A告诉主机数据来了,从第四个byte开始全是接收器给主机发送数据,每个byte定义如上图,当有按键按下时对应位为0。例如当LEFT按下时,byte3=01111111;
注意,发送数据的顺序是从低位bit0到高位bit7

2.SPI通讯波形

经过计算,每次读取数据时,CS片选信号拉低时间为1020us
FPGA 解析PS2游戏手柄_第5张图片

每次拉低CS片选信号后,过6us时钟信号clk开始出现第一个下降沿,此时主机给接收器发送第一个byte的bit0,byte1=0x01=00000001,故bit0=1。如图所示,第一个byte期间一共八个clk的下降沿,读到的mosi的数据一次为bit0~bit7=10000000,即第一个byte为0x01,第二个byte期间读到的依次是01000010,即第二个byte为0x42,作用是请求数据
FPGA 解析PS2游戏手柄_第6张图片

每读完一个byte,CLK信号拉高24us,然后读下一个byte,一共读9个byte,读完后拉高CS,结束本次通讯
FPGA 解析PS2游戏手柄_第7张图片

Verilog代码实现

1.首先定义两个时钟,周期分别为6us和1020us,计算方法在我的上一篇博客讲过

always @(posedge clk)   //定义两个时钟,周期分别为6us和1020us
begin
        if(cnt_clk_6us == 10'b00_1001_0110-1) begin
            cnt_clk_6us <= 0;
            clk_6us <= ~clk_6us;   //按位取反
        end
        else
            cnt_clk_6us <= cnt_clk_6us + 1;


            if(cnt_1020us == 16'b0110_0011_1001_1100-1) begin    //  110 0011 1001 1100  周期1020us
            cnt_1020us <= 0;
            clk_1020us <= ~clk_1020us;   //按位取反
        end
        else
            cnt_1020us <= cnt_1020us + 1;
end

每10ms读取一次数据,定义一个reg trig作为一个中间变量,比CS信号提前6us,波形完全一样

always @( posedge clk_1020us)
begin
count_for_trig<=count_for_trig+1;
    if (count_for_trig==4'b0001)   
    trig<=0;
    else if (count_for_trig==4'b0010)   //trig拉低1020us
    trig<=1;
    else    if (count_for_trig==4'b1010)
    count_for_trig<=4'b0000;
end

trig作为处罚,引起CS和MOSI改变(MOSI为主机输出端口),定义trig的作用是是cs和mosi同时作用,因为always里是非阻塞赋值,如果检测到cs拉低再拉高mosi,mosi会慢6us

reg [7:0] data_in1;
reg [7:0] count_trig=0;
always @(negedge clk_6us)
begin
    if (trig==0)   
    begin
    scs<=0;    
    count_trig<=count_trig+1;
    //////////spi_clk波形产生////////////
            if ((017))   //byte1
            sclk<=~sclk;
            else if ((1936))  //byte2
            sclk<=~sclk;
            else if ((3855))  //byte3
            sclk<=~sclk;
            else if ((5774))  //byte4
            sclk<=~sclk;
            else if ((7693))  //byte5
            sclk<=~sclk;
            else if ((95112))  //byte6
            sclk<=~sclk;
            else if ((114131))  //byte7
            sclk<=~sclk;
            else if ((133150))  //byte8
            sclk<=~sclk;
            else if ((152169))  //byte9
            sclk<=~sclk;

    ///////////////////mosi波形产生///////////////////
    //通过波形图可以看出莫斯一共拉高了三次,每次持续12us,因为只需要发送一个0x01和0x42
    if (count_trig<2)
        smosi<=1;
    else if ((2023))
        smosi<=1;
    else if ((3033))
        smosi<=1;
    else 
        smosi<=0;


    //////////////读取miso////////////////////



        if (count_trig==58)    //读byte4
        data_in1[0]<=spi_miso;
    else if (count_trig==60)
        data_in1[1]<=spi_miso;
    else if (count_trig==62)
        data_in1[2]<=spi_miso;
    else if (count_trig==64)
        data_in1[3]<=spi_miso;
    else if (count_trig==66)
        data_in1[4]<=spi_miso;
    else if (count_trig==68)
        data_in1[5]<=spi_miso;
    else if (count_trig==70)
        data_in1[6]<=spi_miso;
    else if (count_trig==72)
        data_in1[7]<=spi_miso;  
    end
    else if(trig==1)   //不通讯时(即trig=1时)cs信号拉高,clk信号拉高
        begin
        scs<=1;
        sclk<=1;
        count_trig<=0;
        end
end

这样miso传过来的第四个字节就放到data_in1这个八位寄存器里了,后续的其他字节的读取以此类推,关于PS2手柄的跟详细教程百度上可以从搜到

你可能感兴趣的:(FPGA)