SPI ( Serial Peripheral Interface,串行外围设备接口)是一种同步、全双工、主从式,高速接口(UART是异步)。
来自主机或 从机 的数据在时钟上升沿或下降沿同步。主机和从机可以同时传输数据。
串行外设接口(SPI)是微控制器和外围IC(如传感器、ADC、DAC、移位寄存器、SRAM等)之间使用最广泛的接口之一。
SPI是一个环形总线结构,由SS(CS)、SCK、MOSI、MISO构成,主要是在SCK的控制下,两个双向移位寄存器进行数据交换。
主机和从机之间的关系:
SPI数据的传输是在串行同步时钟信号(Serial Clock,SCK)的控制下进行的。
SPI怎么保证同步:
主机的时钟发生器一方面控制主机的移位寄存器,另一方面通过从机的SCK信号线来控制从机的移位寄存器,从而保证主机与从机的数据交换是同步进行的。
SPI开始通信
主机发送时钟信号。
主机通过使能片选信号CS(低电平有效)选择从机。
SPI通信期间
SPI总线的主机和从机都有一个移位寄存器。
当主机向自己的移位寄存器写入数据时,数据会通过MOSI信号线进入到从机的移位寄存器;
同时,
从机移位寄存器里的数据,通过MISO信号线进入到主机的移位寄存器。
这样,主机和从机就完成了一次数据交换。
⚠注意:
SPI是全双工接口,主机和从机可以分别通过MOSI和MISO线路同时发送数据。【数据的发送(串行移出到MOSI/SDO总线上)和接收(采样或读入总线(MISO/SDI)上的数据)同时进行。】
串行时钟沿 同步 数据的移位和采样。
SPI接口允许用户灵活选择时钟的上升沿或下降沿来采样和/或移位数据。
SPI串行同步时钟可以设置为不同的极性(Clock Polarity ,CPOL)与相位(Clock Phase ,CPHA)。
❤ 时钟的极性(CPOL)
用来决定在总线空闲时,同步时钟(SCK)信号线上的电位是高电平还是低电平。
🗡 空闲状态:
传输开始时CS为高电平且在向低电平转变的期间,
传输结束时CS为低电平且在向高电平转变的期间。
❤ 时钟的相位(CPHA)
读取数据和发送数据的时钟沿(上升沿还是下降沿)。
⭐ CPHA=1(在SCK信号线的第二个跳变沿进行采样 )
⭐ CPHA=0(在SCK信号线的第一个跳变沿进行采样)
主机必须根据从机的要求选择时钟极性和时钟相位。
根据CPOL和CPHA位的选择,有四种SPI模式可用。
SPI模式 | CPOL | CPHA | 空闲状态下时钟极性 | 用于采样和/或移动数据的时钟相位 |
0 | 0 | 0 | 逻辑低电平 | 上升沿采样,下降沿移出 |
1 | 0 | 1 | 逻辑低电平 | 下降沿采样,上升沿移出 |
2 | 1 | 0 | 逻辑高电平 | 下降沿采样,上升沿移出 |
3 | 1 | 1 | 逻辑高电平 | 下降沿采样,上升沿移出 |
Mode0:CPOL= 0,CPHA = 0:(在SCK信号线的第一个跳变沿进行采样 )
CPOL =0 :
时钟在数据传输前和完成后都保持低电平(时钟空闲电平为低电平)
CPHA =0:
在第一个时钟沿(上升沿)采样数据,
在第二个时钟沿(下降沿)输出数据。
MODE0的传输时序:
Mode1:CPOL = 0,CPHA = 1:(在SCK信号线的第二个跳变沿进行采样 )
CPOL =0 :
时钟在数据传输前和完成后都保持低电平(时钟空闲电平为低电平)
CPHA =1:
在第二个时钟沿(上升沿)采样数据,
在第一个时钟沿(下降沿)输出数据。
MODE1的传输时序:
Mode2:CPOL= 1,CPHA = 0:(在SCK信号线的第一个跳变沿进行采样 )
CPOL =1:
时钟在数据传输前和完成后都保持高电平(时钟空闲电平为高电平)
CPHA =0:
在第一个时钟沿(上升沿)采样数据,
在第二个时钟沿(下降沿)输出数据。
MODE2的传输时序:
Mode3:CPOL = 1,CPHA = 1:(在SCK信号线的第二个跳变沿进行采样 )
CPOL =1:
时钟在数据传输前和完成后都保持高电平(时钟空闲电平为高电平)
CPHA =1:
在第二个时钟沿(上升沿)采样数据,
在第一个时钟沿(下降沿)输出数据。
MODE3的传输时序:
设上升沿发送、下降沿接收、高位先发送。
※时钟极性和时钟相位两种选择:
※根据片选信号CS选择从机
※SPI时序
主机和从机初始化就绪
主机寄存器=0xaa=10101010
从机寄存器=0x55 =01010101
第一个上升沿来的时候
MOSI=1;主机寄存器=0101010x
MISO=0;从机寄存器=1010101x
第一个下降沿到来的时候
MOSI传递到从机寄存器中,从机寄存器=0101010MOSI=01010101
MISO传递到主机寄存器中,主机寄存器=0101010MISO=01010100
8个时钟脉冲以后,两个寄存器的内容互相交换一次。
主机寄存器=01010101
从机寄存器=10101010
这样就完成里一个SPI时序。
根据以上分析,一个完整的传送周期是16位,即两个字节。
解释:
主机先发送命令过去,然后从机根据主机的名准备数据,
主机在下一个8位时钟周期才把数据读回来
脉冲 | 主机寄存器master buffer | 从机slave buffer | MISO | MOSI |
0 | 10101010 | 01010101 | 0 | 0 |
第1个脉冲上升沿(发送) | 0101010x | 1010101x | 0 | 1 |
第1个脉冲下降沿(接收) | 01010100 | 10101011 | 0 | 1 |
第2个脉冲上升沿(发送) | 1010100x | 0101011x | 1 | 0 |
第2个脉冲下降沿(接收) | 10101001 | 01010110 | 1 | 0 |
第3个脉冲上升沿(发送) | 0101001x | 1010110x | 0 | 1 |
第3个脉冲下降沿(接收) | 01010010 | 10101101 | 0 | 1 |
第4个脉冲上升沿(发送) | 1010010x | 0101101x | 1 | 0 |
第4个脉冲下降沿(接收) | 10100101 | 01011010 | 1 | 0 |
第5个脉冲上升沿(发送) | 0100101x | 1011010x | 0 | 1 |
第5个脉冲下降沿(接收) | 01001010 | 10110101 | 0 | 1 |
第6个脉冲上升沿(发送) | 1001010x | 0110101x | 1 | 0 |
第6个脉冲下降沿(接收) | 10010101 | 01101010 | 1 | 0 |
第7个脉冲上升沿(发送) | 0010101x | 1101010x | 0 | 1 |
第7个脉冲下降沿(接收) | 00101010 | 11010101 | 0 | 1 |
第8个脉冲上升沿(发送) | 0101010x | 1010101x | 1 | 0 |
第8个脉冲下降沿(接收) | 01010101 | 10101010 | 1 | 0 |
SPI的主、从设备内部均为一个8位的移位寄存器。
实际实现时,可以利用主机的Sclk信号作为移位时钟信号,按照高位在前,低位在后的顺序,依次将总线上的数据送入内部移位寄存器中,从而完成SPI的接收与发送。
1. SCLK采用10MHz
2.模块工作时钟100MHz
3.当接收到的串行数据做串并转换,将并行数据反馈给上一级模块
4、采用 CPOL = 0 ,CPHA = 1
CPOL =0 :
时钟在数据传输前和完成后都保持低电平(时钟空闲电平为低电平)
CPHA =1:
在第一个时钟沿(上升沿)采样数据,
在第二个时钟沿(下降沿)输出数据。
模块时钟CLK,频率F=100MHz , 时钟周期 =10ns
SCLK, 频率f=10MHz , 时钟周期 =100ns (10分频)
- //模块时钟:clk
- //spi时钟:sclk
- //复位信号:rst_n
- //计数时钟个数的计数器:clk_cnt
- //CPOL =0 (时钟在数据传输前和完成后都保持低电平(时钟空闲电平为低电平))
-
- always@(posedge clk or negedge rst_n)
- begin
- if(!rst_n)
- clk_cnt<=0;
- sclk<=0;
- else
- begin
- //(10分频/2-1)
- if(clk_cnt==4'd4)
- begin
- sclk<=~sclk;
- clk_cnt<=0;
- end
- else
- begin
- clk_cnt<=clk_cnt+1;
- end
- end
- //master发送数据
- always@(negedge sclk or negedge rst_n)
- begin
- if(!rst_n)
- mosi<=1'b1;
- else if(spi_done)
- mosi<=1'b1;
- else(!sclk&&!cs)
- begin
- mosi<=master_buf[7];
- end
- end
- //master接收数据
- always@(negedge sclk or negedge rst_n)
- begin
- if(!rst_n)
- master_buf[7:0]<=8'b0;
- else if(spi_done)
- master_buf[7:0]<=8'b0;
- else(!sclk&&!cs)
- begin
- master_buf[7:0]<={master_buf[6:0],miso};
- end
- end
- //slave发送数据
- always@(negedge sclk or negedge rst_n)
- begin
- if(!rst_n)
- miso<=slave_buf[0];
- else if(spi_done)
- miso<=slave_buf[0];
- else(!sclk&&!cs)
- begin
- miso<=slave_buf[7];
- end
- end
- //slave接收数据
- always@(negedge sclk or negedge rst_n)
- begin
- if(!rst_n)
- slave_buf[7:0]<=8'b0;
- else if(spi_done)
- slave_buf[7:0]<=8'b0;
- else(!sclk&&!cs)
- begin
- slave_buf[7:0]<={slave_buf[6:0],mosi};
- end
- end
参考: