一、SPI通信简介
-
SPI ( Serial Peripheral Interface )是由 Motorola 公司开发的一种通用数据总线
-
四根通信线:
-
SCK ( Serial Clock )串行时钟线
-
MOSI ( Master Output Slave Input )主机输出从机输入(主机向从机发送数据)
-
MISO ( Master Input Slave Output )主机输入从机输出(主机从从机读取数据)
-
SS ( Slave Select )从机选择
-
专门用于指定从机(有几个从机就开几条SS)(但可能造成资源浪费)
-
-
-
同步,全双工
-
支持总线挂载多设备(一主多从)
//I2C还支持多主多从(多主机模式)
二、硬件电路
-
所有 SPI 设备的 SCK 、 MOSI 、 MISO 分别连在一起
-
主机另外引出多条 SS 控制线,分别接到各从机的 SS 引脚
-
【输出】引脚配置为【推挽输出】,【输入】引脚配置为【浮空或上拉输入】
-
推挽输出:高低电平均有很强的驱动能力,将使得SPI引脚信号的上升/下降沿非常迅速(因此传输速度更高)
-

//SPI主机实际引出了6根通信线:因为有3个从机,所以SS线需要3根;再加SCK、MOSI、MISO总共6根通信线
-
SCK时钟线:时钟线完全由主机掌控
-
对于主机来说时钟线为输出
-
对于从机来说时钟线为输入
-
-
MOSI主机输出从机输入:主机通过MOSI输出,从机通过MOSI输入
-
MISO主机输入从机输出:主机通过MISO输入,从机从过MISO输出
-
当从机的SS引脚为高电平(未被主机选中),从机的MISO引脚必须切换为【高阻态】
-
//相当于引脚断开,不输出任何电平——防止一条线上有多个输出而导致电平冲突
-
在从机的SS引脚为低电平(被主机选中),从机的MISO引脚才允许变为【推挽输出】
-
SS从机选择:主机的SS线都是输出,从机的SS线都是输入
-
SS线低电平有效(主机需要指定时,将对应的SS输出线置低电平就行)
-
主机初始化之后所有SS线输出线都置高电平,即谁也不指定
-
同一时间主机只能置一个SS为低电平(只能选中一个从机)
-
三、SPI基本收发电路示意图

-
移位寄存器的时钟源由主机提供(波特率发生器),波特率发生器产生的时钟驱动主机的移位寄存器进行移位
-
同时这个时钟也通过【SCK】引脚进行输出,接到从机的移位寄存器里
-
-
规定:在波特率发生器时钟的【上升沿】,所有移位寄存器 向左移动一位(移出最高位)放到引脚上
-
规定:在波特率发生器时钟的【下降沿】,引脚上的位采样输入到移位寄存器的【最低位】
-
SPI为【高位先行】,每来一个时钟,两个移位寄存器都会向左进行移位
-
【主机移位寄存器】 左边移出去的数据通过【MOSI】引脚输入到【从机移位寄存器】的 右边
-
【从机移位寄存器】 左边移出去的数据通过【MISO】引脚输入到【主机移位寄存器】的 右边
四、SPI时序基本单元
-
起始条件: SS 从高电平切换到低电平(下降沿)
-
终止条件: SS 从低电平切换到高电平(上升沿)

//数据传输基本单元
-
基于【SPI基本收发电路模型】
-
什么时候开始移位?上升沿移位 or 下降沿移位?——可自行配置SPI选择(两个配置位,总共可组成4种模式)
-
CPOL(Clock Polarit)时钟极性【1/0】
-
CPHA(Clock Phase)时钟相位【1/0】:决定第几个边沿采样
-
有模式0、1、2、3;模式虽多,但功能相同
-
-
//注意:
-
SS高电平时,MISO用一条中间的线表示【高阻态】
-
当从机的SS引脚为高电平(未被主机选中),从机的MISO引脚必须切换为【高阻态】
-
相当于引脚断开,不输出任何电平——防止一条线上有多个输出而导致电平冲突
-
-
-
SS低电平时,从机的MISO被允许开启输出
-
在从机的SS引脚为低电平(被主机选中),从机的MISO引脚才允许变为【推挽输出】
-
模式0:(应用最多,重点掌握)
-
交换一个字节(模式 0 )
-
CPOL=0 :空闲状态时, SCK 为低电平
-
CPHA=0 : SCK 第一个边沿移入数据,第二个边沿移出数据
//与【模式1】区别:【模式0】数据移入移出时机提前半个时钟(相位提前)

-
在SS未被选中时(SS高电平),SCK默认为【低电平】
-
通信开始前SS为【高电平】,通信过程中SS始终保持【低电平】,通信结束SS恢复【高电平】
//数据在第一个边沿之前就要提前【移出】以确保在SCK第一个边沿(上升沿)能够有空间【移入】数据
-
移位传输流程
-
趁SCK没有变化,在SS下降沿时就要立刻将数据【移位输出】
-
SCK 第一个边沿(上升沿)主机和从机同时移入数据 (进行数据采样)
-
主机通过MOSI移入最低位(B7)
-
从机通过MISO移入最低位(B7)
-
-
第二个边沿(下降沿)主机和从机同时移出数据
-
主机刚刚移出的B7进入【从机移位寄存器】的最高位
-
从机刚刚移出的B7进入【主机移位寄存器】的最高位
-
-
-
到此一个时钟脉冲产生完毕,一个数据位传输完毕;反复8次就完成了一个字节的数据交换
//如果主机只想交换一个字节,那么此时置SS为【高电平】就结束通信了
-
在SCK的下降沿,MOSI还可以再变化一次置默认的高or低电平(SPI未硬性规定)
-
SS高电平时,MISO置【高阻态】
//如果主机想交换多个字节,主机不必将SS置【高电平】,只需重复【SS低电平】之后交换一个字节的时序即可
-
在SCK的下降沿,MOSI和MISO还需要再变化一次(提前移入下一个字节的B7)
-
相位提前,因此下个字节的数据会在前一个传输时序的最后露头
-
方便下一个字节传输时序第一个SCK上升沿来移入数据
-
-
模式1:与【三、SPI基本收发电路】模型对应
-
交换一个字节(模式 1 )
-
CPOL=0 :空闲状态时, SCK默认 为低电平
-
CPHA=1 : SCK 第一个边沿移出数据,第二个边沿移入数据

-
在SS未被选中时(SS高电平),SCK默认为【低电平】
-
通信开始前SS为【高电平】,通信过程中SS始终保持【低电平】,通信结束SS恢复【高电平】
-
移位传输流程
-
SCK 第一个边沿(上升沿)主机和从机同时移出数据
-
主机通过MOSI移出最高位(B7)
-
从机通过MISO移出最高位(B7)
-
-
第二个边沿(下降沿)主机和从机同时移入数据(进行数据采样)
-
主机刚刚移出的B7进入【从机移位寄存器】的最低位
-
从机刚刚移出的B7进入【主机移位寄存器】的最低位
-
-
-
到此一个时钟脉冲产生完毕,一个数据位传输完毕;反复8次就完成了一个字节的数据交换
//如果主机只想交换一个字节,那么此时置SS为【高电平】就结束通信了
-
在SS的上升沿,MOSI还可以再变化一次置默认的高or低电平(SPI未硬性规定)
-
SS高电平时,MISO置【高阻态】
//注意:
-
SS高电平时,MISO用一条中间的线表示【高阻态】
-
当从机的SS引脚为高电平(未被主机选中),从机的MISO引脚必须切换为【高阻态】
-
相当于引脚断开,不输出任何电平——防止一条线上有多个输出而导致电平冲突
-
-
-
SS低电平时,从机的MISO被允许开启输出
-
在从机的SS引脚为低电平(被主机选中),从机的MISO引脚才允许变为【推挽输出】
-
//如果主机想交换多个字节,主机不必将SS置【高电平】,只需重复【SS低电平】之后交换一个字节的时序即可
模式2
-
交换一个字节(模式 2 )
-
CPOL =1 :空闲状态时, SCK 为高电平
-
CPHA =0 : SCK 第一个边沿移入数据,第二个边沿移出数据

//与【模式0】相比SCK极性取反
模式3
-
交换一个字节(模式 3 )
-
CPOL =1 :空闲状态时, SCK 为高电平
-
CPHA =1 : SCK 第一个边沿移出数据,第二个边沿移入数据

//与【模式1】相比SCK极性取反
五、SPI时序
SPI对字节流功能的规定:采用【指令码+读写数据】模型
-
SPI起始后,第一个交换发送给从机的数据叫作【指令码】
-
在从机中对应有一个【指令集】,在起始后第一个字节发送【指令集】里面的数据,就能指导从机完成相应功能
-
-
不同指令可以有不同的数据个数
-
有的指令只需要一个字节的指令码就可以完成
-
有的指令后面需要再跟需要读写的数据
-
//区别I2C:规定有效数据流第一个字节是寄存器地址,之后依次是读写的数据,采用【读写寄存器】模型
基于【SPI模式0】
发送指令
-
向 SS 指定的设备,发送指令( 0x06 )
-
在【W25Q64芯片】中0x06代表写使能
-

//空闲状态时SS为【高电平】、SCK为【低电平】、MOSI和MISO的默认电平没有严格规定
//SCK低电平是【变化】的时期,SCK高电平是【读取】的时期
-
SS产生下降沿,时序开始
-
下降沿时刻,MOSI和MISO变换数据
-
MOSI,由于指令码最高位仍然是0,此处保持不变
-
MISO,从机现在没有数据发给主机,因为STM32的MISO是上拉输入,所以此处MISO为高电平
-
-
-
SCK第一个上升沿进行数据采样(移入数据),主机输入得到1,从机输入得到0
-
下降沿时刻,MOSI和MISO变换数据
-
-
之后的时序不变,当主机要发送数据1时序开始变化
-
下降沿时刻数据移出,主机将1移出到MOSI,MOSI为高电平
-
上升沿进行数据采样(移入数据),主机输入得到1,从机输入得到1
-
-
之后的时序不变,当主机要发送数据0时序开始变化
-
下降沿时刻数据移出,主机将0移出到MOSI,MOSI为低电平
-
上升沿进行数据采样(移入数据),主机输入得到1,从机输入得到0
-
-
总结:主机用【0x06】换来了从机的【0xFF】
-
实际上从机并没有输出任何东西,【0xFF】是默认的高电平——达到主机写入从机的目的
-
指定地址写
-
向 SS 指定的设备,发送写指令( 0x02 ),随后在指定地址(Address[23:0])下,写入指定数据(Data)

//空闲状态时SS为【高电平】、SCK为【低电平】、MOSI和MISO的默认电平没有严格规定
//SCK低电平是【变化】的时期,SCK高电平是【读取】的时期
-
SS产生下降沿,时序开始
-
下降沿时刻,MOSI和MISO变换数据
-
MOSI,由于指令码最高位仍然是0,此处保持不变
-
MISO,从机现在没有数据发给主机,因为STM32的MISO是上拉输入,所以此处MISO为高电平
-
-
-
SCK第一个上升沿进行数据采样(移入数据),主机输入得到1,从机输入得到0
-
下降沿时刻,MOSI和MISO变换数据
-
-
之后的时序不变,当主机要发送数据1时序开始变化
-
下降沿时刻数据移出,主机将1移出到MOSI,MOSI为高电平
-
上升沿进行数据采样(移入数据),主机输入得到1,从机输入得到1
-
-
之后的时序不变,当主机要发送数据0时序开始变化
-
下降沿时刻数据移出,主机将0移出到MOSI,MOSI为低电平
-
上升沿进行数据采样(移入数据),主机输入得到1,从机输入得到0
-
-
总结:主机用【0x02】换来了从机的【0xFF】
-
实际上从机并没有输出任何东西,【0xFF】是默认的高电平——达到主机写入从机的目的
-
//发出写字节指令【0x02】,之后跟着写字节的地址和具体数据
-
由于是【模式0】(相位提前)——在最后一个SCK下降沿,将下一个字节的最高位提前放到MOSI上
-
确保下一个字节时序开始时能直接移入数据
-
-
下一个字节的最高位仍然是0
-
SCK第一个上升沿进行数据采样(移入数据),主机输入得到1,从机输入得到0
-
下降沿时刻,MOSI和MISO变换数据
-
-
之后的时序不变,当主机要发送数据1时序开始变化
-
下降沿时刻数据移出,主机将1移出到MOSI,MOSI为高电平
-
上升沿进行数据采样(移入数据), 主机输入得到1,从机输入得到1
-
-
之后的时序不变,当主机要发送数据0时序开始变化
-
下降沿时刻数据移出,主机将0移出到MOSI,MOSI为低电平
-
上升沿进行数据采样(移入数据), 主机输入得到1,从机输入得到0
-
-
之后的时序不变,当主机要发送数据0时序开始变化
-
下降沿时刻数据移出,主机将0移出到MOSI,MOSI为低电平
-
上升沿进行数据采样(移入数据), 主机输入得到1,从机输入得到1
-
-
总结:主机用【0x12】换来了从机的【0xFF】
-
实际上从机并没有输出任何东西,【0xFF】是默认的高电平——达到主机写入从机的目的
-
//根据【W25Q64芯片】的规定,写指令之后的字节定义为地址高位, 因此【0x12】表示发送地址的【23~16位】
之后两个字节发送时序分别发送了数据【0x34】、【0x56】
————从机得到指定地址【0x123456】
-
发送写入从机指定地址的内容
-
调用发送一个字节数据【0x55】,表示主机在从机【0x123456】地址下写入数据【0x55】
-
//如果主机只想交换一个字节,那么此时置SS为【高电平】就结束通信了
//如果主机想交换多个字节,主机不必将SS置【高电平】,只需重复【SS低电平】之后交换一个字节的时序即可
-
在SCK的下降沿,MOSI和MISO还需要再变化一次(提前移入下一个字节的B7)
-
相位提前,因此下个字节的数据会在前一个传输时序的最后露头
-
方便下一个字节传输时序第一个SCK上升沿来移入数据
-
-
-
SPI里也有和I2C一样的地址指针,每读写一个字节,地址指针自动加1
//发送一个字节不终止,继续发送的字节就会依次写入到后续的存储空间里
//另外可以看到,由于整个流程只需要主机发送功能,并没有主机接收的需求,所以MISO线路始终“挂机”
指定地址读
-
向 SS 指定的设备,发送读指令( 0x03 ),随后在指定地址(Address[23:0])下,读取从机数据(Data)

//空闲状态时SS为【高电平】、SCK为【低电平】、MOSI和MISO的默认电平没有严格规定
//SCK低电平是【变化】的时期,SCK高电平是【读取】的时期
-
SS产生下降沿,时序开始
-
下降沿时刻,MOSI和MISO变换数据
-
MOSI,由于指令码最高位仍然是0,此处保持不变
-
MISO,从机现在没有数据发给主机,因为STM32的MISO是上拉输入,所以此处MISO为高电平
-
-
-
SCK第一个上升沿进行数据采样(移入数据),主机输入得到1,从机输入得到0
-
下降沿时刻,MOSI和MISO变换数据
-
-
之后的时序不变,当主机要发送数据1时序开始变化
-
下降沿时刻数据移出,主机将1移出到MOSI,MOSI为高电平
-
上升沿进行数据采样(移入数据), 主机输入得到1,从机输入得到1
-
-
之后的时序不变,当主机要发送数据0时序开始变化
-
下降沿时刻数据移出,主机将0移出到MOSI,MOSI为低电平
-
上升沿进行数据采样(移入数据), 主机输入得到1,从机输入得到0
-
-
总结:主机用【0x03】换来了从机的【0xFF】
-
实际上从机并没有输出任何东西,【0xFF】是默认的高电平——达到主机读取从机的目的
-
//发出读字节指令【0x03】,之后跟着读字节的地址和具体数据
-
由于是【模式0】(相位提前)——在最后一个SCK下降沿,将下一个字节的最高位提前放到MOSI上
-
确保下一个字节时序开始时能直接移入数据
-
-
下一个字节的最高位仍然是0
-
SCK第一个上升沿进行数据采样(移入数据),主机输入得到1,从机输入得到0
-
下降沿时刻,MOSI和MISO变换数据
-
-
之后的时序不变,当主机要发送数据1时序开始变化
-
下降沿时刻数据移出,主机将1移出到MOSI,MOSI为高电平
-
上升沿进行数据采样(移入数据), 主机输入得到1,从机输入得到1
-
-
之后的时序不变,当主机要发送数据0时序开始变化
-
下降沿时刻数据移出,主机将0移出到MOSI,MOSI为低电平
-
上升沿进行数据采样(移入数据), 主机输入得到1,从机输入得到0
-
-
之后的时序不变,当主机要发送数据0时序开始变化
-
下降沿时刻数据移出,主机将0移出到MOSI,MOSI为低电平
-
上升沿进行数据采样(移入数据), 主机输入得到1,从机输入得到1
-
-
总结:主机用【0x12】换来了从机的【0xFF】
-
实际上从机并没有输出任何东西,【0xFF】是默认的高电平——达到主机写入从机的目的
-
//根据【W25Q64芯片】的规定,写指令之后的字节定义为地址高位, 因此【0x12】表示发送地址的【23~16位】
之后两个字节发送时序分别发送了数据【0x34】、【0x56】
————从机得到指定地址【0x123456】
-
指定地址之后,主机开始接收数据
-
三个指定地址的字节结束后,主机用【0xFF】换取从机的【0x55】
-
实际上主机并没有输出任何东西,【0xFF】是默认的高电平——达到主机读取从机的目的
-
-
//如果主机需要读取多个从机数据,则继续将MOSI线路拉高以换取从机数据即可
-
最后数据传输完毕,SS置【高电平】,时序结束