目录

一、介绍
1.SPI(Serial Peripheral Interface),顾名思义就是串行外围设备接口。它由摩托罗拉公司在20世纪80年代开发,并且已经成为一种广泛采用的工业标准。。
2.SPI是一种高速的,全双工,同步的通信协议(可以同时发出和接收串行数据)
3.SPI接口有两根单向数据线,速率较高(比I²C和UART等协议更快),SPI协议主要用于在短距离内进行设备通信,
二、SPI特点
-
主从模式:SPI采用主设备(Master)与从设备(Slave)结构,意味着有一个主设备控制通信的时钟信号(控制通信过程),而一个或多个从设备响应主设备的请求。
-
一次通信时,只有一个主设备,但可以有多个从设备,通过片选信号来选择与哪个从设备通信。
-
同步传输:SPI通信依赖时钟信号(由主设备提供),从设备按照这个时钟信号同步发送和接收数据。时钟信号确保通信的时序一致性。
4. 无确认机制:SPI通信不像I²C那样有明确的错误检测和纠错机制(如ACK/NACK信号),因此需要在设计时考虑错误处理问题。,数据完整性需要其他手段保障。
-
提供频率可编程时钟;SPI的频率是主设备的时钟源频率经过分频器后的结果.
-
发送结束中断标志;
SPI通信过程中,主设备和从设备的数据是通过移位寄存器传输的。为了提高效率并进行事件处理,SPI控制器通常会提供一个发送结束中断标志(Transmit Complete Flag),用于通知系统传输已经完成。当SPI完成一次完整的数据传输,发送结束标志位会被置位。触发一个中断信号,通知主设备传输完成,从而避免轮询SPI状态寄存器,节省处理器资源。 -
写冲突保护;
写冲突保护(Write Collision Protection)是SPI通信中的一种机制,用于防止在不适当的时刻向SPI数据寄存器(SPDR)写入数据,从而避免数据丢失或错误的发生。写冲突保护通过检测SPI数据寄存器的状态来防止此类冲突的发 -
总线竞争保护;
总线竞争(Bus Contention)通常发生在多主设备系统中,即多个主设备同时试图控制SPI总线的情况。如果两个主设备同时生成时钟信号并传输数据,会导致数据传输错误,甚至可能损坏系统。因此,需要一种机制来防止总线竞争。
硬件方法:通过硬件上的总线仲裁机制确保只有一个主设备能够访问总线。如果多个主设备同时试图访问总线,硬件会自动仲裁,优先级较高的主设备获得控制权,而其他主设备必须等待。
软件方法:通过在主设备中加入总线占用检测逻辑。当一个主设备占用总线时,其他主设备会检测到这一状态,并推迟自己的传输请求,直到总线空闲
三、SPI的基本信号线
SPI总线是一种4线总线,因其硬件功能很强,所以与SPI有关的软件就相当简单,使中央处理器有更多的时间处理其他事务。
-
SCLK(Serial Clock,串行时钟)
由主设备产生的时钟信号,用于同步主设备和从设备的数据传输。SCLK的频率决定了通信速度,并需确保连接的从设备能够支持指定的时钟频率。例如,在STM32设备上,SPI最大时钟频率可达到fpclk/2。 -
MOSI(Master Out Slave In,主出从入)
主设备到从设备的数据传输线。MOSI线上数据的方向是从主设备输出到从设备输入,用于发送指令或数据。 -
MISO(Master In Slave Out,主入从出)
从设备到主设备的数据传输线。MISO线上数据的方向是从从设备输出到主设备输入,通常用于从设备向主设备发送响应或数据。 -
SS/CS(Slave Select/Chip Select,从设备选择/片选)
用于选择要通信的从设备。当SS/CS信号线拉低时,相应从设备被选中与主设备通信;当SS/CS信号线拉高时,从设备将退出通信。多从设备系统中,主设备可以控制多根SS/CS信号线来选择不同的从设备。对于单从设备,只需将CS线拉低即可开始通信。
四、SPI 协议层
SPI协议层定义了通信的起始和停止信号、数据有效性、时钟同步等细节,以确保主设备和从设备间的数据可靠传输。
-
NSS信号
(1).NSS(或称CS/SS)信号由高变低,表示SPI通信的起始信号。每个从设备都拥有独立的NSS信号线,当从设备检测到NSS线从高变低时,即明白自己被主设备选中,准备进行数据交互。
(2).当NSS信号由低变高,表示通信结束,从设备的选中状态被取消。 -
数据传输和同步
(1).SPI使用MOSI和MISO数据线传输数据,并通过SCLK信号线同步数据传输。数据在SCLK的每个时钟周期传输一位,输入输出同步进行。
(2).数据有效性由SCLK的时钟边沿决定。通常,在SCLK的下降沿采样数据,而上升沿则用于数据变化和传输。数据的高电平表示“1”,低电平表示“0”。在非采样时刻,MOSI和MISO线为下一次数据传输做准备。 -
数据格式和长度
(1).SPI的每次传输可以以8位或16位为单位进行,没有固定的传输长度,用户可根据需求配置。
(2).数据传输的顺序(MSB先行或LSB先行)由通信双方约定,并确保一致。
4、数据传输格式
SPI支持四种模式的时钟配置,以满足不同外设的需求。这些模式由CPOL(时钟极性)和CPHA(时钟相位)参数组合而成:
CPOL(Clock Polarity):定义时钟空闲时的电平。
CPHA(Clock Phase):定义数据采样的时钟边沿,决定数据在上升沿或下降沿采样。
SPI的四种模式(模式0到模式3)通过不同的CPOL和CPHA配置,为用户提供了灵活的传输时序设置。
五、SPI通信过程
1. 启动通信:主设备将SS/CS拉低,选择一个从设备。
2. 数据传输:主设备通过MOSI线发送数据,并通过MISO线接收从设备的数据,同时SCLK提供时钟同步。
3. 结束通信:当通信完成后,主设备将SS/CS拉高,终止与该从设备的通信。
4.重复或改变片选:主设备可以重复上述步骤与同一个从设备通信,或者改变CS/SS线的状态来与另一个从设备通信。
六、实例操作(SPI协议的OLED屏显示)

引脚说明:
D0(SCLK,Serial Clock,串行时钟):时钟信号,用于同步数据传输。
D1(MOSI,Master Out Slave In,主出从入):主设备数据输出,从设备数据输入的引脚。
RES(Reset,复位):用于对显示屏进行初始化复位操作
DC(Data/Command,数据/命令选择):选择传输的数据类型:高电平表示传输的是显示数据,为低电平表示传输的是控制命令
CS(Chip Select,片选):选择当前显示屏是否参与SPI通信。低开高关。
//-----------------OLED端口定义----------------
#define OLED_SCLK_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_5)
#define OLED_SCLK_Set() GPIO_SetBits(GPIOA,GPIO_Pin_5)
#define OLED_SDIN_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_7)//DIN
#define OLED_SDIN_Set() GPIO_SetBits(GPIOA,GPIO_Pin_7)
#define OLED_RST_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_0)//RES
#define OLED_RST_Set() GPIO_SetBits(GPIOB,GPIO_Pin_0)
#define OLED_DC_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_1)//DC
#define OLED_DC_Set() GPIO_SetBits(GPIOB,GPIO_Pin_1)
#define OLED_CS_Clr() GPIO_ResetBits(GPIOA,GPIO_Pin_4)//CS
#define OLED_CS_Set() GPIO_SetBits(GPIOA,GPIO_Pin_4)
#define OLED_CMD 0 //写命令
#define OLED_DATA 1 //写数据
编写写入数据或命令到OLED显示屏的函数
// dat(一个8位要发送的数据)
//cmd(命令类型标志,cmd=1 表示发送的是数据,cmd=0 表示发送的是命令。)
void OLED_WR_Byte(u8 dat, u8 cmd)
{
u8 i;
if(cmd)
OLED_DC_Set(); // 如果cmd为1,将DC引脚置高,表示发送数据
else
OLED_DC_Clr(); // 如果cmd为0,将DC引脚置低,表示发送命令
OLED_CS_Clr();// 拉低CS引脚,选择OLED设备开始通信
// 通过SPI逐位发送8位数据
for(i = 0; i < 8; i++)
{
OLED_SCLK_Clr(); // 将时钟信号置低,准备传输一位数据
if(dat & 0x80) // 检查dat的最高位 (MSB)
OLED_SDIN_Set(); // 如果最高位为1,将SDIN引脚置高
else
OLED_SDIN_Clr(); // 如果最高位为0,将SDIN引脚置低
OLED_SCLK_Set(); // 将时钟信号置高,通知从设备读取该位数据
dat <<= 1; // 将dat左移一位,准备发送下一位数据
}
// 拉高CS引脚,结束本次传输
OLED_CS_Set();
// 将DC引脚重置为高电平,可能是为下次数据传输做准备
OLED_DC_Set();
}
是的,有了这个基础的数据传输函数 OLED_WR_Byte(),根据使用手册,发送相关指令,我们就可以使用它来与OLED屏幕进行通信,例如执行屏幕旋转这样的功能。

//屏幕旋转180度
void OLED_DisplayTurn(u8 i)
{
if(i==0)
{
OLED_WR_Byte(0xC8,OLED_CMD);//正常显示
OLED_WR_Byte(0xA1,OLED_CMD);
}
if(i==1)
{
OLED_WR_Byte(0xC0,OLED_CMD);//反转显示
OLED_WR_Byte(0xA0,OLED_CMD);
}
}
进行字符显示:
//画点
//x:0~127
//y:0~63
void OLED_DrawPoint(u8 x,u8 y)
{
u8 i,m,n;
i=y/8;
m=y%8;
n=1<<m;
OLED_GRAM[x][i]|=n;
}
//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//size:选择字体 12/16/24
//取模方式 逐列式
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1)
{
u8 i,m,temp,size2,chr1;
u8 y0=y;
size2=(size1/8+((size1%8)?1:0))*(size1/2); //得到字体一个字符对应点阵集所占的字节数
chr1=chr-' '; //计算偏移后的值
for(i=0;i<size2;i++)
{
if(size1==12)
{temp=asc2_1206[chr1][i];} //调用1206字体
else if(size1==16)
{temp=asc2_1608[chr1][i];} //调用1608字体
else if(size1==24)
{temp=asc2_2412[chr1][i];} //调用2412字体
else return;
for(m=0;m<8;m++) //写入数据
{
if(temp&0x80)OLED_DrawPoint(x,y);
else OLED_ClearPoint(x,y);
temp<<=1;
y++;
if((y-y0)==size1)
{
y=y0;
x++;
break;
}
}
}
}
结合之前所写的GY39代码(IIC协议),我们使用OLED对其采集的环境数据进行显示:

总之,SPI以其高效、可靠的特性在嵌入式系统中得到广泛应用,适合那些对传输速度要求较高、通信距离较短的场景。
SPI以简单的硬件结构和高效的数据传输方式在嵌入式开发中扮演了重要角色,从屏幕显示到传感器通信,都离不开SPI的支持。它的全双工通信、可灵活配置的时钟特性,使得在多设备间实现稳定高速的通信成为可能。希望这篇文章能够帮助读者掌握SPI的基础知识,并为后续项目中的协议选择和开发提供参考。
6119

被折叠的 条评论
为什么被折叠?



