SPI通信
- SPI (串行外设接口,Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
- 四根通信线:
- SCK (Serial Clock) 串行时钟线 或者称为SCLK、CLK、CK
- MOSI (Master Output Slave Input) 主机输出从机输入 或者称为 DO(Data Output)
- MISO (Master Input Slave Output) 主机输入从机输出 或者称为 DI(Data Input)
- SS (Slave Select) 从机选择 或者称为 NSS(Not Slave Select)、CS(Chip Select)
- 同步,全双工
- 支持总线挂载多设备(一主多从) 仅支持一主多从,不支持多主机
SPI相较IIC的优缺点
- 1.SPI传输更快,SPI协议并没有严格规定最大传输速度,这个最大传输速度取决于芯片厂商的设计需求
- 2.SPI的设计比较简单粗暴,实现的功能没有IIC那么多
- 3.SPI的硬件卡开销比较大,通信线的个数比较多,并且通信过程中,经常会有资源浪费的现象
硬件电路
- 所有SPI设备的SCK、MOSI、MISO分别连在一起
- 主机另外引出多条SS控制线,分别接到各从机的SS引脚
- 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
SS线是低电平有效,主机想和谁通信,就把对应的SS输出线置低电平就行了,所以刚开始初始化的时候把所有的SS线置高电平,即1,这样就谁都不指定,通信结束之后,再置为高电平
当然同一时间,主机只能置一个SS为低电平,只能选中一个从机,否则,如果主机同时选中多个从机,就会导致数据冲突
这里为什么IIC不使用推挽输出获得更快的传输速度呢?
推挽输出,高低电平都有很强的驱动能力,这将使得SPI引脚信号的下降沿,非常迅速,上升沿,也非常迅速,SPI信号变化的快,那自然它就能达到更高的传输速度,一般SPI信号都能轻松达到MHZ的速度级别
然而这里IIC并不是不想使用更快的推挽输出,而是I2C要实现半双工,经常要切换输入输出,另外I2C又要实现多主机的时钟同步和总线仲裁,这些功能,都不允许I2C使用推挽输出
在这里还有一个问题,就是如果三个从机都始终是推挽输出,势必会导致冲突,所以在SPI协议里,有一条规定,当从机的SS引脚为高电平,也就是从机未被选中时,它的MISO引脚,必须切换为高阻态,这样就可以防止,一条线有多个输出,而导致的电平冲突的问题了,在SS为低电平时,MISO才被允许变为推挽输出,当然这个切换过程都是在从机里,我们都是一般写主机程序,并不需要关注这个问题
移位示意图
SPI一般都是高位先行的,所以波特率发生器也就是时钟源SCK,每来一个时钟,移位寄存器都会向左进行移位,从机中的移位寄存器也是同理
在时钟波形图中,当处于上升沿时,主机将高位数据输出到MOSI通信线上,从机也把最高位输出到MISO通信线上,然后下降沿进行数据采样输入,MOSI线上的数据就输出到从机的移位寄存器中,同样,MISO线上的数据输入到主机的移位寄存器中,这样就完成了一次一位数据的交换,这样的步骤重复8次也就是8个时钟后,那么就可以交换一个字节数据了
- 如果只想发送数据,不接受数据,那么主机只管发送数据即可,不去读取从机发送过来的数据即可
- 如果只想接收数据,那么主机只管随便发送一个字节数据,把从机的数据交换过来就可以了,通常都是发送
0x00
或0xFF
SPI时序基本单元
起始和终止条件
- 起始条件:SS从高电平切换到低电平
- 终止条件:SS从低电平切换到高电平
传输单元
什么时候开始移位?是上升沿移位还是下降沿移位?SPI并没有限定死,给了我们可以配置的选择,这样的话,SPI就可以兼容更多的芯片。
SPI有两个可以配置的位CPOL(时钟极性,Clock Polarity)和CPHA(时钟相位,Clock Phase),每一位可以配置为0和1,这样就可以有四种模式,但功能都是一样的
- 交换一个字节 (模式0)
- GPOL=0:空闲状态时,SCK为低电平
- CPHA=0: SCK第一个边沿移入数据,第二个边沿移出数据
- 交换一个字节(模式1)
- CPOL=0:空闲状态时,SCK为低电平
- CPHA=1: SCK第一个边沿移出数据,第二个边沿移入数据
- 交换一个字节 (模式2)
- CPOL=1:空闲状态时,SCK为高电平
- CPHA=0: SCK第一个边沿移入数据,第二个边沿移出数据
- 交换一个字节(模式3)
- CPOL=1:空闲状态时,SCK为高电平
- CPHA=1: SCK第一个边沿移出数据,第二个边沿移入数据
IIC规定一般是有效数据流第一个字节是寄存器地址,之后依次是读写的数据,使用的是读写寄存器的模型
而SPI使用的是指令码加读写数据模型,第一个发送交换的数据,一般叫作指令码,在从机中,对应的会定义一个指令集,当我们需要发送什么指令时,就可以在起始后第一个字节发送指令集里面的数据,这样就能指导完成相应的功能了,不同的指令,可以有不同的数据个数,有的指定,只需要一个字节的指令码就可以完成,比如W25Q64(存储器模块)的写使能,写失能等指令,而有的指令,后面就需要再跟要读写的数据,比如W25Q54的写数据、读数据等,写数据,指令后面就得跟上,我要在哪里写,写什么