主页 > 互联网 > 内容页

世界动态:SPI的设计与实现

2023-03-26 18:17:10 来源:数字IC与好好生活的两居室

SPI简介


(资料图片仅供参考)

SPI 全称为 Serial Peripheral Interface,译为串行外设接口。它是 Motorola 公司推出的一种相对高速的同步、全双工的通信总线协议。

SPI 一般有以下几个特点:

因为时钟线的存在,SPI 是同步的串行通信总线。

因为 Master 与 Slave 之间存在两根不同方向的数据线,所以 SPI 支持全双工传输。

SPI 通信协议简单,数据传输速率较快,传输速率没有特殊设定,一般在 10兆波特率以下,最高可支持 30Mbps 甚至 50Mbps。

SPI 采用主从机通信模式,应用广泛,多用于 EEPROM、Flash、实时时钟(RTC)、 数模转换器(ADC) 等模块的通信。

缺点是没有应答机制和校验机制,不能确认是否接收到数据、是否传输有错。

SPI 引脚

SPI 通信最少需要 4 根信号线,分别是 CSN、SCLK、MOSI 与 MISO。各个信号说明如下:

CS/CSN: Chip Select,主设备产生的从设备片选信号。当 Slave 片选信号有效时,Slave 可被 Master 访问并进行通信。一个 Master 可能有多个片选信号,但一个 Slave 只能有一个片选信号。CS 表示片选信号为高时开始数据传输,CSN 表示片选信号为低时开始数据传输。

SCLK: Master 产生的时钟信号,用于数据的同步传输。时钟频率决定了数据传输速率。

MOSI: Mater Output Slave Input,主设备输出、从设备输入的数据线,用于 Master 向 Slave 进行数据传输。

MISO: Mater Input Slave Output,主设备输入、从设备输输出的数据线,用于 Slave 向 Master 进行数据传输。

SPI 的通信采用主从模式,通常会有一个主设备和一个或多个从设备。

某 Master 与 多个 Slave 通过 SPI 信号线的互联示意图如下。

SPI 协议

SPI 通信有 4 种传输模式,规定了数据在不同时钟边沿的采样与发送规则,由时钟极性 (CPOL,Clock Polarity) 和时钟相位 (CPHA,Clock Phase) 两两组合来定义。其中,CPOL 参数决定了时钟信号 SCLK 空闲状态为低电平还是高电平,CPHA 参数决定了数据是在时钟 SCKL 的上升沿采样还是下降沿采样。Slave 可能在出厂时就配置为某种模式不能修改的固定模式,这就要求 SPI Master 发送的传输模式要与 Slave 一致。

SPI 的 4 种传输模式示意图如下:

SPI 的 4 种传输模式说明如下:

CPOL=0,CPHA=0:空闲态时 SCLK 处于低电平,数据采样发生在 SCLK 时钟的第一个边沿时刻。即 Slave 在 SCLK 由低电平到高电平的上升沿跳变时进行数据采样,Master 在 SCLK 下降沿发送数据。

CPOL=0,CPHA=1:空闲态时 SCLK 处于低电平,数据采样发生在 SCLK 时钟的第二个边沿时刻。即 Slave 在 SCLK 下降沿接收数据,Master 在 SCLK 上升沿发送数据。

CPOL=1,CPHA=0:空闲态时 SCLK 处于高电平,数据采样发生在 SCLK 时钟的第一个边沿时刻。即 Slave 在 SCLK 下降沿接收数据,Master 在 SCLK 上升沿发送数据。

CPOL=1,CPHA=1:空闲态时 SCLK 处于高电平,数据采样发生在 SCLK 时钟的第二个边沿时刻。即 Slave 在 SCLK 上升沿接收数据,Master 在 SCLK 下降沿发送数据。

以 CPOL=1、CPHA=1 为例,说明 SPI Master 向 Slave 传输 4bit 数据、并读取 4bit 数据的过程,示意图如下。

(1) 空闲状态,SCLK、CSN、MOSI、MISO 均为高电平;

(2) SPI Master CSN 拉低,选择对应 SPI Slave,将开始传输数据;

(3) SCLK 拉低,同时 MOSI 输出单 bit 数据 D1 ;

(4) SCLK 拉高,此时 SPI Slave 读取 MOSI 对应的数据 D1 ;

(5) 重复此过程,直至 SPI Slave 接收到 4bit

(6) 如果 Slave 接收到的 4bit 数据包含 SPI Master 读控制,则在 SPI Master 仍然会继续输出时钟 SCLK,但无需做额外驱动 MOSI ;

(7) SPI Slave 在 SCLK 下降沿输出数据至 MISO;

(8) SPI Master 在 SCLK 上升沿对 MISO 进行采集;

(9) 重复步骤 (6)~(8),直至 Slave 传输完 4bit 数据。

需要注意的是,SPI 协议中的时钟线 SCLK、片选线 CSN 和数据线 MOSI 都是由 SPI Master 控制,并不像 UART或 IIC 协议有明显的通信起始信号、结束信号和通信周期,所以 SPI 通信时 SCLK 有效个数和 CSN 有效长度要控制得当。当没有数据交互时,CSN(CS) 信号要保持为高电平 (低电平) 状态,时钟也需要保持高电平或持低电平状态不变。

SPI 实现

假设某 SPI Slave 中存在 128 个可读可写的 8bit 位宽的寄存器

规定某 SPI Master 与 该SPI Slave 每次通信时传输 16bit 数据,其中最高位 bit[15] 为读写控制位,次高位 bit[14:8] 为地址位,低 8bit 数据位。

数据位数据项数据说明
15读写控制为 1 时表示写操作,为 0 时表示读操作
14:8地址信息Master 访问 Slave 的地址信息
7:0数据信息当进行写访问时,代表写数据,对应 MOSI 数据线;当进行读访问时,代表读数据,对应 MISO 数据线。

下面,对 CPOL=1、CPHA=1 工作模式 (下降沿发送数据、上升沿接收数据) 的 SPI 进行 Verilog设计与简单仿真

参数设计

工作时钟:200 Mhz

波特率:20MHz

传输位度:16

地址位度:7

数据位宽:8

SPI Master 设计

SCLK 由 SPI 模块工作时钟产生,为避免工作时钟与 SCLK 交互时的相位差关系,SPI Master 输出的 SCLK 、CSN 与 MOSI 信号均在工作时钟下产生, SCLK 只作为输出时钟驱动。

SPI Master 信号端口说明如下:

端口信号方向位宽说明
rstninput1工作时钟
clkinput1复位
tx_datainput15并行数据输入
tx_data_eninput1并行数据输入有效
readyoutput1空闲状态指示,可接收并行数据
rdataoutput8接收的并行数据
rdata_validoutput1接收的并行数据有效
csnoutput1SPI 片选信号
sclkoutput1SPI 时钟信号
mosioutput1SPI Master 数据输出
misoinput1SPI Master 数据输入

SPI Master 代码描述如下:

// spi master:// at negedge send data// at posedge recevie datamodule spi_master  (   input                rstn,   input                clk,   //data sended and received   input  [15:0]        tx_data,   input                tx_data_en,   output [7:0]         rdata,   output               rdata_valid,   output               ready,   //spi intf   output               sclk,   output               csn,   output               mosi,   input                miso   );   //100MHz clk, 10MHz spi clk   parameter            BAUD_NUM = 100/10 ;   //==========================================================   //baud clk generating by baud counter   reg [4:0]    baud_cnt_r ;   //generating negedge sclk   wire         baud_cnt_end = baud_cnt_r == BAUD_NUM-1;   //generating posedge sclk   wire         baud_cnt_half = baud_cnt_r == BAUD_NUM/2-1;   always @(posedge clk or negedge rstn) begin      if (!rstn) begin         baud_cnt_r <= "b0 ;      end      else if (csn) begin         baud_cnt_r <= "b0 ;      end      else if (baud_cnt_end) begin         baud_cnt_r <= "b0 ;      end      else begin         baud_cnt_r <= baud_cnt_r + 1"b1 ;      end   end   //==========================================================   //bit counter   reg [7:0]            bit_cnt_r ;   always @(posedge clk or negedge rstn) begin      if (!rstn) begin         bit_cnt_r <= "b0 ;      end      else if (csn) begin         bit_cnt_r <= "b0 ;      end      //add: at posedge sclk      else if (baud_cnt_half && bit_cnt_r != 16) begin         bit_cnt_r <= bit_cnt_r + 1"b1 ;      end   end   //(1) generate spi clk   reg          sclk_r ;   always @(posedge clk or negedge rstn) begin      if (!rstn) begin         sclk_r      <= 1"b1 ;      end      else if (csn) begin         sclk_r      <= 1"b1 ;      end      else if (baud_cnt_half && bit_cnt_r != 16) begin         sclk_r      <= 1"b0 ;      end      else if (baud_cnt_end) begin         sclk_r      <= 1"b1 ;      end   end   assign sclk = sclk_r ;   //==========================================================   //(2) generate csn   reg          csn_r ;   always @(posedge clk or negedge rstn) begin      if (!rstn) begin         csn_r <= 1"b1 ;      end      else if (tx_data_en) begin         csn_r <= 1"b0;      end      //16 data finished, delay half cycle      else if (!csn_r && bit_cnt_r == 16 && baud_cnt_half) begin         csn_r <= 1"b1 ;      end   end   assign csn =        csn_r ;   //==========================================================   //tx_data buffer   reg [15:0]           tx_data_r ;   always @(posedge clk or negedge rstn) begin      if (!rstn) begin         tx_data_r      <= "b0 ;      end      else if (tx_data_en && ready) begin         tx_data_r      <= tx_data ;      end   end   //(3) generate mosi   reg          mosi_r ;   always @(posedge clk or negedge rstn) begin      if (!rstn) begin         mosi_r <= 1"b1 ;      end      else if (csn) begin         mosi_r <= 1"b1 ;      end      //output tx_data      else if (baud_cnt_half && bit_cnt_r != 16 ) begin         mosi_r <= tx_data_r[15-bit_cnt_r] ;      end   end   assign mosi = mosi_r ;   //(4) receive data by miso   reg [7:0]    rdata_r;   reg          rdata_valid_r;   always @(posedge clk or negedge rstn) begin      if (!rstn) begin         rdata_r        <= 8"b0 ;         rdata_valid_r  <= 1"b0 ;      end      else if (rdata_valid_r) begin         rdata_valid_r  <= 1"b0 ;      end      else if (!tx_data_r[15] && bit_cnt_r ==16 && baud_cnt_end) begin         rdata_r        <= {rdata_r[6:0], miso} ;         rdata_valid_r  <= 1"b1 ;      end      else if (!tx_data_r[15] && bit_cnt_r >=9 && baud_cnt_end) begin         rdata_r        <= {rdata_r[6:0], miso} ;      end   end   reg          ready_r ;   always @(posedge clk or negedge rstn) begin      if (!rstn) begin         ready_r        <= 1"b1 ;      end      else if (tx_data_en) begin         ready_r        <= 1"b0 ;      end      else if (csn) begin         ready_r        <= 1"b1 ;      end   end // always @ (negedge clk or negedge rstn)   assign ready = ready_r ;   assign rdata         = rdata_r ;   assign rdata_valid   = rdata_valid_r ;endmodule

SPI Slave 设计

SPI Master 模块只保留 4 个信号即可,片选信号可以当做复位信号使用,说明如下。

端口信号方向位宽说明
csninput1SPI 片选信号
sclkinput1SPI 时钟信号
mosiinput1SPI Slave 数据输入
misooutput1SPI 数据输入

SPI Slave 也是在 SCLK 下降沿开始接收数据,在 SCLK 上升沿输出数据,代码描述如下:

// spi slave:// at negedge send data// at posedge recevie datamodule spi_slave  (   input                sclk,   input                csn,   input                mosi,   output               miso);   //===============================================   //bit counter   reg [3:0]            bit_cnt_r ;   always @(posedge sclk or posedge csn) begin      if (csn) begin         bit_cnt_r <= "b0 ;      end      else begin         bit_cnt_r <= bit_cnt_r + 1"b1 ;      end   end   //===============================================   //(1) receive rw cmd   reg          rw_r ;   always @(posedge sclk or posedge csn) begin      if (csn) begin         rw_r <= 1"b0 ;      end      else if (bit_cnt_r == 0) begin         rw_r <= mosi ;      end   end   //(2) receive address   reg [6:0]    addr_r;   always @(posedge sclk or posedge csn) begin      if (csn) begin         addr_r <= 6"b0 ;      end      else if (bit_cnt_r >= 1 && bit_cnt_r <= 7) begin         addr_r <= {addr_r[5:0], mosi} ;      end   end   //(3) receive data   reg [7:0]    data_r;   always @(posedge sclk or posedge csn) begin      if (csn) begin         data_r <= 8"b0 ;      end      else if (rw_r && bit_cnt_r >=8 && bit_cnt_r <= 15) begin         data_r <= {data_r[6:0], mosi} ;      end   end   //===============================================   //write regs   reg [7:0]    reg_group_r [127:0];   always @(posedge sclk) begin      if (rw_r && bit_cnt_r == 15) begin         reg_group_r[addr_r]    <= {data_r[6:0], mosi} ;      end   end   //===============================================   //rd regs and send out   reg          miso_r ;   always @(negedge sclk or posedge csn) begin      if (csn) begin         miso_r         <= "b0;      end      else if (!rw_r && bit_cnt_r >= 8 && bit_cnt_r <= 15) begin         miso_r         <= reg_group_r[addr_r][15-bit_cnt_r] ;      end   end   assign miso = miso_r ;endmodule

Testbench

测试时,通过向 SPI 输入包含读写控制位、读写地址、和读写数据的并行数据,来判断 SPI Master 向 SPI Slave 写入和读出的数据是否一致,以达到仿真 SPI 功能的目的。

`timescale 1ns/1psmodule test ;   reg          clk_200mhz ;   reg          rstn ;   reg [15:0]   tx_data ;   reg          tx_data_en ;   wire         sclk, csn, mosi, miso ;   wire [7:0]   rdata ;   wire         rdata_valid ;   wire         ready ;   //==========================================   // clk and reset   initial begin      clk_200mhz = 0 ;      rstn = 0 ;      #11.3 rstn = 1 ;   end   always #(2.5)   clk_200mhz  = ~clk_200mhz ;   //==========================================   //driver task   task spi_cmd ;      input [15:0]      data_send ;      begin         wait(ready) ;         @(posedge clk_200mhz) ;         # 0.7 ;         tx_data        = data_send ;         tx_data_en     = 1"b1 ;         @(posedge clk_200mhz) ;         # 0.7 ;         tx_data_en     = 1"b0 ;         tx_data        = "b0 ;         wait(ready) ;      end   endtask // spi_rw   //==========================================   //driver   initial begin      tx_data    = 16"b0 ;      tx_data_en = 1"b0 ;      //(1) wr address: 100-102      #133.7 ;  spi_cmd({1"b1, 7"d100, 8"hAA}) ;      #501.3 ;  spi_cmd({1"b1, 7"d101, 8"h55}) ;      #501.3 ;  spi_cmd({1"b1, 7"d102, 8"hA5}) ;      //(2) rd address: 102-100      #2001.3 ; spi_cmd({1"b0, 7"d102, 8"h0}) ;      #501.3 ;  spi_cmd({1"b0, 7"d101, 8"h0}) ;      #501.3 ;  spi_cmd({1"b0, 7"d100, 8"h0}) ;   end   //finish   reg          err_flag ;   initial begin      err_flag = 0 ;      #100;      //1st read      @(posedge rdata_valid) ;      @(negedge clk_200mhz) ;      if (rdata != 8"ha5)  err_flag |= 1;      //2nd read      @(posedge rdata_valid) ;      @(negedge clk_200mhz) ;      if (rdata != 8"h55)  err_flag |= 1;      //3rd 3read      @(posedge rdata_valid) ;      @(negedge clk_200mhz) ;      if (rdata != 8"haa)  err_flag |= 1;      #13.7 ;      $display("-------------------------");      if (err_flag !== 0) begin         $display("Simulation Failed!");      end      else begin         $display("Simulation Succeed!");      end      $display();      $display();      #1000 ;      $finish ;   end   spi_master u_spi_master  (       .rstn            (rstn),       .clk             (clk_200mhz),       //parallel       .tx_data         (tx_data),       .tx_data_en      (tx_data_en),       .ready           (ready),       //spi intf       .sclk            (sclk),       .csn             (csn),       .mosi            (mosi),       .miso            (miso),       .rdata           (rdata),       .rdata_valid     (rdata_valid)   );   spi_slave u_spi_slave  (       .sclk            (sclk),       .csn             (csn),       .mosi            (mosi),       .miso            (miso));endmodule

SPI Slave 设计

仿真中的自检验成功截图如下所示。

Master 向第 100 个寄存器写数据 0xAA 的仿真波形图如下所示。

Master 发送读取第 100 个寄存器的命令及 Slave 返回对应寄存器数据的波形图如下所示。

标签:

上一篇:
下一篇: