一、SPI概述
另一种超级常用的硬件协议就是SPI协议,I2C的速度上限是400Khz,速度想更快点的话可以使用SPI通信。
SPI 是 Motorola 公司推出的一种同步串行接口技术,是一种高速、全双工的同步通信总线, SPI 时钟频率相比 I2C 要高很多,最高可以工作在上百 MHz。 SPI一般为4线也可以是3线。
常见的为四线模式,分别是下面四根线
CS/SS | 片选信号线,选择需要进行通信的从设备。拉低有效。 |
---|---|
SCK | 串行时钟信号线,为SPI提供时钟(主机发出) |
MOSI/SDO | 主出从入信号线,主机向从机发送数据。 |
MISO/SDI | 主入从出信号线,从机向主机发送数据。 |
一、SPI通信硬件连接图
SPI协议的建立需要主机发出SCK信号,主从机的MISO\MOSI连接,下图是个简单的一主三从连接示意图
MOSI:Master Out Slave In,主机数据出从机数据入即从机发主机收
MISO:Master In Slave Out,主机数据入从机数据出即主机发从机收
二、SPI工作模式
与I2C协议不同,SPI有个工作模式的区别,SPI一共有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)两两组合得到。
①、 CPOL=0,串行时钟空闲状态为低电平。
②、 CPOL=1,串行时钟空闲状态为高电平。
③、 CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。
④、 CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。
模式 | 组合 |
---|---|
0 | CPOL=0,CPHA=0 SCL,SDA空闲为低电平,在SCL上升沿采集数据(最常用的模式) |
1 | CPOL=0,CPHA=1 |
2 | CPOL=1,CPHA=0 |
3 | CPOL=1,CPHA=1 |
三、为什么SPI会有这么多种工作模式,而I2C没有?
I2C的硬件连接决定了I2C的模式较为单一,I2C的硬件有两个上拉的4.7K电阻,所以I2C总线上,默认的空闲状态为SDA和SCL都为高
即为空闲模式
相位的概念:
相位描述的比较抽象了,同步通信是共用一个时钟源SCL的,一个时钟周期内只有一个上升沿一个下降沿,数据的变化都是在边沿发生的,边沿可能是上升沿也可能是下降沿,可能是前一个沿也可能是后一个沿,所以区分相位,参考下图四种模式的图解,这里我建议不理解的记住模式0就行,后面实战中用几次就会明白了。
四、SPI时序,以模式0详细讲解
①.CS片选拉低,整个通讯周期都是拉低,拉高意味结束
②.此时主机上的SCLK和MOSI和MISO都是拉低的,空闲状态
③.SCLK拉高,拉高期间,MOSI和MISO上的数据变化都是有效的,MOSI发送了高电平到从机,MISO接受到了从机的低电平,
数据发送了8个bit
④.CS拉高了,通信结束,收发各8bit
最终通信数据:
主机发从机:11010010
从机发主机:01100110
五、关于模式1,2,3
附录SPI通信代码
个人项目上的SPI裸机代码,保证可用,很多刚接触单片机的搞不清主从机概念,简单来说主机就是你的MCU,从机就是你要通信的芯片,
一般从机可能是FLASH芯片,DAC,ADC芯片等等,也有可能是两个mcu之间的通信但是得看你的mcu是否支持从机模式,两个MCU之间通信的方式太多了,使用SPI我感觉是有点麻烦了。485会是个不错的选择。
代码如下
// SPI 发送数据代码
static void spiBusSendData(uint8_t data)
{
uint8_t i = 0;
for (i = 0; i < 8; i++)
{
(data & 0x80) ? writeGpioPin(Board_SPI_FLASH_CPLD_MOSI, 1) : writeGpioPin(Board_SPI_FLASH_CPLD_MOSI, 0); //如果data最高位为1写入1否则写入0
data <<= 1; //写入1位后左移1位,次高位变成最高位
writeGpioPin(Board_SPI_FLASH_CPLD_CLK, 1);
CPUdelay(1);/* 3 cycles per loop: 1 loop @ 48 Mhz ~= 62 ns */
writeGpioPin(Board_SPI_FLASH_CPLD_CLK, 0);
CPUdelay(1);/* 3 cycles per loop: 1 loop @ 48 Mhz ~= 62 ns */
}
}
// SPI写数据代码
static uint8_t spiBusRecvData(void)
{
uint8_t i = 0;
uint8_t data = 0;
for (i = 0; i < 8; i++)
{
writeGpioPin(Board_SPI_FLASH_CPLD_CLK, 1);
CPUdelay(1);/* 3 cycles per loop: 1 loop @ 48 Mhz ~= 62 ns */
data <<= 1;
if (readGpioPin(Board_SPI_FLASH_CPLD_MISO))
{
data |= 0x01;
}
writeGpioPin(Board_SPI_FLASH_CPLD_CLK, 0);
CPUdelay(1);/* 3 cycles per loop: 1 loop @ 48 Mhz ~= 62 ns */
}
return data;
}