0、本文要点思维导图
面向面试学习之SPI篇,本文从理论+代码+问答三个维度展开:牛鼻子问题罗列如下
1.SPI简介与使用的4根逻辑线(功能)
2.SPI数据通信流程:四步走,片选→时钟信号(四种工作模式)→主机收发数据→从机收发数据
3.SPI的工作模式(此问题其实是围绕SPI数据通信流程的第二步展开来的,做到心中有图)
1、简介
1.1 SPI简介
高速(≥10M/bps)+全双工+同步+一主多从+四根线
- MISO( Master Input Slave Output):主设备数据输入,从设备数据输出;
- MOSI(Master Output Slave Input):主设备数据输出,从设备数据输入;
- SCLK(Serial Clock):时钟信号,由主设备产生;
- CS/SS(Chip Select/Slave Select):由主设备控制,拉低某从设备,开始工作
以下为一个标准的SPI连接方式:
- 所有SPI设备的SCK、MOSI、MISO分别连在一起
- 主机另外引出多条SS控制线,分别接到各从机的SS引脚
- 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
1.2 通信方式拓展
通信协议可以按照通信方式分为单工,半双工,全双工三种。
单工:发射端和接收端固定,有一条数据通路,通路上数据单向流动。
半双工:发射端和接收端可变,有一条数据通路,通路上数据双向流动。IIC
全双工:发射端和接收端,有两条数据通路,一条从TX到RX,另一条从RX到TX。SPI、uart
2、通信原理
SPI主设备和从设备都有一个串行移位寄存器,主设备通过向它的SPI串行寄存器写入一个字节来发起一次传输。寄存器是一种用于临时存储数据的内存单元,主打一个读写速度比内存快的多
SPI数据通信的流程可以分为以下四步走,看图说话:
主机进行片选→发送时钟信号(四种工作模式)→主机收发数据→从机收发数据
1、主设备发起信号,将CS/SS拉低,启动通信。
2、主设备通过发送时钟信号,来告诉从设备进行写数据或者读数据操作(采集时机可能是时钟信号的上升沿(从低到高)或下降沿(从高到低),因为SPI有四种模式,后面会讲到),它将立即读取数据线上的信号,这样就得到了一位数据(1bit)。
3、主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(缓存长度不一定,看单片机配置),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。
4、从机(Slave)也将自己的串行移位寄存器(缓存长度不一定,看单片机配置)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。
例如,下图示例中简单模拟SPI通信流程,主机拉低NSS片选信号,启动通信,并且产生时钟信号,上升沿触发边沿信号,主机在MOSI线路一位一位发送数据0X53,在MISO线路一位一位接收数据0X46,如下图所示:
这里有一点需要着重说明一下:SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。若只进行写操作,主机只需忽略接收到的字节(虚拟数据);反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。
需要补充一个图,该图关键信息:高位先行、收发错位、 起始结束信号
3、通信详解
3.1 设备选择
当SPI主设备想读/写从设备时,它首先拉低从设备对应的SS线(SS是低电平有效)。接着开始发送工作脉冲到时钟线上,在相应的脉冲时间上,主设备把信号发到MOSI实现“写”,同时可对MISO采样而实现“读”。如下图所示:
3.2 设备时钟与工作模式
3.2.1 设备时钟
SPI时钟特点主要包括:时钟速率、时钟极性和时钟相位
时钟速率:
SPI总线上的主设备必须在通信开始时候配置并生成相应的时钟信号。从理论上讲,只要实际可行,时钟速率就可以是你想要的任何速率,当然这个速率受限于每个系统能提供多大的系统时钟频率,以及最大的SPI传输速率。
时钟极性:
时钟极性通常写为CKP或CPOL,CKP可以配置为1或0。这意味着你可以根据需要将时钟的默认状态(IDLE)设置为高或低。极性反转可以通过简单的逻辑逆变器实现;
- CKP = 0:时钟空闲IDLE为低电平 0;
- CKP = 1:时钟空闲IDLE为高电平1。
时钟相位:
时钟相位通常写为CKE或CPHA。时钟相位/边沿,也就是采集数据时是在时钟信号的具体相位或者边沿;
- CKE = 0:在时钟信号SCK的第一个跳变沿采样;
- CKE = 1:在时钟信号SCK的第二个跳变沿采样。
3.2.2 工作模式
SPI有4种不同的传输时序,取决于时钟极性CPOL和时钟相位CPHA的组合。
下面看看具体例子:
时钟线空闲时是低电平,数据在第1个跳变沿(上升沿)采样 | 时钟线空闲时是低电平,数据在第2个跳变沿(下降沿)采样 |
时钟线空闲时是高电平,数据在第1个跳变沿(下降沿)采样 | 时钟线空闲时是高电平,数据在第2个跳变沿(上降沿)采样 |
3.2.3 看图说话
实际芯片的Datasheet中,大部分是不会给出SPI时序图工作模式,此时需要根据时钟和数据读取方式判断工作模式。从CLK看支持Mode 0,空闲时低电平,第一个上升沿采样。
时序分析如下:
- CS拉低,开始通信;
- CLK引脚上出现脉冲,在脉冲的边沿上,I/O引脚开始脱离高阻态,最多占用一个时钟周期;
- MOSI引脚发送指令0x0Bh,MISO引脚处于高阻态,占用八个时钟周期;
- MOSI引脚按照大端序发送24位地址,先发送高字节,MISO引脚处于高阻态,共占用24个时钟周期;
- MOSI和MISO均进入高阻态,等待八个时钟周期;
- MOSI进入空闲,MISO上开始出现数据脉冲,在该芯片上,数据按照写入的顺序读出,读出N个字节,占用N*8个时钟周期;
- CS拉高,MOSI/MOSI进入高阻态,CLK引脚回到空闲电平,通信结束。
补充:大端序和小端序
3.3 多从机模式
应该不是很重要:
SPI多从机配置模式
菊花链模式
3.4 SPI优缺点
优点 | 缺点 |
1.无起始位和停止位,因此数据位可以连续传输而不会被中断; | 1.使用四根信号线(I2C和UART使用两根信号线); 2.无法确认是否已成功接收数据(I2C拥有此功能); |
4、代码理解
此处附上一组软件模拟SPI 通信传输数据的代码,方便各位理解其实现的方法。
首先主机和从机都选择同一传输模式。然后主机片选拉低,选中从机。接着在时钟的驱动下, MOSI发送数据,同时MISO读取接收数据。最后完成传输,取消片选。
/*
* 函数名: void SPI_WriteByte(uint8_t data)
* 输入参数: data -> 要写的数据
* 输出参数:无
* 返回值:无
* 函数作用:模拟 SPI 写一个字节
*/ SPI写1 Byte,循环8次,每次发送1 Bit;
void SPI_WriteByte(uint8_t data) {
uint8_t i = 0;
uint8_t temp = 0;
for(i=0; i<8; i++) {
temp = ((data&0x80)==0x80)? 1:0; //将data最高位保存到temp;
data = data<<1; //data左移一位,将次高位变为最高位,用于下次取最高位;
SPI_CLK(0); //CPOL=0 //拉低时钟,即空闲时钟为低电平, CPOL=0;
SPI_MOSI(temp); //根据temp值,设置MOSI引脚的电平;
SPI_Delay(); //简单延时,可以定时器或延时函数实现
SPI_CLK(1); //CPHA=0 //拉高时钟, W25Q64只支持SPI模式0或1,即会在时钟上升沿采样MOSI数据;
SPI_Delay();
}
SPI_CLK(0); //最后SPI发送完后,拉低时钟,进入空闲状态;
}
/*
* 函数名: uint8_t SPI_ReadByte(void)
* 输入参数:
* 输出参数:无
* 返回值:读到的数据
* 函数作用:模拟 SPI 读一个字节
*/ SPI读1 Byte,循环8次,每次接收1 Bit;
uint8_t SPI_ReadByte(void) {
uint8_t i = 0;
uint8_t read_data = 0xFF;
for(i=0; i<8; i++) {
read_data = read_data << 1; //“腾空” read_data最低位,8次循环后,read_data将高位在前;
SPI_CLK(0); //拉低时钟,即空闲时钟为低电平;
SPI_Delay();
SPI_CLK(1);
SPI_Delay();
if(SPI_MISO()==1) {
read_data = read_data + 1;
}
}
SPI_CLK(0); //最后SPI读取完后,拉低时钟,进入空闲状态
return read_data;
}
前面提到SPI传输可以看作一个虚拟的环形拓扑结构,即输入和输出同时进行。在前面“ SPI_WriteByte()”函数里,发送了1 Byte,也应该接收1 Byte,只是代码中忽略了接收引脚MISO的状态; 在前面“ SPI_ReadByte()”函数里,接收了1 Byte,也应该发送1 Byte,只是代码中忽略了发送引脚MOSI的内容。有些场景, SPI需要同时读写,因此还需要编写SPI同时读写函数。
/*
* 函数名: uint8_t SPI_WriteReadByte(uint8_t data)
* 输入参数: data -> 要写的一个字节数据
* 输出参数:无
* 返回值:读到的数据
* 函数作用:模拟 SPI 读写一个字节
*/SPI读和写1 Byte,循环8次,每次发送和接收1 Bit;
uint8_t SPI_WriteReadByte(uint8_t data) {
uint8_t i = 0;
uint8_t temp = 0;
uint8_t read_data = 0xFF;
for(i=0;i<8;i++) {
temp = ((data&0x80)==0x80)? 1:0; //将data最高位保存到temp;
data = data<<1; //data左移一位,将次高位变为最高位,用于下次取最高位;
read_data = read_data<<1; //“腾空” read_data最低位,8次循环后,read_data将高位在前;
SPI_CLK(0);
SPI_MOSI(temp);
SPI_Delay();
SPI_CLK(1);
SPI_Delay();
if(SPI_MISO()==1) { //读取MISO上的数据,保存到当前read_data最低位;
read_data = read_data + 1;
}
}
SPI_CLK(0);
return read_data;
}
5、面试问答
1.什么是SPI?
高速(≥10M/bps)+全双工+同步+一主多从+四根线
2.SPI主从模式有哪些特点?
SPI分为主、从两种模式,提供时钟的为主设备,接收时钟的设备为从设备
SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。
3.SPI如何进行从设备选择
SPI是单主设备( single-master )通信协议,这意味着总线中的只有一支中心设备能发起通信。
当SPI主设备想读/写[从设备]时它首先拉低[从设备]对应的SS线(SS是低电平有效)
接着开始发送工作脉冲到时钟线上,在相应的脉冲时间上,[主设备]把信号发到MOSI实现“写”,同时可对MISO采样而实现“读”
4.SPI通信的四种模式
时钟极性+时钟相位,各种能设置成0和1,组合起来就是4种模式
具体内容根据上面的图片辅助记忆
5.对比一下IIC、UART、SPI、USB
6.spi通信中,两个设备都是主机模式,会出现什么情况
两个设备只有一个启动的话,读写数据都是错误没有用的,
两个设备都启动的话,无法建立通信
7.spi的最大传输速率受什么的影响
主设备的时钟频率
从设备的响应速度
cpu的处理spi数据的能力
输出端的驱动能力(pcb所允许最大信号传输速率)
8.spi输出引脚为什么采用推挽输出
9.主设备如何选择数据传输模式
根据从设备的数据传输模式,主设备要去匹配相应模式,主从匹配才能建立spi通信