基于Verilog的SPI接口设计

SPI接口电路的学习

1、SPI接口电路原理

SPI, Serial Perripheral Interface, 串行外围设备接口, 是Motorola 公司推出的一种同步串行接口技术。 SPI 总线在物理上是通过接在外围设备微控制器(PICmicro) 上面的微处理控制单元 (MCU) 上叫作同步串行端口(Synchronous Serial Port) 的模块(Module)来实现的, 它允许 MCU 以全双工的同步串行方式, 与各种外围设备进行高速数据通信。

SPI 主要应用在EEPROM, Flash, 实时时钟(RTC), 数模转换器(ADC),
数字信号处理器(DSP) 以及数字信号解码器之间。 它在芯片中只占用四根管脚 (Pin) 用来控制以及数据传输, 节约了芯片的 pin 数目, 同时为 PCB 在布局上节省了空间. 正是出于这种简单易用的特性, 现在越来越多的芯片上都集成了 SPI技术。

特点

①采用主-从模式(Master-Slave)的控制方式:

  • SPI 规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master
    设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI
    协议还规定 Slave 设备的 Clock 由 Master 设备通过SCK 管脚提供给 Slave 设备, Slave
    设备本身不能产生或控制 Clock, 没有 Clock 则Slave 设备不能正常工作。

②采用同步方式(Synchronous)传输数据:

  • Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal)
    , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样,
    来保证数据在两个设备之间是同步传输的。

③数据交换(Data Exchanges):

  • SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI设备不能在数据通信过程中仅仅只充当一个"发送者(Transmitter)"或者 “接收者(Receiver)”. 在每个 Clock 周期内, SPI 设备都会发送并接收一
    bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了。

    一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access).
    所以, Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上。

    在数据传输的过程中, 每次接收到的数据必须在下一次数据传输之前被采样. 如果之前接收到的数据没有被读取,
    那么这些已经接收完成的数据将有可能会被丢弃, 导致 SPI 物理模块最终失效. 因此, 在程序中一般都会在 SPI 传输完数据后,
    去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的。

    **

工作机制:**

①通常四线SPI设备的四个信号:
在这里插入图片描述

Serial Clock, 主要的作用是 Master 设备往 Slave 设备传输时钟信号, 控制数据交换的时机以及速率;

SS/CS, Slave Select/Chip Select, 用于 Master 设备片选
Slave 设备, 使被选中的 Slave 设备能够被 Master 设备所访问;

SDO/MOSI, Serial Data Output/Master Out Slave In, 在 Master 上面也被称为Tx-Channel, 作为数据的出口, 主要用于 SPI 设备发送数据;

SDI/MISO, Serial Data Input/Master In Slave Out, 在 Master 上面也被称为
Rx-Channel, 作为数据的入口, 主要用于SPI 设备接收数据。

②两个概念:时钟极性与时钟相位:

CPOL: 时钟极性, 表示 SPI 在空闲时, 时钟信号是高电平还是低电平. 若 CPOL 被设为 1, 那么该设备在空闲时
SCK 管脚下的时钟信号为高电平. 当 CPOL 被设为 0 时则正好相反
CPOL = 0: SCK idle phase is low;
CPOL = 1: SCK idle phase is high;
CPHA: 时钟相位, 表示 SPI 设备是在 SCK 管脚上的时钟信号变为上升沿时触发数据采样, 还是在时钟信号变为下降沿时触发数据采样. 若 CPHA 被设置为 1, 则 SPI 设备在时钟信号变为下降沿时触发数据采样, 在上升沿时发送数据. 当 CPHA 被设为 0 时也正好相反.

    CPHA = 0: Output data at negedge of clock while

receiving data at posedge of clock;

    CPHA = 1: Output data at posedge of clock

while receiving data at negedge of clock;

在这里插入图片描述

在主设备这边配置SPI接口时钟的时候一定要弄清楚从设备的时钟要求,因为主设备这边的时钟极性和相位都是以从设备为基准的。因此在时钟极性的配置上一定要搞清楚从设备是在时钟的上升沿还是下降沿接收数据,是在时钟的下降沿还是上升沿输出数据。但要注意的是,由于主设备的SDO连接从设备的SDI,从设备的SDO连接主设备的SDI,从设备SDI接收的数据是主设备的SDO发送过来的,主设备SDI接收的数据是从设备SDO发送过来的,所以主设备这边SPI时钟极性的配置(即SDO的配置)跟从设备的SDI接收数据的极性是相反的,跟从设备SDO发送数据的极性是相同的。
在这里插入图片描述

当从机增多时我们可以采用芯片来进行辅助控制。

③数据传输:

在一个SPI时钟周期内,会完成如下操作:
在这里插入图片描述

1)主机通过MOSI线发送1位数据,从机通过该线读取这1位数据;

2)从机通过MISO线发送1位数据,主机通过该线读取这1位数据。

这是通过移位寄存器来实现的。如下图所示,主机和从机各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主机寄存器和从机寄存器,并且依次移入从机寄存器和主机寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。

(4)Slave从机的实现:

SPI接口一端的信号有选通信号cs, 串行输入时钟信号sclk, 串行输入数据信号sdi, 串行输出数据信号sdo。它另一端与存储器进行连接,这端的信号有读写控制信号wr_rd, 读写地址信号addr,写数据并行输出信号dataout,
读数据并行输入信号datain。

SPI接口一端的信号中,串行输入数据信号sdi和串行输出数据信号sdo可以进行复用,即共用一个信号SDIO,此时称为三线制的SPI接口。下面是三线制SPI接口的示意波形。为了描述方便,我们往往用四线制的SPI接口,即SPI接口一端的信号有选通信号cs, 串行输入时钟信号sclk, 串行输入数据信号sdi, 串行输出数据信号sdo等共4根信号线。

在这里插入图片描述
写操作

在这里插入图片描述

读操作

2、仿真测试

本次使用的软件还是Quartus,由于前一周已经详细了解过了,所以这里就不演示每一步的创建过程了,主要侧重于代码的实现及仿真结果的分析。

(1) 假设某阶段SPI一端的端口信号在一个传输周期共传输24bit信息,其中16比特是指令信息,8比特是数据信息。第一个比特为1表示写操作,否则为读操作。



module spi (reset, cs, sclk, sdi, sdo,wr_rd, addr, rdata, wdata);
input     reset;
input           cs;
input          sclk;
input          sdi;
output        sdo;
output         wr_rd;
output  [14:0]  addr;
input   [7:0]   rdata;
output  [7:0]   wdata;
reg 	[ 2:0] cnt_bit;

always @(posedge sclk or negedge reset)

 
if (!reset)

cnt_bit <= 3'b0;
 
else if (!cs)
cnt_bit <= cnt_bit + 3'b1; 
else
  
cnt_bit <= 3'b0;

wire   cnt_full;

assign  	cnt_full = (cnt_bit==3'd7);

parameter IDLE    = 2'd0;   //初始状态

parameter INSTR_H = 2'd1;    //接收指令16比特中的高8位比特状态

parameter INSTR_L = 2'd2;    //接收指令16比特中的低8位比特状态

parameter DATA    = 2'd3;   //传输8比特数据状态

reg 	[ 1:0] current_state, next_state;

always @(posedge sclk or negedge reset)

  if (!reset)

   
current_state <= IDLE;

 
else if (cs)

   
current_state <= IDLE;

 
else

   
current_state <= next_state;

 

always @(*)

begin

 
case (current_state)

   
IDLE: begin

     
if (cs)

       
next_state = IDLE;

     
else

       
next_state = INSTR_H;

     
end

   
INSTR_H : begin

     
if (cnt_full)

       
next_state = INSTR_L;

     
else

       
next_state = INSTR_H;

     
end

   
INSTR_L : begin

     
if (cnt_full)

       
next_state = DATA;

     
else
next_state = INSTR_L
end
    DATA : begin     
if (cs)       
next_state = IDLE;
else       
next_state = DATA;
end   
default: next_state = IDLE;

endcase

end
reg 	[15:0] shift_reg_din;

always @(posedge sclk or negedge reset)
 
if (!reset)   
shift_reg_din <= 15'd0;

else if (cs)
shift_reg_din <= 15'd0;
else
  
shift_reg_din <= {shift_reg_din[14:0],sdi};

wire    cstate;
assign  	cstate = (current_state==INSTR_L);

reg     cnt_full_d, cstate_d;
always @(posedge sclk or negedge reset)

if (!reset) 
begin
cnt_full_d <= 0;

cstate_d <= 0;

end
else begin
   cnt_full_d <= cnt_full;
  
cstate_d <= cstate;

end

reg  	[14:0]  addr;

always @(negedge sclk or negedge reset)

 
if (!reset)
 
addr <= 15'd0; 
else if (cnt_full_d && cstate_d)//表示地址读取完了

   
addr <= shift_reg_din[14:0];//将15位地址赋值给addr
else
  
addr <= addr;

reg               wr_rd;//写相对于spi来说是输出
always @(negedge sclk or negedge reset)

if (!reset)   
wr_rd <= 1'b0; 
else if (cnt_full_d && cstate_d)
  
wr_rd <= shift_reg_din[15];//地址读取完后将最高位赋值给写,若为1则表明是进行写操作

reg  	[7:0]  wdata;

always @(posedge cs or negedge reset)

 
if (!reset)

   
wdata <= 8'd0;

 
else

   
wdata <= shift_reg_din[7:0];

 

reg  	[ 7:0] shift_reg_dout;

always @(negedge sclk or negedge reset)

 
if (!reset)

   
shift_reg_dout <= 8'b0;

 
else if ((cnt_bit==3'd0) && (current_state==DATA))

   
shift_reg_dout <= rdata;

 
else 

   
shift_reg_dout <= {shift_reg_dout[6:0],1'b0};//串行输出

assign sdo  = ((!wr_rd)&&(!cs)) ?
shift_reg_dout[7] : 1'b0;

endmodule


测试激励如下:



module spi_tb();
reg    reset;
reg  cs  ;
reg  sclk;
reg  sdi  ;
wire sdo  ;
reg   [7:0]   rdata;
wire    wr_rd ;
wire  	[14:0]  addr;
wire  [7:0]   wdata;

spi u0(reset, cs, sclk, sdi, sdo, wr_rd,addr, rdata, wdata);

always  
 #5sclk=~sclk;

initial begin   

sclk = 0;

reset=0; 

cs = 1;

sdi = 0;

rdata=8'b00001111;

#27 reset=1;

#14 cs = 0;

sdi = 1;   
//写操作

repeat(1) @(negedge sclk);

sdi = 0;

repeat(11) @(negedge sclk);

sdi = 1;

repeat(1) @(negedge sclk);

sdi = 0;

repeat(1) @(negedge sclk);

sdi = 1;

repeat(1) @(negedge sclk);

sdi = 0;

repeat(2) @(negedge sclk);

sdi = 1;

repeat(1) @(negedge sclk);

sdi = 0;

repeat(1) @(negedge sclk);

sdi = 1;

repeat(1) @(negedge sclk);

sdi = 0;

repeat(1) @(negedge sclk);

sdi = 1;

repeat(1) @(negedge sclk);

sdi = 0;

repeat(1) @(negedge sclk);

sdi = 1;

#11 cs = 1;

//-------以上完成一个写传输操作---------

 

repeat(5) @(negedge sclk);

cs = 0;

sdi = 0;   
//读操作

repeat(1) @(negedge sclk);

sdi = 0;

repeat(11) @(negedge sclk);

sdi = 1;

repeat(1) @(negedge sclk);

sdi = 0;

repeat(1) @(negedge sclk);

sdi = 1;

repeat(1) @(negedge sclk);

sdi = 0;

repeat(1) @(negedge sclk);

sdi = 1'bz;
repeat(7) @(negedge sclk);
#11 cs = 1;
//-------以上完成一个读传输操作---------
#200 
$stop;


仿真结果:
在这里插入图片描述

  • 5
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值