参考内容:Verilog硬件描述语言 西安电子科技大学 蔡觉平等主讲 https://www.bilibili.com/video/BV1zb411s7bY?p=21
王建飞《你好FPGA一本可以听的书》
蔡觉平《Verilog HDL数字集成电路设计原理与应用》
正点原子《开拓者FPGA开发指南》
https://www.cnblogs.com/liujinggang/p/9609739.html
SPI(Serial Peripheral Interface,串行外围设备接口),是Motorola公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输,广泛用于EEPROM、Flash、RTC(实时时钟)、ADC(数模转换器)、DSP(数字信号处理器)以及数字信号解码器上。SPI通信的速度很容易达到好几兆bps,所以可以用SPI总线传输一些未压缩的音频以及压缩的视频。
四根线的作用分别如下:
SCK(Serial Clock):SCK是串行时钟线,作用是Master向Slave传输时钟信号,控制数据交换的时机和速率;
MOSI(Master Out Slave in):在SPI Master上也被称为Tx-channel,作用是SPI主机给SPI从机发送数据;
CS/SS(Chip Select/Slave Select):作用是SPI Master选择与哪一个SPI Slave通信,低电平表示从机被选中(低电平有效);
MISO(Master In Slave Out):在SPI Master上也被称为Rx-channel,作用是SPI主机接收SPI从机传输过来的数据;
模式0:CPOL= 0,CPHA=0。SCK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
模式1:CPOL= 0,CPHA=1。SCK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
模式2:CPOL= 1,CPHA=0。SCK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
模式3:CPOL= 1,CPHA=1。SCK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
FPGA管脚配置
信号名 | 方向 | 管教 | 端口说明 |
sys_clk | input | E1 | 系统时钟50M |
sys_rst_n | input | M1 | 系统复位,低电平有效 |
led0 | output | F3 | |
led1 | output | F5 | |
key0 | input | G5 | |
key1 | input | L7 | |
key2 | input | J6 | |
spi_miso | output | D5 | SPI串行输入 |
spi_sck | input | C3 | SPI时钟 |
spi_cs | input | D6 | SPI片选信号 |
spi_mosi | input | D4 | SPI输出 |
I_tx_en是主机给从机发送数据的使能信号,当I_tx_en为1时主机才能给从机发送数据;
I_rx _en是主机从从机接收数据的使能信号,当I_rx_en为1时主机才能从从机接收数据;
O_tx_done是主机给从机发送数据完成的标志位,发送完成后会产生一个高脉冲;
O_rx_done是主机从从机接收数据完成的标志位,接收完成后会产生一个高脉冲;
data_recv是把从机接收回来的串行数据并行化以后的并行数据;
data_send是主机要发送的并行数据;
选用模式0的时序,下面是发送数据状态机。
状态0:SCK为0,MOSI为要发送的数据的最高位,即data_send[7]
状态1:SCK为1,MOSI保持不变
状态2:SCK为0,MOSI为要发送的数据的次高位,即data_send[6]
状态3:SCK为1,MOSI保持不变
状态4:SCK为0,MOSI为要发送的数据的下一位,即data_send[5]
状态5:SCK为1,MOSI保持不变
状态6:SCK为0,MOSI为要发送的数据的下一位,即data_send[4]
状态7:SCK为1,MOSI保持不变
状态8:SCK为0,MOSI为要发送的数据的下一位,即data_send[3]
状态9:SCK为1,MOSI保持不变
状态10:SCK为0,MOSI为要发送的数据的下一位,即data_send[2]
状态11:SCK为1,MOSI保持不变
状态12:SCK为0,MOSI为要发送的数据的下一位,即data_send[1]
状态13:SCK为1,MOSI保持不变
状态14:SCK为0,MOSI为要发送的数据的最低位,即data_send[0]
状态15:SCK为1,MOSI保持不变
一个字节数据发送完毕以后,产生一个发送完成标志位O_tx_done并把CS/SS信号拉高完成一次发送。
当FPGA通过SPI总线从QSPI Flash中接收一个字节(8-bit)的数据时,首先FPGA把CS/SS片选信号设置为0,表示准备开始接收数据,整个接收数据过程其实也可以分为16个状态,但是与发送过程不同的是,为了保证接收到的数据准确,必须在数据的正中间采样,也就是说模式0时序图中灰色实线的地方才是代码中锁存数据的地方,所以接收过程的每个状态执行的操作为:
状态0:SCK为0,不锁存MISO上的数据
状态1:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[7]
状态2:SCK为0,不锁存MISO上的数据
状态3:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[6]
状态4:SCK为0,不锁存MISO上的数据
状态5:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[5]
状态6:SCK为0,不锁存MISO上的数据
状态7:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[4]
状态8:SCK为0,不锁存MISO上的数据
状态9:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[3]
状态10:SCK为0,不锁存MISO上的数据
状态11:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[2]
状态12:SCK为0,不锁存MISO上的数据
状态13:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[1]
状态14:SCK为0,不锁存MISO上的数据
状态15:SCK为1,锁存MISO上的数据,即把MISO上的数据赋值给data_recv[0]
一个字节数据接收完毕以后,产生一个接收完成标志位O_rx_done并把CS/SS信号拉高完成一次数据的接收。
发送程序:
if(I_tx_en)begin
spi_cs <= 1'b0 ; // pull down the cs
case(R_tx_state)
4'd0:
begin
spi_mosi <= data_send[7];
spi_sck <= 1'd0;
R_tx_state <= R_tx_state + 1'd1;
end
4'd1:
begin
spi_sck <= 1'd1;
R_tx_state <= R_tx_state + 1'd1;
end
4'd2:
begin
spi_mosi <= data_send[6];
spi_sck <= 1'd0;
R_tx_state <= R_tx_state + 1'd1;
end
4'd3:
begin
spi_sck <= 1'd1;
R_tx_state <= R_tx_state + 1'd1;
end
4'd4:
begin
spi_mosi <= data_send[5];
spi_sck <= 1'd0;
R_tx_state <= R_tx_state + 1'd1;
end
4'd5:
begin
spi_sck <= 1'd1;
R_tx_state <= R_tx_state + 1'd1;
end
4'd6:
begin
spi_mosi <= data_send[4];
spi_sck <= 1'd0;
R_tx_state <= R_tx_state + 1'd1;
end
4'd7:
begin
spi_sck <= 1'd1;
R_tx_state <= R_tx_state + 1'd1;
end
4'd8:
begin
spi_mosi <= data_send[3];
spi_sck <= 1'd0;
R_tx_state <= R_tx_state + 1'd1;
end
4'd9:
begin
spi_sck <= 1'd1;
R_tx_state <= R_tx_state + 1'd1;
end
4'd10:
begin
spi_mosi <= data_send[2];
spi_sck <= 1'd0;
R_tx_state <= R_tx_state + 1'd1;
end
4'd11:
begin
spi_sck <= 1'd1;
R_tx_state <= R_tx_state + 1'd1;
end
4'd12:
begin
spi_mosi <= data_send[1];
spi_sck <= 1'd0;
R_tx_state <= R_tx_state + 1'd1;
end
4'd13:
begin
spi_sck <= 1'd1;
R_tx_state <= R_tx_state + 1'd1;
end
4'd14:
begin
spi_mosi <= data_send[0];
spi_sck <= 1'd0;
R_tx_state <= R_tx_state + 1'd1;
O_tx_done <= 1'd1;
end
4'd15:
begin
spi_sck <= 1'd1;
R_tx_state <= R_tx_state + 1'd1;
O_tx_done <= 1'd0;
end
default:;
endcase
接收程序:
else if(I_rx_en)begin
spi_cs <= 1'b0 ; // pull down the cs
case(R_rx_state)
4'd0:
begin
spi_sck <= 1'd0;
R_rx_state <= R_rx_state + 1'd1;
O_rx_done <= 1'd0;
end
4'd1:
begin
spi_sck <= 1'd1;
R_rx_state <= R_rx_state + 1'd1;
data_recv[7] <= spi_miso;
O_rx_done <= 1'd0;
end
4'd2:
begin
spi_sck <= 1'd0;
R_rx_state <= R_rx_state + 1'd1;
O_rx_done <= 1'd0;
end
4'd3:
begin
spi_sck <= 1'd1;
R_rx_state <= R_rx_state + 1'd1;
data_recv[6] <= spi_miso;
O_rx_done <= 1'd0;
end
4'd4:
begin
spi_sck <= 1'd0;
R_rx_state <= R_rx_state + 1'd1;
O_rx_done <= 1'd0;
end
4'd5:
begin
spi_sck <= 1'd1;
R_rx_state <= R_rx_state + 1'd1;
data_recv[5] <= spi_miso;
O_rx_done <= 1'd0;
end
4'd6:
begin
spi_sck <= 1'd0;
R_rx_state <= R_rx_state + 1'd1;
end
4'd7:
begin
spi_sck <= 1'd1;
R_rx_state <= R_rx_state + 1'd1;
data_recv[4] <= spi_miso;
end
4'd8:
begin
spi_sck <= 1'd0;
R_rx_state <= R_rx_state + 1'd1;
end
4'd9:
begin
spi_sck <= 1'd1;
R_rx_state <= R_rx_state + 1'd1;
data_recv[3] <= spi_miso;
end
4'd10:
begin
spi_sck <= 1'd0;
R_rx_state <= R_rx_state + 1'd1;
end
4'd11:
begin
spi_sck <= 1'd1;
R_rx_state <= R_rx_state + 1'd1;
data_recv[2] <= spi_miso;
end
4'd12:
begin
spi_sck <= 1'd0;
R_rx_state <= R_rx_state + 1'd1;
end
4'd13:
begin
spi_sck <= 1'd1;
R_rx_state <= R_rx_state + 1'd1;
data_recv[1] <= spi_miso;
end
4'd14:
begin
spi_sck <= 1'd0;
R_rx_state <= R_rx_state + 1'd1;
end
4'd15:
begin
spi_sck <= 1'd1;
R_rx_state <= R_rx_state + 1'd1;
data_recv[0] <= spi_miso;
O_rx_done <= 1'd1;
end
default:;
endcase
用sigaltap观察波形