配合我的上一篇SPI 通信 协议-CSDN博客一起理解更佳,本文后看
SPI 是由摩托罗拉(Motorola)公司开发的全双工同步串行总线,是 MCU 和外围设备之间进行通信的同步串行端口。主要应用在EEPROM、Flash、RTC、ADC、网络控制器、MCU、DSP以及数字信号解码器之间。SPI 系统可直接与各个厂家生产的多种标准外围器件直接接口,一般使用4 条线:SCK、MISO、MOSI 、SS
STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
- 可配置8位/16位数据帧、高位先行/低位先行
- 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256) 支持多主机模型、主或从操作
- 可精简为半双工/单工通信
- 支持DMA 兼容I2S协议
STM32F103C8T6 硬件SPI资源:SPI1(APB2)、SPI2(APB1)
SPI基本图
 NSS :从设备选择。这是一个可选的引脚,用来选择主 / 从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。 
 
 
 LSBFIRST是低位先行 。如果是MSBFIRST,那么就是高位先行, 
 
此时MISO和MOSI需要换,如图的另一种指法。 
 

SPI基本结构

注意:该图是高位先行
主模式传输
 主模式、全双工模式下 
 
连续传输(BIDIMODE=0并且RXONLY=0)时
 TXE/RXNE/BSY 的变化示意图 
 
 
本质上来说:第一个字节数据发送,发送时下一个字节就已经到发送缓冲器等待,当第一个字节发送成功,立马发送第二个,第三个字节进入发送缓冲器等待 。
非连续传输发送(BIDIMODE=0并且RXONLY=0)时
TXE/BSY变化示意图

非连续传输发送来说:第一个字节数据发送,发送成功后,下一个字节才到发送缓冲器,再发送第二个,等发送成功后,第三个字节再进入发送缓冲器。
代码
/*** 函    数:SPI初始化* 参    数:无* 返 回 值:无*/
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模式0SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;			//NSS,选择由软件控制SPI_InitStructure.SPI_CRCPolynomial = 7;			//CRC多项式,暂时用不到,给默认值7SPI_Init(SPI1, &SPI_InitStructure);					//将结构体变量交给SPI_Init,配置SPI1/*SPI使能*/SPI_Cmd(SPI1, ENABLE);								//使能SPI1,开始运行/*设置默认电平*/MySPI_W_SS(1);										//SS默认高电平
}SPI起始
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);	//根据BitValue,设置SS引脚的电平
}/*** 函    数:SPI起始* 参    数:无* 返 回 值:无*/
void MySPI_Start(void)
{MySPI_W_SS(0);				//拉低SS,开始时序
}
SPI终止
/*** 函    数:SPI终止* 参    数:无* 返 回 值:无*/
void MySPI_Stop(void)
{MySPI_W_SS(1);				//拉高SS,终止时序
}
交换一个字节
/*** 函    数:SPI交换传输一个字节,使用SPI模式0* 参    数:ByteSend 要发送的一个字节* 返 回 值:接收的一个字节*/
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);							//读取接收到的数据并返回
}