SPI串行外设接口设计实现

接口/总线/驱动

1116人已加入

描述

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 信号线的互联示意图如下。

RTC

SPI 协议

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

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

RTC

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 数据的过程,示意图如下。

RTC

(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 数据位。

RTC

下面,对 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 信号端口说明如下:

RTC

SPI Master 代码描述如下:

// spi master:
// at negedge send data
// at posedge recevie data


module 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 个信号即可,片选信号可以当做复位信号使用,说明如下。

RTC

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

// spi slave:
// at negedge send data
// at posedge recevie data


module 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/1ps


module 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 设计

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

RTC

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

RTC

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

RTC

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分