串行外设接口(Serial Peripheral Interface, SPI)
目录
- 1. 功能介绍
- 1.1. 全双工 串行 同步
- 波特率
- 只能全双工
- 1.2. 工作模式
- Master/Slave Select Register(MSTR)
- Clock Polarity(CPOL)与 Clock Phase(CPHA)
- CPHA的意义
- 1.3. SPI与UART的区别
- 2. 架构
- 2.1. spi
- 2.2. baud_clk_gen
- 3. 逻辑设计
- 3.1. spi_master
- 3.2. spi_slave
- 3.3. spi
- 3.4. spi_tb
- 4. 测试
- 4.1. spi_master与spi_slave1、spi_slave2的pingpong测试
《SPI Block Guide V04.01, Motorola, Inc》
SPI原理超详细讲解—值得一看
arduino教程-9. 串行外设接口(spi)
SPI通信总线原理及工作过程
《FPGA Verilog开发实战指南——基于Altera EP4CE10》
1. 功能介绍
串行外设接口(Serial Peripheral Interface, SPI)协议是Motorola提出的一种全双工、串行、同步通信协议,广泛用于 电可擦编程只读存储器(Electrically Erasable Programmable Read-Only Memory,EEPROM)、Flash、实时时钟(Real Time Clock,RTC)、数模转换器ADC、数字信号处理器DSP以及数字信号解码器上。
相比于UART,SPI的速率较快,但是缺陷与UART相同——没有握手机制确认数据是否接受,故数据可靠性存在缺陷
下面细讲
1.1. 全双工 串行 同步
可以通过介绍想一下SPI的信号啊,全双工串行说明可以同时单bit读写,所以一个SPI模块一定有两根线用于发送、接受。
而同步则说明SPI模块间通信是在同一个时钟域下,所以还有一个时钟信号。
如下图
其中SCK是同步时钟,MOSI(Master Output Slave Input)和MISO(Master Input Slave Output)是全双工串行总线,CSn则用于对Slave使能、低电平有效。而Master和Slave则表示主机和从机。
波特率
SPI速度比UART快就体现在波特率上,UART是在异步时钟常用的115.2kbps嘛最高可达3Mbps,而SPI由于工作在同步时钟的波特率从12.21kHZ~12.5MHZ不等。
别忘了SPI是同步串口,所以波特率是通过通过同步时钟保证的。
常用UART速率就是115200HZ,SPI则是3MHZ,I2C就是1MHZ左右。
而且UART数据位只能是5~7 bit,而SPI则可以任意
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)
别忘了时钟频率/波特率 = 每bit变化的时钟周期数,对于SPI来说就是SCK每周期的时钟周期数
只能全双工
注意SPI是只能全双工工作,不存在半双工工作模式。
UART也是全双工,即发送的同时可以接收。但是呢UART也可以变成半双工,即发送的同时不接收,接收的时候可以不发送。
而SPI只能全双工的意思是,发送的同时必须接收,要么就既不发送也不接收,为什么呢?
这个与SPI的发送接收机制有关,实际上SPI的发送和接收是基于一个移位寄存器实现的。
如下图Master移位寄存器和Slave移位寄存器均在SCK时钟域下
工作过程是这样的:
● Master将/SS拉低开启与Slave通信
● Master和Slave各自将缓存Memory数据放入各自的移位寄存器中
● SCK每一拍将Master移位寄存器的一位通过MOSI传送至Slave移位寄存器的中,Slave移位寄存器的一位通过MISO传送至Master移位寄存器的中
● 经过8个SCK周期,实现了Master与Slave的数据交换。
本质上是Master通过/SS控制对Slave的读和写。
所以说Master只写不读,就不用采样交换之后的寄存器值。只读不写,就可以给Slave发空数据,然后采样交换之后的寄存器值。当然也可以又读又写。
1.2. 工作模式
之前的功能除了同步时钟,其他的与UART没什么区别,但是SPI可以配置成多种工作模式
Master/Slave Select Register(MSTR)
MSTR是SPI control register 1 的某一位,用于确定该SPI是Master还是Slave,也就是说SPI具有一主多从工作模式
一个Master SPI 可连接多个Slave SPI,Master通过片选信号线选择与哪几个Slave建立通信,如下图
而I2C、EMIF则是通过地址选择相应的Slave
注意MSTR决定了是MASTER SPI还是SLAVE SPI,如果是MASTER那么SCK、MOSI、CSn是输出,MISO是输入,如果是SLAVE那么SCK、MOSI、CSn是输入,MISO则是输出
片选不仅仅是选择了哪个Slave进行通信,片选信号拉低也是与该Slave通信开始的标志。
Clock Polarity(CPOL)与 Clock Phase(CPHA)
CPOL与CPHA都属于SPI control register 1,均为1bit,分别用于确定该SPI 作为Master时空闲(与任何Slave都没有通信)的SCK电平和通信时对输入信号采样的SCK沿、对输出信号驱动的SCK沿
此处的采样是指将输入采样至移位寄存器,驱动是指将移位寄存器的值驱动给输出。
换句话说,CPHA决定的是 对输入采样并驱动给移位寄存器的SCK边沿 和 对移位寄存器采样并驱动给输出的SCK边沿
如下表
工作方式 | CPOL | CPHA | MSTR | 说明 |
---|---|---|---|---|
SP0 | 1'b0 | 1'b0 | 1'b0 | 作为Master,空闲时SCK电平为低,上升沿MISO采样,下降沿MOSI驱动 |
1'b1 | 作为Slave,空闲时SCK电平为低,上升沿MOSI采样,下降沿MISO驱动 | |||
SP1 | 1'b0 | 1'b1 | 1'b0 | 作为Master,空闲时SCK电平为低,下降沿MISO采样,上升沿MOSI驱动 |
1'b1 | 作为Slave,空闲时SCK电平为低,下降沿MOSI采样,上升沿MISO驱动 | |||
SP2 | 1'b1 | 1'b0 | 1'b0 | 作为Master,空闲时SCK电平为高,下降沿MISO采样,上升沿MOSI驱动 |
1'b1 | 作为Slave,空闲时SCK电平为高,下降沿MOSI采样,上升沿MISO驱动 | |||
SP3 | 1'b1 | 1'b1 | 1'b0 | 作为Master,空闲时SCK电平为高,上升沿MOSI采样,下降沿MISO驱动 |
1'b1 | 作为Slave,空闲时SCK电平为高,上升沿MISO采样,下降沿MOSI驱动 |
假设SPI输入输出均为1bit,如下图所示,红色虚线表示SCK采样的边沿、绿色虚线表示SCK驱动的边沿
CPOL和CPHA需要MASTER SPI和SLAVE SPI统一
注意虽然输入输出都是1bit,但由于采样输入和驱动输出不在一个沿,所以移位寄存器得是2bit
实际上常用的工作模式是SP0和SP3,发送的数据则默认只是有效数据位,可配置为LSB发送或MSB发送
CPHA的意义
这里讲一下CPHA搞个采样驱动不同边沿有啥用。
如果使用的单沿采样驱动,此时采样输入和驱动输出在同一拍故移位寄存器就是1bit,那么从采样输入到移位寄存器、从移位寄存器到输出也是2拍,从速度上来说这个与CPHA模式相同。
实际上,CPHA模式的意义就是为了保证在信号中间时刻采样,以得到稳定的信号,利用SCK周期长的特点,让SCK的驱动沿和采样沿不是同一个。
我也一直在想同步时钟的话,SCK、MOSI和MISO的远距离传输必然会有信号偏斜的弊端。
UART是在baud_cnt为中间值时采样,与SPI在SCK中间沿采样异曲同工
如下图若在绿线处采样,那么就会采样到1,但由于延迟Slave那边的的MOSI.CK和MOSI.D会比Master这边的MOSI.CK和MOSI.Q晚一些。
但是Slave.MOSI.CK和Slave.MOSI.D慢的速度可能不同,会导致延迟之后红线处采样到0。但如果使用下降沿采样就会采样到1。
再说细一点,直接上那个熟悉的公式:
TsetupSI<TSCK+TSCK2SI−(TSCK2MO+TCK2QMO+TMO2SI)(a)T^{SI}_{setup}<T_{SCK}+T_{SCK2SI}-(T_{SCK2MO}+T^{MO}_{CK2Q}+T_{MO2SI}) \tag{a}TsetupSI<TSCK+TSCK2SI−(TSCK2MO+TCK2QMO+TMO2SI)(a)
TholdSI<TSCK2MO+TCK2QMO+TMO2SI−TSCK2SI(b)T^{SI}_{hold}<T_{SCK2MO}+T^{MO}_{CK2Q}+T_{MO2SI}-T_{SCK2SI} \tag{b}TholdSI<TSCK2MO+TCK2QMO+TMO2SI−TSCK2SI(b)
首先TsetupSIT^{SI}_{setup}TsetupSI一般可以满足,因为TSCKT_{SCK}TSCK比较大,而且其他量相比于TSCKT_{SCK}TSCK小很多。
但是TholdSIT^{SI}_{hold}TholdSI则不好说,如果SCK走线时间和MOSI走线时间差距比较大的话,可能会违背。但如果对MO/CK和SI/CK在SCK不同沿的话,(b)(b)(b)式就会在左侧多一个12TSCK\frac{1}{2}T_{SCK}21TSCK,就可以保证。
可以算一下验证,触发器建立时间约为5ns,保持时间约为25ns,12.5MHZ最快的SPI周期是160ns
最后一个问题,那么为什么平时的设计没怎么见到这种双沿驱动呢?因为成本,你看双沿模式是不是比单沿模式多一个触发器?
1.3. SPI与UART的区别
上述是SPI本身的特性,但如果SPI要与外部模块交互呢?Master肯定有一个外部的用户时钟clk输入,用于传入要发送的数据并根据波特率产生SCK对吧。
但是Slave也需要与外部模块进行数据交互,所以Slave也需要有一个外部的clk输入。这样的话一定会涉及到SCK时钟域到clk时钟域的相互同步,这样的话是不是有一点点像UART?
UART不就是串行全双工么?然后tx和rx是通过波特率保证的。
而上述的SLAVE SPI 加上单bit同步,其本质也是通过波特率保证MOSI和MISO的交互
那么这样的SPI与UART的区别在哪里?
● 全双工:SPI发送接收必须同时完成,而UART可以发送接收分离
● 跨时钟域: SPI涉及到clk域与SCK域的相互同步问题,UART涉及到txd到rxd的跨时钟域问题
● 波特率实现: SPI是通过clk分频成SCK保证波特率的,而UART是通过clk计数器保证SCK波特率
● 工作模式:SPI具备一主多从工作,UART则是点对点
SPI基于SCK不同沿采样和驱动,UART则是基于波特率计数器采样和驱动。
2. 架构
由于SPI根据不同的配置产生不同的工作模式,所以SPI实现就需要根据parameter值用generate生成不同的代码。
根据谁呢?根据MSTR判断是生成spi_master还是spi_slave,如下图
注意顶层的spi使用了APB接口
2.1. spi
Signal | Direction | Width(bits) | Description |
---|---|---|---|
prstn | input | 1 | 复位信号 |
pclk | input | 1 | SPI的用户时钟 |
paddr | input | PADDR_WIDTH | 用于访问spi内部FIFO |
pwrite | input | 1 | 1表示写,0表示读 |
psel | input | 1 | 是否对spi选通 |
penable | input | 1 | APB使能 |
pwdata | input | PDATA_WIDTH | 写数据 |
prdata | input | PDATA_WIDTH | 读出的数据 |
pready | output | 1 | usart准备标志 |
sck | inout | 1 | 波特率时钟,Master SPI为output、Slave SPI为input |
mosi | inout | 1 | SPI单bit通信端口,Master SPI为output、Slave SPI为input |
miso | inout | 1 | SPI单bit通信端口,Master SPI为input、Slave SPI为output |
csn_i | input | CHIP_SEL_NUM | 对Master/Slave SPI片选的控制信号 |
csn_o | output | CHIP_SEL_NUM | Master SPI对Slave SPI的片选控制 |
之后是参数描述
Parameter | Units | Description |
---|---|---|
BAUD_RATE | bit per second | 设定的波特率 |
PCLK_FREQ | HZ | clk的时钟频率 |
PADDR_WIDTH | bit | 访问SPI内部FIFO的地址位宽 |
PDATA_WIDTH | bit | 写入or读出的数据位宽 |
CHIP_SEL_NUM | bit | Master SPI可片选Slave SPI的个数 |
MSTR | bit | 0表示Master SPI、1表示Slave SPI |
CPOL | bit | 表示sck空闲时的电平 |
CPHA | bit | 用于确定sck对单bit输入采样沿和单bit输出的驱动沿 |
ASYNC_FIFO_WIDTH | bit | 可选,异步FIFO深度 |
2.2. baud_clk_gen
用于产生波特率时钟sck
Signal | Direction | Width(bits) | Description |
---|---|---|---|
rstn | input | 1 | 复位信号 |
clk | input | 1 | SPI的用户时钟 |
sck | output | 1 | 波特率时钟 |
之后是参数描述
Parameter | Units | Description |
---|---|---|
BAUD_RATE | bit per second | 设定的波特率 |
CLK_FREQ | HZ | clk的时钟频率 |
CPOL | bit | 表示sck空闲时的电平 |
3. 逻辑设计
3.1. spi_master
串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_master
3.2. spi_slave
串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_slave
3.3. spi
整个顶层模块如下
module spi#(parameter BAUD_RATE = 12500000,parameter PCLK_FREQ = 50000000,parameter PADDR_WIDTH = 32,parameter PDATA_WIDTH = 32,parameter CHIP_SEL_NUM = 3,parameter MSTR = 0,parameter CPOL = 0,parameter CPHA = 0,parameter ASYNC_FIFO_WIDTH = 4096)(input prstn,input pclk,input [PADDR_WIDTH-1:0] paddr,input pwrite,input psel,input penable,input [PDATA_WIDTH-1:0] pwdata,output [PDATA_WIDTH-1:0] prdata,output pready,inout sck,inout mosi,inout miso,input [CHIP_SEL_NUM-1:0] csn_i,output [CHIP_SEL_NUM-1:0] csn_o);localparam TX_DATA_ADDR = 32'h0000_0000_0000_1000;
localparam RX_DATA_ADDR = TX_DATA_ADDR + 32'h4;
localparam CSN_I_ADDR = TX_DATA_ADDR + 32'h8;reg [PDATA_WIDTH-1:0] tx_data;
reg tx_data_val;
wire tx_fifo_full;
reg rx_req;
wire [PDATA_WIDTH-1:0] rx_data;
wire rx_data_val;
wire rx_fifo_empty;
reg [PDATA_WIDTH-1:0] prdata_r;
reg pready_r;always@(*) beginif(psel && penable && pwrite && paddr == TX_DATA_ADDR) begintx_data = pwdata;tx_data_val = 1'b1;endelse begintx_data = 'd0;tx_data_val = 1'b0;end
endalways@(*) beginif(psel && penable && !pwrite && paddr == RX_DATA_ADDR) prdata_r = rx_data;else if(psel && penable && !pwrite && paddr == TX_DATA_ADDR)prdata_r = tx_data;else if(psel && penable && !pwrite && paddr == CSN_I_ADDR)prdata_r = csn_i;elseprdata_r = 'd0;
endassign prdata = prdata_r;always@(posedge pclk or negedge prstn) beginif(!prstn)rx_req <= 1'b0;else if(psel && !pwrite && paddr == RX_DATA_ADDR) beginif(!penable)rx_req <= 1'b1;else if(rx_fifo_empty)rx_req <= 1'b1;elserx_req <= 1'b0;endelserx_req <= 1'b0;
endalways@(*) beginif(psel && penable && pwrite && paddr == TX_DATA_ADDR) pready_r = !tx_fifo_full;else if(psel && penable && !pwrite && paddr == RX_DATA_ADDR) pready_r = rx_data_val;else if(psel && penable && pwrite && paddr == CSN_I_ADDR) pready_r = 1'b1;elsepready_r = 1'b0;
endassign pready = pready_r;generate if(!MSTR) beginreg [CHIP_SEL_NUM-1:0] csn_i_r;always@(posedge pclk or negedge prstn) beginif(!prstn)csn_i_r <= {CHIP_SEL_NUM{1'b1}};else if(psel && (!penable) && pwrite && paddr == CSN_I_ADDR) csn_i_r <= pwdata;
endbaud_clk_gen#(.BAUD_RATE (BAUD_RATE ),.CPOL (CPOL ),.CLK_FREQ (PCLK_FREQ ))u_baud_clk_gen(.rstn (prstn ),.clk (pclk ),.sck (sck ));spi_master#(.CHIP_SEL_NUM (CHIP_SEL_NUM ),.DATA_WIDTH (PDATA_WIDTH ),.ASYNC_FIFO_WIDTH (ASYNC_FIFO_WIDTH ),.CPOL (CPOL ),.CPHA (CPHA ))u_spi_master(.rstn (prstn ),.clk (pclk ),.tx_data (tx_data ),.tx_data_val (tx_data_val ),.tx_fifo_full (tx_fifo_full ),.rx_req (rx_req ),.rx_data (rx_data ),.rx_data_val (rx_data_val ),.rx_fifo_empty (rx_fifo_empty ),.sck (sck ),.mosi (mosi ),.miso (miso ),.csn_i (csn_i_r ),.csn_o (csn_o ));end
else beginspi_slave#(.CHIP_SEL_NUM (CHIP_SEL_NUM ),.DATA_WIDTH (PDATA_WIDTH ),.ASYNC_FIFO_WIDTH (ASYNC_FIFO_WIDTH ),.CPOL (CPOL ),.CPHA (CPHA ))u_spi_slave(.rstn (prstn ),.clk (pclk ),.tx_data (tx_data ),.tx_data_val (tx_data_val ),.tx_fifo_full (tx_fifo_full ),.rx_req (rx_req ),.rx_data (rx_data ),.rx_data_val (rx_data_val ),.rx_fifo_empty (rx_fifo_empty ),.sck (sck ),.mosi (mosi ),.miso (miso ),.csn (csn_i[0] ));endendgenerateendmodule
3.4. spi_tb
`timescale 1ns/1psmodule spi_tb();parameter BAUD_RATE = 3000000;
parameter PCLK_FREQ = 50000000;
parameter CHIP_SEL_NUM = 2;
parameter PADDR_WIDTH = 32;
parameter PDATA_WIDTH = 32;
parameter CPOL = 0;
parameter CPHA = 0;
parameter ASYNC_FIFO_WIDTH = 4096;parameter SPI_TX_DATA_ADDR = 32'h0000_0000_0000_1000;
parameter SPI_RX_DATA_ADDR = SPI_TX_DATA_ADDR + 32'h4;
parameter SPI_MASTER_CSN_I_ADDR = SPI_TX_DATA_ADDR + 32'h8;logic prstn;
logic pclk;logic [PADDR_WIDTH-1:0] paddr_mst;
logic pwrite_mst;
logic psel_mst;
logic penable_mst;
logic [PDATA_WIDTH-1:0] pwdata_mst;
logic [PDATA_WIDTH-1:0] prdata_mst;
logic pready_mst;logic [PADDR_WIDTH-1:0] paddr_slv1;
logic pwrite_slv1;
logic psel_slv1;
logic penable_slv1;
logic [PDATA_WIDTH-1:0] pwdata_slv1;
logic [PDATA_WIDTH-1:0] prdata_slv1;
logic pready_slv1;logic [PADDR_WIDTH-1:0] paddr_slv2;
logic pwrite_slv2;
logic psel_slv2;
logic penable_slv2;
logic [PDATA_WIDTH-1:0] pwdata_slv2;
logic [PDATA_WIDTH-1:0] prdata_slv2;
logic pready_slv2;wire sck;
wire mosi;
wire miso;
logic miso_r;
wire miso_slv1;
wire miso_slv2;
logic [CHIP_SEL_NUM-1:0] csn_o; initial beginpclk = 0;forever #10 pclk = !pclk; //50MHZ
endinitial beginprstn = 1;#50 prstn = 0;#50 prstn = 1;
endinitial beginpaddr_mst = 'd0;pwrite_mst = 1'b0;psel_mst = 1'b0;penable_mst = 1'b0;pwdata_mst = 'd0;paddr_slv1 = 'd0;pwrite_slv1 = 1'b0;psel_slv1 = 1'b0;penable_slv1 = 1'b0;pwdata_slv1 = 'd0;paddr_slv2 = 'd0;pwrite_slv2 = 1'b0;psel_slv2 = 1'b0;penable_slv2 = 1'b0;pwdata_slv2 = 'd0;#300;spi_pingpong_test();
endtask spi_pingpong_test();forkbeginspi_master_sequence(SPI_TX_DATA_ADDR,1'b1,32'd1);spi_master_sequence(SPI_TX_DATA_ADDR,1'b1,32'd2);endbeginspi_slave1_sequence(SPI_TX_DATA_ADDR,1'b1,32'd5);spi_slave1_sequence(SPI_TX_DATA_ADDR,1'b1,32'd6);endjoin@(posedge pclk);#1;spi_master_sequence(SPI_MASTER_CSN_I_ADDR,1'b1,32'b10);forkbeginspi_master_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);spi_master_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);endbeginspi_slave1_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);spi_slave1_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);endjoin@(posedge sck);@(posedge sck);@(posedge pclk);#1;spi_master_sequence(SPI_MASTER_CSN_I_ADDR,1'b1,32'b11);forkbeginspi_master_sequence(SPI_TX_DATA_ADDR,1'b1,32'd3);spi_master_sequence(SPI_TX_DATA_ADDR,1'b1,32'd4);endbeginspi_slave2_sequence(SPI_TX_DATA_ADDR,1'b1,32'd7);spi_slave2_sequence(SPI_TX_DATA_ADDR,1'b1,32'd8);endjoin@(posedge pclk);#1;spi_master_sequence(SPI_MASTER_CSN_I_ADDR,1'b1,32'b01);forkbeginspi_master_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);spi_master_sequence(SPI_RX_DATA_ADDR,1'b0,32'd0);endbeginspi_slave2_sequence(SPI_RX_DATA_ADDR,1'b0,32'd7);spi_slave2_sequence(SPI_RX_DATA_ADDR,1'b0,32'd8);endjoin@(posedge pclk);#1;spi_master_sequence(SPI_MASTER_CSN_I_ADDR,1'b1,32'b11);endtasktask spi_master_sequence(input bit [PADDR_WIDTH-1:0] addr,input bit write,input bit [PDATA_WIDTH-1:0] wdata);@(posedge pclk);#1;paddr_mst = addr;pwrite_mst = write;psel_mst = 1'b1;penable_mst = 1'b0;if(pwrite_mst)pwdata_mst = wdata;@(posedge pclk);#1;penable_mst = 1'b1;forever begin@(posedge pclk);if(psel_mst && penable_mst && pready_mst) begincase(paddr_mst)SPI_TX_DATA_ADDR:if(pwrite_mst)$display("spi master has successfully written data %d into transfer FIFO",pwdata_mst);else$display("spi master has written %d last time",pwdata_mst);SPI_RX_DATA_ADDR:if(pwrite_mst)$display("spi master: this read-only register can not be written.");else$display("spi master has successfully read data %d from receive FIFO",prdata_mst);SPI_MASTER_CSN_I_ADDR:if(pwrite_mst)$display("spi master has successfully selected %b",pwdata_mst[CHIP_SEL_NUM-1:0]);else$display("spi master has select %b last time",pwdata_mst[CHIP_SEL_NUM-1:0]);default:$display("spi master: paddr is set wrong");endcase#1;psel_mst = 1'b0;break;endend
endtasktask spi_slave1_sequence(input bit [PADDR_WIDTH-1:0] addr,input bit write,input bit [PDATA_WIDTH-1:0] wdata);@(posedge pclk);#1;paddr_slv1 = addr;pwrite_slv1 = write;psel_slv1 = 1'b1;penable_slv1 = 1'b0;if(pwrite_slv1)pwdata_slv1 = wdata;@(posedge pclk);#1;penable_slv1 = 1'b1;forever begin@(posedge pclk);if(psel_slv1 && penable_slv1 && pready_slv1) begincase(paddr_slv1)SPI_TX_DATA_ADDR:if(pwrite_slv1)$display("spi slave1 has successfully written data %d into transfer FIFO",pwdata_slv1);else$display("spi slave1 has written %d last time",pwdata_slv1);SPI_RX_DATA_ADDR:if(pwrite_slv1)$display("spi slave1: this read-only register can not be written.");else$display("spi slave1 has successfully read data %d from receive FIFO",prdata_slv1);default:$display("spi slave1: paddr is set wrong");endcase#1;psel_slv1 = 1'b0;break;endend
endtasktask spi_slave2_sequence(input bit [PADDR_WIDTH-1:0] addr,input bit write,input bit [PDATA_WIDTH-1:0] wdata);@(posedge pclk);#1;paddr_slv2 = addr;pwrite_slv2 = write;psel_slv2 = 1'b1;penable_slv2 = 1'b0;if(pwrite_slv2)pwdata_slv2 = wdata;@(posedge pclk);#1;penable_slv2 = 1'b1;forever begin@(posedge pclk);if(psel_slv2 && penable_slv2 && pready_slv2) begincase(paddr_slv2)SPI_TX_DATA_ADDR:if(pwrite_slv2)$display("spi slave1 has successfully written data %d into transfer FIFO",pwdata_slv2);else$display("spi slave1 has written %d last time",pwdata_slv2);SPI_RX_DATA_ADDR:if(pwrite_slv2)$display("spi slave1: this read-only register can not be written.");else$display("spi slave1 has successfully read data %d from receive FIFO",prdata_slv2);default:$display("spi slave1: paddr is set wrong");endcase#1;psel_slv2 = 1'b0;break;endend
endtaskspi#(.BAUD_RATE (BAUD_RATE ),.PCLK_FREQ (PCLK_FREQ ),.CHIP_SEL_NUM (CHIP_SEL_NUM ),.PADDR_WIDTH (PADDR_WIDTH ),.PDATA_WIDTH (PDATA_WIDTH ),.MSTR (0 ),.CPOL (CPOL ),.CPHA (CPHA ),.ASYNC_FIFO_WIDTH (ASYNC_FIFO_WIDTH ))u_spi_master(.prstn (prstn ),.pclk (pclk ),.paddr (paddr_mst ),.pwrite (pwrite_mst ),.psel (psel_mst ),.penable (penable_mst ),.pwdata (pwdata_mst ),.prdata (prdata_mst ),.pready (pready_mst ),.sck (sck ),.mosi (mosi ),.miso (miso ),.csn_o (csn_o ),.csn_i ( ));always@(*) begincase(csn_o)2'b10:miso_r = miso_slv1;2'b01:miso_r = miso_slv2;default: miso_r = 1'b1;endcase
endassign miso = miso_r;spi#(.BAUD_RATE (BAUD_RATE ),.PCLK_FREQ (PCLK_FREQ ),.CHIP_SEL_NUM (CHIP_SEL_NUM ),.PADDR_WIDTH (PADDR_WIDTH ),.PDATA_WIDTH (PDATA_WIDTH ),.MSTR (1 ),.CPOL (CPOL ),.CPHA (CPHA ),.ASYNC_FIFO_WIDTH (ASYNC_FIFO_WIDTH ))u_spi_slave1(.prstn (prstn ),.pclk (pclk ),.paddr (paddr_slv1 ),.pwrite (pwrite_slv1 ),.psel (psel_slv1 ),.penable (penable_slv1 ),.pwdata (pwdata_slv1 ),.prdata (prdata_slv1 ),.pready (pready_slv1 ),.sck (sck ),.mosi (mosi ),.miso (miso_slv1 ),.csn_o ( ),.csn_i (csn_o[0] ));spi#(.BAUD_RATE (BAUD_RATE ),.PCLK_FREQ (PCLK_FREQ ),.CHIP_SEL_NUM (CHIP_SEL_NUM ),.PADDR_WIDTH (PADDR_WIDTH ),.PDATA_WIDTH (PDATA_WIDTH ),.MSTR (1 ),.CPOL (CPOL ),.CPHA (CPHA ),.ASYNC_FIFO_WIDTH (ASYNC_FIFO_WIDTH ))u_spi_slave2(.prstn (prstn ),.pclk (pclk ),.paddr (paddr_slv2 ),.pwrite (pwrite_slv2 ),.psel (psel_slv2 ),.penable (penable_slv2 ),.pwdata (pwdata_slv2 ),.prdata (prdata_slv2 ),.pready (pready_slv2 ),.sck (sck ),.mosi (mosi ),.miso (miso_slv2 ),.csn_o ( ),.csn_i (csn_o[1] ));endmodule
4. 测试
4.1. spi_master与spi_slave1、spi_slave2的pingpong测试
测试的内容如spi_tb代码所写,先是将spi_master与spi_slave1进行1v1通信,然后是spi_master与spi_slave2进行1v1通信
参数设定如下:
parameter BAUD_RATE = 3000000; //SCK频率
parameter PCLK_FREQ = 50000000; //用户时钟
parameter CHIP_SEL_NUM = 2; //spi master可片选的spi slave数量
parameter PADDR_WIDTH = 32; //地址位宽
parameter PDATA_WIDTH = 32; //数据位宽
parameter CPOL = 0; //CPOL
parameter CPHA = 0; //CPHA
parameter ASYNC_FIFO_WIDTH = 4096; //异步FIFO深度parameter SPI_TX_DATA_ADDR = 32'h0000_0000_0000_1000; //spi内部发送FIFO的访问地址
parameter SPI_RX_DATA_ADDR = SPI_TX_DATA_ADDR + 32'h4; //spi内部接收FIFO的访问地址
parameter SPI_MASTER_CSN_I_ADDR = SPI_TX_DATA_ADDR + 32'h8; //spi_master内部片选寄存器csn_i的访问地址
首先给出transcript信息,数据交换正确
再看部分信号波形,在复位刚刚结束时,先将要传输的数据分别写入各自的FIFO内,spi master和spi slave不断去读,直到读出要发送的数据并将其写入各自的移位寄存器,在此期间片选信号为高。
之后片选信号拉低,表示开始传输,spi master直接从IDLE进入TRANS状态。由于CPOL和CPHA均为0,上升沿采样、下降沿驱动,故bit_cnt下降沿减1、移位寄存器在sck上升沿和下降沿均会变化。
当bit_cnt为0时传输结束,虽然此时csn_i依然为2’b10,但csn_o自动置2’b11。spi master先进入FIFO_WRITE状态将获得的数据写入rx_async_fifo,再在IDLE状态从tx_async_fifo读出新数据。
spi slave也是先写再读,整个过程与spi master类似不再赘述,可自行仿真。
CPOL和CPHA其他取值可用相同的方法进行测试。
串行外设接口(Serial Peripheral Interface, SPI)相关推荐
- 嵌入式硬件协议: SPI串行外设接口 Serial Peripheral Interface
简介 SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是 一种高速全双工的通信总线.它被广泛地使用在ADC.LCD 等设备与M ...
- 谈谈SPI (Serial Peripheral Interface,串行外设接口)
今天我们来一起聊聊谈谈SPI (Serial Peripheral Interface,串行外设接口): 什么是SPI SPI (Serial Peripheral Interface,串行外设接口) ...
- arduino教程-9. 串行外设接口(spi)
文章目录 相关资料 1. spi针脚 Arduino 串行外设接口 串行外设接口简介 板的SPI引脚 SPI.h 库 SPI.h官方示例 SPI为主机 例子 SPI为从机 例子 相关资料 SPI li ...
- 同步和串行的区别_谈谈SPI (Serial Peripheral Interface,串行外设接口)
什么是SPI SPI (Serial Peripheral Interface,串行外设接口)是Motorola 公司推出的一 种同步串行接口技术,是一种高速的,全双工,同步的通信总线: 它以主从方式 ...
- 微雪树莓派PICO笔记——7. SPI(串行外设接口)
文章目录 SPI简介 硬件连接 通讯协议详解 RP2040 SPI 主要参数 RP2040 SPI 逻辑框图 machine.SPI类函数详解 例程地址 代码示例 代码实现 SPI简介 SPI全称为串 ...
- DSP SPI串行外设接口
1.SPI介绍 1.1 SPI简介 SPI的全称是"Serial Peripheral Interface",意为串行外围接口,是Motorola首先在其MC68HCXX系列处理器 ...
- F28335第十一篇——串行外设接口(SPI)
摘要 本文大致介绍了F28335中SPI工作原理和大致寄存器.还有很多细节知识没有列出,需要详细了解的同学,可以参考TI官方文档(TI官网免费下载),或者可以看书籍.重点推荐符晓编写的<TMS3 ...
- 串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_master
目录 1. baud_clk_gen 1.1. 代码 2. spi_master 2.1. IDLE:等待写入 2.2. WAIT_CS:等待选通 2.3. TRANS:传输 (CPOL ^ CPHA ...
- 串行外设接口(Serial Peripheral Interface, SPI)逻辑设计部分 - spi_slave
目录 1. (csn == 1'b1) 时 1.1. spi_slave 完成 Master读写握手 的最短时间 1.2. spi_slave 完成 Slave读写握手 的最短时间 2. (csn = ...
- platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架
platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...
最新文章
- 去重之后统计条数_BOPET:12的普通包装膜到底去哪了?
- MATLAB实战系列(二十九)-头脑风暴优化(BSO)算法求解旅行商问题(TSP)-交叉算子
- 一文看尽9篇语义分割最新论文(GPSNet/Graph-FCN/HMANet等)
- Jenkins 权限配置与集群配置
- 音视频技术开发周刊 | 204
- SQL语句、PL/SQL语句、SQL*PLUS语句结束符号
- HBase: Thrift写数据报错——socket.error: [Errno 32] Broken pip
- 添加第三方库到Maven资源库
- 【经典回放】多种语言系列数据结构算法:二叉树(C#版)
- Webpack构建性能优化指南
- linux-01-概述
- inner join 与 left join 、right join之间的区别
- 将视图状态存入数据库(3)
- 28.类型提示的实现
- 软件项目管理案例教程第四版答案
- webrtc中视频采集实现分析(一) 采集及图像处理接口封装
- PicGo搭建图床避坑
- Activity中的数据传送—案例: 购买装备
- python 小游戏——外星人入侵源码倾情奉献
- 专利申请流程及费用及时间?
热门文章
- 暴雪守望先锋显示连接暴雪服务器超时,守望先锋 连接暴雪游戏服务器超时
- 重要不紧急紧急不重要
- Flink状态的缩放(rescale)与键组(Key Group)设计
- cvc 降噪_此降噪非彼降噪,你要的是哪种降噪?
- Excel技巧 一秒取消合并单元格,让你的取消合并操作简单快捷
- Java 简单计算器
- Trickbot 年度版本变化情况
- OPNsense用户手册-缓存代理
- 用好这 43 款 Chrome 插件,让你开发学习一下子好轻松!
- 【CVPR 2021】Revisiting Knowledge Distillation: An Inheritance and Exploration Framework