SPI简介

SPI为一种同步、串行、全双工、一主多从模式的通信协议,由Motorola公司开发

4个引脚分布:MOSI(主机发送 从机接收);MISO(主机接收 从机发送);SS(片选信号);SCK(时钟)

SPI的时钟频率可为数十M,由主机SCK的时钟源决定

SPI通信方式

一主多从的引脚连接

SPI一主多从模式的硬件连接如下,通过SS片选从机设备,当拉低对应的SS信号代表与对应的从机通信开始,拉高代表结束

主机输入引脚为推挽输出(增强驱动能力),输入引脚为浮空或上拉输入模式

SPI的从机规定:为避免多个从机的输出引脚MISO冲突,协议规定各个从机SS引脚为高电平(即未被选中状态)时,MISO输出为高阻态

SPI主从设备的通信原理

主机与从机通过互相交换数据实现通信,哪怕主机或从机只需要接收或发送,也需同时完成;

主机(从机)将一字节的数据通过移位寄存器进行发送,从机(主机)并通过移位寄存器进行接收;

上图中SPI的发送为主机左移从高位输出,从机也保持左移,从低位进入

根据电路硬件/软件设计,也可相反

SPI的4种模式

在介绍SPI的4种方式前,再次说明SS信号的作用,其既是选择从机设备的信号线,又是标志SPI通信的起始和结束信号:SS拉低->代表起始信号;ss拉高->代表结束信号

SPI的4种模式(模式0-3)由两个可配置的位决定

CPOL(时钟极性):表示空闲状态时的时钟电平;0代表低电平;1代表高电平

CPHA(时钟相位):表示开始传输时的时钟相位;0代表SCK第一个边沿移入数据,第二个边沿移出数据;1代表SCK第一个边沿移出数据,第二个边沿移入数据

模式0:CPOL=0;CPHA=0;

模式1:CPOL=0;CPHA=1;

模式2:CPOL=1;CPHA=0;

模式3:CPOL=1;CPHA=1;

SPI的数据发送时序

默认采用模式0

SPI主机采用指令码+数据的组合方式进行发送,根据对应从机的指令集选择指令码发送,然后按照从机的数据发送(或接收)时序进行发送或接收。以W25Q64(Flash)作为从机,

写数据波形示例:第一个字节为写指令,第234个字节为24位的地址,最后一个字节为写入数据

当只需要进行接收数据时,发送完指令码(或地址)后,便可通过MISO进行接收,但注意与此同时也要保持发送时序,可发送0xFF代表主机无效的指令;

读数据波形示例:第一个字节为读指令,第234个字节为24位的地址,最后一个字节为读出数据

STM32的SPI外设

以STM32F103C8T6为例,其包含SPI1、SPI2两个外设,

SPI1外设挂载于APB2(最大时钟72M,再接SPI分频器),SPI2挂在于APB1(最大时钟36M,再接SPI分频器)

主要寄存器

LSBFIRST:决定高位/低位先发送

SPE:SPI使能,对应SPI_Cmd函数配置

BR:配置波特率即时钟SPI频率

MSTR:配置SPI外设的主从模式

CPOL、CPOA:用来选择SPI的四种模式

TXE:发送寄存器空,代表当前可继续发送数据(可写入发送数据寄存器)

RXNE:接收寄存器非空,代表有数据接收到可读(可读出接收数据寄存器)

SPI不同于UART和IIC的数据寄存器部分

SPI外设的发送数据寄存器和接收数据寄存器分离,发送和接收移位寄存器共用,全双工且同步

IIC外设的发送和接收数据寄存器共用,移位寄存器也共用,因为是半双工,同步

UART外设的发送和接收数据寄存器分离,移位寄存器也分离,因为是全双工,支持异步通信

外设驱动时序

主机模式下的全双工连续传输时序

该模式较为复杂,但可保证在高速时钟频率(一般为1M以上)下的高利用率

数据发送寄存器写入第一个字节数据后,传递给移位寄存器,然后再次向数据发送寄存器写入下一个字节数据,也就是在接受完第一个待接收数据之前,先写入了两个待发送数据

主机模式下的非连续传输时序

图中未画出接收部分MISO以及RXNE

该模式实现简单,只适合低速率下使用,高速率下会导致利用率极低

先写入一个字节的发送数据寄存器后(TXE=1),然后等待接收到一个字节数据后(RXNE=1),再次写入第二个字节的发送数据

代码

主机模式下的非连续传输,MySPI_SwapByte发送一个字节同时读取一个字节

#include "stm32f10x.h"   


void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

void MySPI_Start(void)
{
	MySPI_W_SS(0);				//拉低SS,开始时序
}

void MySPI_Stop(void)
{
	MySPI_W_SS(1);				//拉高SS,终止时序
}

void MySPI_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);	//开启SPI1的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA4引脚初始化为推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA5和PA7引脚初始化为复用推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA6引脚初始化为上拉输入
	
	/*SPI初始化*/
	SPI_InitTypeDef SPI_InitStructure;						//定义结构体变量
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//模式,选择为SPI主模式
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//方向,选择2线全双工
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据宽度,选择为8位
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//先行位,选择高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率分频,选择128分频
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;				//SPI极性,选择低极性
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//NSS,选择由软件控制
	SPI_InitStructure.SPI_CRCPolynomial = 7;				//CRC多项式,暂时用不到,给默认值7
	SPI_Init(SPI1, &SPI_InitStructure);						//将结构体变量交给SPI_Init,配置SPI1
	
	/*SPI使能*/
	SPI_Cmd(SPI1, ENABLE);									//使能SPI1,开始运行
	
	/*设置默认电平*/
	MySPI_W_SS(1);											//SS默认高电平
}

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);	//等待发送数据寄存器空
	
	SPI_I2S_SendData(SPI1, ByteSend);								//写入数据到发送数据寄存器,开始产生时序
	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);	//等待接收数据寄存器非空
	
	return SPI_I2S_ReceiveData(SPI1);								//读取接收到的数据并返回
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐