1.概念
SPI协议,(Serial Peripheral Interface,串行外围设备接口),是一种同步传输的协议;所谓的同步,是指发送方和接收方使用的是同一个时钟;上一章写的uart的接受始终是自己产生的,两方没使用同一个时钟,因此是异步通信;SPI协议使用较为广泛,比较知名的就有flash;以及其他各种设备,据笔者所知,有一款热成像模块就是用的SPI协议;
2.SPI协议
SPI协议是分不同模式的。SPI通常有四根线,分别是CS(片选),spi_clk(spi时钟),mosi(主输出从输入)以及miso(主输入从输出),这里的M代表FPGA,S代表外围SPI设备例如Flash。
SPI的模式分为如下几种,介绍两个概念:CPOL和CPHA;CPOL叫做时钟极性,CPHA叫做时钟相位;当CPOL为0时,代表spi_clk在空闲状态为1低电平,CPOL为1时,代表spi_clk在空闲状态为1;CPHA表示当时钟来了,我在第几个边沿开始采集;CPHA为0,第一个边沿采集;CPHA为1,第二个边沿开始采集;这里以模式0举例(CPOL=0,CPHA=0),当处于空闲状态时,spi_clk为低,采集数据的边沿在时钟的第一个边沿,也就是上升沿;
当主从设备需要通信的时候,先拉底cs信号,发送spi_clk,并且伴随着时钟在mosi的数据线上发送数据;或者在接受数据的时候,产生spi_clk接收miso上的数据;顺便一提,spi协议通常都是高位先发送,因此在接收数据时可以使用左移的操作;
3.SPI驱动模块解析
输入输出端口定义如下:外部模块输入时钟以及复位;输入输出的SPI一组信号;用户接口有接受miso信号的o_user_data,以及发送数据的o_user_data;用户将需要发送的数据连接到这个端口,即可通过SPI发送数据;用户从o_user_read_data接受的数据就是miso传输过来的数据。
input i_clk ,
input i_rst ,
output o_spi_clk ,
output o_spi_cs ,
output o_spi_mosi ,
input i_spi_miso ,
input [P_DATA_WIDTH - 1 :0] i_user_data ,
input i_user_valid ,
output o_user_ready ,
output [P_READ_DATA_WIDTH - 1:0] o_user_read_data ,
output o_user_read_valid
4.部分代码解析
这里定义了一个运行信号,用于标志spi发送或者接收状态;以下是发送数据接受数据的代码段;
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_run <= 'd0;
else if(r_spi_cnt && r_cnt == 7)
r_run <= 'd0;
else if(w_user_active)
r_run <= 'd1;
else
r_run <= r_run;
end
这里定义了计数器用于计数已发送的数据;spi_clk在运行状态下为输入时钟的二分频,为了表明上升沿或者下降沿,做了一个spi_cnt,当spi_cnt == 1 时,表明为spi_clk的下降沿;spi_cnt == 0 时,代表spi_clk的上升沿;以下是发送数据接受数据的代码段;
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_cnt <= 'd0;
else if(r_spi_cnt && r_cnt == 7)
r_cnt <= 'd0;
else if(r_spi_cnt)
r_cnt <= r_cnt + 1;
else
r_cnt <= r_cnt;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_spi_cnt <= 'd0;
else if(r_run)
r_spi_cnt <= r_spi_cnt + 1;
else
r_spi_cnt <= 'd0;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_spi_mosi <= 'd0;
else if(w_user_active)
ro_spi_mosi <= i_user_data[P_DATA_WIDTH - 1];
else if(r_spi_cnt)
ro_spi_mosi <= r_user_data[P_DATA_WIDTH - 2];
else
ro_spi_mosi <= ro_spi_mosi;
end
always@(posedge ro_spi_clk,posedge i_rst)
begin
if(i_rst)
ro_user_read_data <= 'd0;
else
ro_user_read_data <= {ro_user_read_data[P_DATA_WIDTH - 2 : 0],i_spi_miso};
end
5.仿真模块
以下是仿真截图,在发发送数据期间,cs信号拉低,并且产生了spi_clk;这里发送的端口直接连接的55,也可以看到mosi的数据发送正确;tb文件做了数据回环,因此在read_valid有效期间可以看到接收到的数据也是55,该驱动模块仿真正确,上板使用ila抓取波形后也是正确的;需要注意的是,这里抓到的数据为ff,那是因为选择的管脚未连接外部信号,fpga默认的管脚都是高电平;ILA的使用可以在网上找到教程使用;
6.计划
下周可能更新Flash驱动器的代码,也可能更新DDR3的例程;