SPI协议介绍

一、概述

     SPI, Serial Perripheral Interface, 串行外围设备接口, 是 Motorola 公司推出的一种同步串行接口技术. SPI 总线在物理上是通过接在外围设备微控制器(PICmicro) 上面的微处理控制单元 (MCU) 上叫作同步串行端口(Synchronous Serial Port) 的模块(Module)来实现的, 它允许 MCU 以全双工的同步串行方式, 与各种外围设备进行高速数据通信.
     SPI 主要应用在 EEPROM, Flash, 实时时钟(RTC), 数模转换器(ADC), 数字信号处理器(DSP) 以及数字信号解码器之间. 它在芯片中只占用四根管脚 (Pin) 用来控制以及数据传输, 节约了芯片的 pin 数目, 同时为 PCB 在布局上节省了空间. 正是出于这种简单易用的特性, 现在越来越多的芯片上都集成了 SPI技术。

二、 特点
     1. 采用主-从模式(Master-Slave) 的控制方式
       SPI 规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的 Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作.
     2. 采用同步方式(Synchronous)传输数据
       Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的.
     3. 数据交换(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优点:支持全双工通信、通信简单、数据传输速率块。缺点:没有指定的流控制,没有应答机制确认是否接收到数据,所以跟IIC总线协议比较在数据 ,可靠性上有一定的缺陷。

三、结构

SPI包含四根线:

1、SS(Slave Select):片选信号线,当有多个SPI设备与SPI模块相连时,每个设备的这个片选信号线是与不同的ss相连的,而其他SCK,MOSI,MISO线则为多个设备l连接到相同的SPI总线上,当SS信号线为低电平时,片选有效,开始SPI通信

2、SCK(Serial Clock):时钟信号线,由主通信设备产生,不同的设备支持的时钟频率不一样。

3、MOSI(Master Output,Slave Input):主设备输出、从设备输入引脚

4、MISO(Master Input,Slave Output):主设备输入、从设备输出引脚

SPI内部结构如下图:

上图只是对 SPI 设备间通信的一个简单的描述, 下面就来解释一下图中所示的几个组件(Module):
       SSPBUF, Synchronous Serial Port Buffer, 泛指 SPI 设备里面的内部缓冲区, 一般在物理上是以 FIFO 的形式, 保存传输过程中的临时数据;
       SSPSR, Synchronous Serial Port Register, 泛指 SPI 设备里面的移位寄存器(Shift Regitser), 它的作用是根据设置好的数据位宽(bit-width) 把数据移入或者移出 SSPBUF;
       Controller, 泛指 SPI 设备里面的控制寄存器, 可以通过配置它们来设置 SPI 总线的传输模式.

四、时序


          

        上图通过 Master 设备与 Slave 设备之间交换1 Byte 数据来说明 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;
        上图里的 "Mode 1, 1" 说明了本例所使用的 SPI 数据传输模式被设置成 CPOL = 1, CPHA = 1. 这样, 在一个 Clock 周期内, 每个单独的 SPI 设备都能以全双工(Full-Duplex) 的方式, 同时发送和接收 1 bit 数据, 即相当于交换了 1 bit 大小的数据. 如果 SPI 总线的 Channel-Width 被设置成 Byte, 表示 SPI 总线上每次数据传输的最小单位为 Byte, 那么挂载在该 SPI 总线的设备每次数据传输的过程至少需要 8 个 Clock 周期(忽略设备的物理延迟). 因此, SPI 总线的频率越快, Clock 周期越短, 则 SPI 设备间数据交换的速率就越快.

 五、测试

待测试SPI模块地址及寄存器设置:

实现七路SPI,主模式,基地址如下:

SPI0_F_addr<17:2>:0x1d00;

SPI1_F_addr<17:2>:0x1e00;

SPI2_F_addr<17:2>:0x1f00;

SPI3_F_addr<17:2>:0x2000;

SPI4_F_addr<17:2>:0x2100;

SPI5_F_addr<17:2>:0x2200;

SPI6_F_addr<17:2>:0x2300;

偏移地址F_addr<9:2>

寄存器名称

读写属性

寄存器功能

00H

数据寄存器RBR

R

接收缓冲区,D[15:0],读:接收FIFO数据

00H

数据寄存器TBR

W

发送缓冲区,D[15:0],写:发送FIFO数据

01H

中断控制寄存器ICR

R/W

D[0]=’1’ 使能接收中断

D[1]=’1’ 使能发送缓冲区空中断

02H

中断状态寄存器ISR

R/W

D[0]=’1’ 接收中断有效

D[1]=’1’ 发送缓冲区空中断有效

03H

接收缓冲区数据个数寄存器RBN

R/W

只读,D[7:0]接收缓冲区中的数据个数

04H

发送缓冲区数据个数寄存器TBN

R/W

只读,发送缓冲区中的数据个数

05H

接收中断条件寄存器RIC

R/W

ICR[0]=’1’,且RBN>RIC+1时,ISR[0]会被置位,同时中断输出信号被置位

06H

状态寄存器STR

R

D[0]:’1’表示接收缓冲区空

D[1]:’1’表示接收缓冲区满

D[2]:’1’表示接收缓冲区溢出

D[3]:’1’表示发送缓冲区空

D[4]:’1’表示发送缓冲区满

D[5]:’1’表示发送缓冲区溢出

07H

配置寄存器CFR

R/W

配置寄存器

D[0]:’1’表示使能发送

D[1]:’1’表示清空接收缓冲区,读为’0’

D[2]:’1’表示清空发送缓冲区,读为’0’

D[4]:’1’SPI时钟相位寄存器CPHA设置位,‘1’表示在时钟第2个跳变沿采样数据,‘0’表示在时钟第1个跳变沿采样数据

D[7]:1’表示拓展配置模式,‘0’表示非拓展配置模式

 1 //test SPI module 2019.03.11 zhou
 2 //7路 SPI
 3 
 4 spi_slave u0_spi_slave(~F_spi_ss[0], F_spi_sdo[0], F_spi_sdi[0],F_spi_sclk[0]);
 5 spi_slave u1_spi_slave(~F_spi_ss[1], F_spi_sdo[1], F_spi_sdi[1],F_spi_sclk[1]);
 6 spi_slave u2_spi_slave(~F_spi_ss[2], F_spi_sdo[2], F_spi_sdi[2],F_spi_sclk[2]);
 7 spi_slave u3_spi_slave(~F_spi_ss[3], F_spi_sdo[3], F_spi_sdi[3],F_spi_sclk[3]);
 8 spi_slave u4_spi_slave(~F_spi_ss[4], F_spi_sdo[4], F_spi_sdi[4],F_spi_sclk[4]);
 9 spi_slave u5_spi_slave(~F_spi_ss[5], F_spi_sdo[5], F_spi_sdi[5],F_spi_sclk[5]);
10 spi_slave u6_spi_slave(~F_spi_ss[6], F_spi_sdo[6], F_spi_sdi[6],F_spi_sclk[6]);
11 
12 
13 
14 task CPU_READ_SPI_FIFO;
15     input [17:2] addr;
16     output [15:0] rddata;
17    begin
18         #120ns
19     @(posedge S_cpu_clk) 
20     F_nrd = 1'b1;
21     F_nwr = 1'b1;
22     F_ncs = 1'b1;
23     F_addr =addr;
24     @(posedge S_cpu_clk) 
25     F_nrd = 1'b0;
26     F_nwr = 1'b1;
27     F_ncs = 1'b0;
28     F_addr =addr;
29     @(posedge S_cpu_clk) 
30     wait (F_nrdy==0);
31     #50         //one clock delay then read
32     rddata = F_data_o;
33     @(posedge S_cpu_clk) 
34     F_nrd = 1'b1;
35     F_nwr = 1'b1;
36     F_ncs = 1'b1;
37     F_addr =0;
38    end
39 endtask
40 
41 
42 task TEST_CASE_SPI;
43     logic [15:0] spi_reg;
44     logic [15:0] spi_rec_data;
45     logic [15:0] addr;
46     int i,spi_rec_fifonum;
47 
48     begin
49     RST_ASIC();
50     $display("Testing SPI 0-6...");
51 
52 
53     for(addr = 16'h1d00;addr <= 16'h2300;addr += 16'h0100)
54     begin
55         //寄存器复位值自检
56         CPU_READ(addr+8'h01,spi_reg);        //ICR 0
57         if(spi_reg != 16'h0000)        $display("%h:ICR error:%h(0x0000)",addr,spi_reg);
58         CPU_READ(addr+8'h02,spi_reg);        //ISR 0 
59         if(spi_reg != 16'h0000)        $display("%h:ISR error:%h(0x0000)",addr,spi_reg);
60         CPU_READ(addr+8'h03,spi_reg);        //RBN 0
61         if(spi_reg != 16'h0000)        $display("%h:RBN error:%h(0x0000)",addr,spi_reg);
62         CPU_READ(addr+8'h04,spi_reg);        //TBN 0
63         if(spi_reg != 16'h0000)        $display("%h:TBN error:%h(0x0000)",addr,spi_reg);
64         CPU_READ(addr+8'h05,spi_reg);        //RIC 0
65         if(spi_reg != 16'h0000)        $display("%h:RIC error:%h(0x0000)",addr,spi_reg);
66         CPU_READ(addr+8'h06,spi_reg);        //STR 09
67         if(spi_reg != 16'h0009)        $display("%h:STR error:%h(0x0009)",addr,spi_reg);
68         CPU_READ(addr+8'h07,spi_reg);        //CFR 0
69         if(spi_reg != 16'h0000)        $display("%h:CFR error:%h(0x0000)",addr,spi_reg);
70     
71         //写SPI的配置寄存器
72         CPU_WRITE(addr+8'h07,8'h06);    //clear fifo
73         CPU_WRITE(addr+8'h07,8'h01);    //enable sending
74     
75 
76         //向发送缓冲寄存器写数据
77         CPU_WRITE(addr+8'h00,16'h7654);
78         CPU_WRITE(addr+8'h00,16'h3210);
79     
80         #10us;
81 
82         //接收数据
83 
84         CPU_READ(addr+8'h03,spi_rec_fifonum);
85         for(i = 0; i < spi_rec_fifonum;i++)
86         begin
87             CPU_READ_SPI_FIFO(addr+8'h00,spi_rec_data);
88             $display("spi_rec_data:%h",spi_rec_data);
89         end
90 
91     
92     end    //end for
93 
94     $display("Testing SPI Sending & Receiving is Finished!");    
95     end
96 
97 endtask
SPI测试代码
`timescale 1ns/10ps

module spi_slave( ncs, mosi, miso, sck);
input ncs, mosi, sck;
output miso;

reg nrst;

initial
begin
    nrst=0;
    #100            //related to the number of data
    nrst=1;
end


//SPI接收状态机
reg[15:0] byte_received;
reg[3:0] bit_received_cnt;
reg[15:0] rec_data;

always @ (negedge nrst or posedge sck)  //每次sck都会接收数据,spi的顶端模块状态机决定是否取用
begin
    if(~nrst)
    begin
        byte_received <= 16'h0000;
        bit_received_cnt <= 4'h0;
    end
    else
    begin
        if(~ncs)
        begin

        byte_received = {byte_received[14:0], mosi};
        if(bit_received_cnt == 4'hF)
        begin
            rec_data = byte_received;
            $display("spi_test_outer_module received data 0x%h at %t",rec_data,$time);
            bit_received_cnt = 4'h0;
        end
        else    bit_received_cnt = bit_received_cnt + 1;
        
        end        
    end
end

//SPI发送状态机
reg miso;
reg[15:0] byte_sended;  //发送移位寄存器
reg[3:0] bit_sended_cnt;  //SPI发送位计数器

always @ (negedge nrst)
begin
    if(~nrst)
    begin
        byte_sended <= 16'h89ab;
        bit_sended_cnt <= 0;
    end
end


always @ (negedge sck or negedge ncs)
begin
    if(~ncs && nrst)
    begin
        miso = byte_sended[15];
        byte_sended = {byte_sended[14:0],1'b0};
        
        if(bit_sended_cnt == 4'hF)
        begin
            byte_sended <= 16'hcdef;
            bit_sended_cnt <= 0;
        end
        else bit_sended_cnt += 1;
    end
end

endmodule
SPI简易从模块代码

 测试结果

原文地址:https://www.cnblogs.com/zhouliyan/p/10615398.html