在嵌入式开发过程中,常用到的通讯方式之一就是SPI协议,SPI(Serial Peripheral Interface–串行外设接口)总线系统是一种同步串行外设接口,通常通过四根线即可实现通讯
自从PS2手柄被破解后,许多玩家喜欢用它作为遥控器做DIY小机器人,它使用的是SPI通信协议,且两个摇杆可以输出四路模拟量
接收器的接口定义为:
DI/DAT:手柄接收器发送给主机(本实验中为FPGA)的信号,此信号是一个8bit 的串行数据,同步传送于时钟的下降沿。
DO/CMD: 主机发送给手柄接收器的信号,信号是一个8bit 的串行数据,
同步传送于时钟的下降沿。
NC:空端口;
GND:电源地;
VDD: 接收器工作电源,电源范围3~5V;
CS/SEL :用于提供手柄触发信号。在通讯期间,处于低电平,即主机拉低CS信号线表明开始通讯;
CLK: 时钟信号,由主机发出,用于保持数据同步;
NC: 空端口;
ACK: 从手柄到主机的应答信号。此信号在每个8bits 数据发送的最后一个周期变低并且CS一直保持低电平,如果CS 信号不变低,约60 微秒PS 主机会试另一个外设。本次未使用ACK 端口。
信号的读取在时钟由高到低的变化过程中完成。通讯过程如图所示
其中CLK周期为12us,在通讯过程中,cs信号在一串数据(九个字节,每个8位)发送完毕后才会拉高,而不是每个字节都拉高,在CLK时钟的下降沿完成数据的读取和发送,通讯过程如下
**首先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
经过计算,每次读取数据时,CS片选信号拉低时间为1020us
每次拉低CS片选信号后,过6us时钟信号clk开始出现第一个下降沿,此时主机给接收器发送第一个byte的bit0,byte1=0x01=00000001,故bit0=1。如图所示,第一个byte期间一共八个clk的下降沿,读到的mosi的数据一次为bit0~bit7=10000000,即第一个byte为0x01,第二个byte期间读到的依次是01000010,即第二个byte为0x42,作用是请求数据
每读完一个byte,CLK信号拉高24us,然后读下一个byte,一共读9个byte,读完后拉高CS,结束本次通讯
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手柄的跟详细教程百度上可以从搜到