SPI协议——基于STM32的学习和代码

目录

引言

一、软件SPI理论

1.1 硬件电路理论

1.2 移位示意图

1.3 SPI时序基本单元

1.3.1 起始条件和终止条件

1.3.2 SPI的四种模式

1.4 SPI时序

1.4.1 发送指令

1.4.2 指定地址写

1.4.3 指定地址读

二、硬件SPI理论

2.1 基于STM32的SPI外设简介

2.2 SPI框图

2.3 主模式全双工连续运输

2.4 非连续传输

三、软件SPI基础代码

3.1 头文件的引脚宏定义

3.2 SPI初始化

3.3 四个引脚的重命名封装函数

3.4 SPI的三个基本时序函数

3.4.1 开始时序

3.4.2 结束时序

3.4.3 数据交换时序

3.5 整个文件代码

3.5.1 MySPI.c 文件

3.5.2 MySPI.h文件

四、硬件SPI基础代码

4.1 头文件的引脚宏定义

4.2 SPI初始化

4.3 CS引脚的重封装

4.4 SPI的三个基本时序函数

4.4.1 开始时序

4.4.2 结束时序

4.4.3 数据交换时序

4.5 整个文件代码

4.5.1 MySPI.c文件

4.5.2 MySPI.h文件

五、总结


引言

学习的内容是基于b站江协科技等的视频(图片来源于江协科技

此篇用于记录学习感受和学习代码

一、软件SPI理论

SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线

有四根数据总线:

串行时钟线SCK(Serisl Clock)

主设备输出从设备输入数据总线MOSI(Master Output Slave INput)(重名DO,例如从设备上的DO,就是从设备的输出,那就对应主设备的输入)

主设备输入从设备输出数据总线MISO(Master Input Slave Output)(重名DI,例如从设备上的DI,就是从设备的输入,那就对应主设备的输出)

从机选择线,低电平有效SS(Slave Select)(重名NSS、CS)

特征同步、全双工(数据发送和接收各占一条线)

支持总线挂载多设备(一主多从

一主多从的实现简述:通过多根SS线对各个从设备连接,需要对哪个设备通信就使那个设备连接的SS线为低电平。

数据的输出和输入都是在SCK的上升沿或下降沿进行的。

1.1 硬件电路理论

所有SPI设备的SCK、MOSI、MISO分别连接在一起

主机另外引出多条SS控制线,分别接到各从机的SS引脚

输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入

主机主导SCK线

由于各引脚都是单端信号,所以全部的设备需要共地

同一时间,主机只能选择一个从机,避免数据冲突

所有的从设备的SS为高电平时,该设备上的MISO引脚设置为高组态(即相当于引脚断开,不输出任何电平)

1.2 移位示意图

一般SPI都是高位先行,所以每来一个时钟,移位寄存器就会向左移位。

波特率发生器:即主机的时钟源

SPI通信的基础是交换字节:

第一个时钟上升沿到来时:

第一个下降沿时钟到来时:

这样依次循环8次,实现主机跟从机的一个字节数据交换。

这样就能实现发送一个字节,接收一个字节,同时发送和接收一个字节。

1.3 SPI时序基本单元

1.3.1 起始条件和终止条件

起始条件:SS从高点平切换到低电平

终止条件:SS从低电平切换到高电平

1.3.2 SPI的四种模式

SPI的模式由时钟极限寄存器CPOL和时钟相位寄存器CPHA决定的。

交换一个字节:(模式0)

CPOL = 0:空闲状态时,SCK为低电平

CPHA = 0:SCK第一个边沿移入数据,第二个边沿移出数据(数据采样)

即SCK默认低电平下,产生上升沿后移入数据,产生下降沿后移出数据(数据采样)

其实这个情况下将SS的下降沿作为第一次移出数据。

SS上升沿作为最后一次移入数据

交换一个字节:(模式1)

CPOL = 0:空闲状态时,SCK为低电平

CPHA = 1:SCK第一个边沿移出数据,第二个边沿移入数据(数据采样)

即SCK默认低电平下,产生上升沿后移出数据,产生下降沿后移入数据(数据采样)

交换一个字节:(模式2)

CPOL = 1:空闲状态时,SCK为高电平

CPHA = 0:SCK第一个边沿移入数据,第二个边沿移出数据(数据采样)

即SCK默认低电平下,产生下降沿后移入数据,产生上升沿后移出数据(数据采样)

其实这个情况下将SS的下降沿作为第一次移出数据。

SS上升沿作为最后一次移入数据

区别模式0在于两者SCK极性相反。

交换一个字节:(模式3)

CPOL = 1:空闲状态时,SCK为高电平

CPHA = 1:SCK第一个边沿移出数据,第二个边沿移入数据(数据采样)

即SCK默认高电平下,产生下降沿后移出数据,产生上升沿后移入数据(数据采样)

相对模式1,也是两者SCK的波形相反。

模式0和模式3都是SCK上升沿采样,模式1和模式2都是下降沿采样

1.4 SPI时序

SPI的时序一般为:起始条件+指令集+数据位等。

指令集的定义由从设备的芯片厂家定义。

下面时序以SPI模式0为例子。

以W256Q64的SPI指令集为例子:写指令时,默认的从设备输出为高组态,即主设备交换的字节都是0xFF

1.4.1 发送指令

向SS指定的设备,发送指令(0x06)

1.4.2 指定地址写

向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0](以W256Q64为例))下,写入指定数据(Data)

1.4.3 指定地址读

向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)

在传完地址后,向从机传输0xFF,从机会向主机指定的设备地址下的数据传输到的输入数据寄存器中。

从机发送完数据后,其当前指针指示地址会加1,如果继续向从机读取数据,从机就会把指定地址的下一个地址存储的数据交换过来。

传输完毕后,SS置回高电平。

二、硬件SPI理论

2.1 基于STM32的SPI外设简介

STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担

可以配置8位/16位数据帧、高位先行/低位先行

时钟频率:f(PCLK)/(2,4,8,16,32,64,128,256)

支持多主机模型、主或从操作

可精简为半双工/单工通信

支持DMA

兼容I2S协议

STM32F103C8T6硬件SPI资源:SPI1(APB2时钟线外设)、SPI2(APB1时钟线外设)

2.2 SPI框图

 该图对应低位先行的配置

配置 LSBFIRST 寄存器可以改变是高位先行还是低位先行

LSBFIRST = 0 : 高位先行

LSBFIRST = 1 : 低位先行

DR寄存器:接收缓冲区:RDR        发送缓冲区:TDR

两者占用同一个地址,统一叫DR

发送寄存器空:TXE = 1

接收寄存器非空:RXNE = 1

波特频率:默认输入72MHz/36Mhz,输出的频率是经过分频后的频率

 简化框图:

2.3 主模式全双工连续运输

借助缓冲区,数据前仆后继,实现连续数据流

传输更快,操作更复杂

2.4 非连续传输

容易封装,好用好理解,但是会损失部分性能。

发送数据时,先检测SPI_DR为空:TXE = 1,这时可以写发送数据寄存器

等第一个数据写入结束,然后先读出数据,再写入数据

总体步骤:

写入TXE = 1

写入数据

等待RXNE = 1

读出数据

三、软件SPI基础代码

3.1 头文件的引脚宏定义

改变这里,去方便修改更改外设时的应用场景

#define MY_SPI_CLK 	RCC_APB2Periph_GPIOA	//GPIO时钟线
//#define MY_SPI_CLK 	RCC_APB2Periph_GPIOB
//#define MY_SPI_CLK 	(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB)

#define MY_SPI_CS_GPIOX			GPIOA //从机片选线时钟源
#define MY_SPI_SCK_GPIOX		GPIOA //时钟总线时钟源
#define MY_SPI_MISO_GPIOX		GPIOA //主机输入线时钟源
#define MY_SPI_MOSI_GPIOX		GPIOA //主机输出线时钟源

#define MY_SPI_CS_PIN			GPIO_Pin_4 //从机片选线
#define MY_SPI_SCK_PIN		GPIO_Pin_5 //时钟总线
#define MY_SPI_MISO_PIN		GPIO_Pin_6 //主机输入线
#define MY_SPI_MOSI_PIN		GPIO_Pin_7 //主机输出线

3.2 SPI初始化

除了输入引脚MISO设置为上拉模式,其他模式都设置为推挽输出模式

/*
 *	我的SPI时序的初始化
 *	关于引脚定义在MySPI.h文件
 */
void MySPI_Init(void)
{
		GPIO_InitTypeDef GPIO_InitStucture;//定义结构体。引用结构体的所有变量
		
		RCC_APB2PeriphClockCmd(MY_SPI_CLK, ENABLE);//使能时钟

		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
		GPIO_InitStucture.GPIO_Pin = MY_SPI_CS_PIN;
		GPIO_InitStucture.GPIO_Speed = GPIO_Speed_50MHz;//GPIO_Speed_50MHz指给这个配置50MHz的
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Pin = MY_SPI_SCK_PIN;	
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Pin =  MY_SPI_MOSI_PIN;	
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_IPU;	//上拉输入
		GPIO_InitStucture.GPIO_Pin = MY_SPI_MISO_PIN;
		GPIO_Init(MY_SPI_MISO_GPIOX,&GPIO_InitStucture);
	
		/* 初始化后默认引脚电平设置 */
		MySPI_W_SS(1);
#if ((MODESX == 0) || (MODESX == 1))
		MySPI_W_SCK(0);
#elif ((MODESX == 2) || (MODESX == 3))
		MySPI_W_SCK(1);
#endif
}

3.3 四个引脚的重命名封装函数

CS引脚的写函数:

/*
 *	重命名SPI的CS引脚的写函数
 *	参数(uint8_t bitValue):0 低电平 1 高电平
 */
void MySPI_W_SS(uint8_t bitValue)
{
		GPIO_WriteBit(MY_SPI_CS_GPIOX, MY_SPI_CS_PIN, (BitAction)bitValue);
}

SCK引脚的写函数:

/*
 *	重命名SPI的SCK引脚的写函数
 *	参数(uint8_t bitValue):0 低电平 1 高电平
 */
void MySPI_W_SCK(uint8_t bitValue)
{
		GPIO_WriteBit(MY_SPI_SCK_GPIOX, MY_SPI_SCK_PIN, (BitAction)bitValue);
}

MOSI引脚的写函数:

/*
 *	重命名SPI的MOSI引脚的写函数
 *	参数(uint8_t bitValue):0 低电平 1 高电平
 */
void MySPI_W_MOSI(uint8_t bitValue)
{
		GPIO_WriteBit(MY_SPI_MOSI_GPIOX, MY_SPI_MOSI_PIN, (BitAction)bitValue);
}

MISO引脚的读函数:

/*
 *	重命名SPI的MISO引脚的读函数
 *	返回值(uint8_t):0 低电平 1 高电平
 */
uint8_t MySPI_R_MISO(void)
{
		return GPIO_ReadInputDataBit(MY_SPI_MISO_GPIOX, MY_SPI_MOSI_PIN);
}

3.4 SPI的三个基本时序函数

3.4.1 开始时序

/*
 *	SPI开始条件
 */
void MySPI_Start(void)
{
		MySPI_W_SS(0);
}

3.4.2 结束时序

/*
 *	SPI结束条件
 */
void MySPI_Stop(void)
{
		MySPI_W_SS(1);
}

3.4.3 数据交换时序

/*
 *	SPI数据交换函数,交换一个字节
 *	参数(uint8_t byteSend):要发送的数据
 *	返回值(uint8_t):返回读取到的一个字节
 */
uint8_t MySPI_SwapByte(uint8_t byteSend)
{
		uint8_t i;
		MySPI_Start();
#if (MODESX == 0)//模式0
		
		for(i = 0;i < 8; i ++)
		{
			/* 移出数据 */
			MySPI_W_MOSI(byteSend & 0x80);
			/* 移位 */
			byteSend <<= 1;
			MySPI_W_SCK(1);
			if(MySPI_R_MISO() == 1)
			{
				byteSend |= 0x01;
			} 
			MySPI_W_SCK(0);
		}

#elif (MODESX == 1)//模式1
		
		for(i = 0;i < 8; i ++)
		{
			MySPI_W_SCK(1);
			/* 移出数据 */
			MySPI_W_MOSI(byteSend & 0x80);
			/* 移位 */
			byteSend <<= 1;
			MySPI_W_SCK(0);
			if(MySPI_R_MISO() == 1)
			{
				byteSend |= 0x01;
			} 
		}
		
#elif (MODESX == 2)//模式2
		
		for(i = 0;i < 8; i ++)
		{
			/* 移出数据 */
			MySPI_W_MOSI(byteSend & 0x80);
			/* 移位 */
			byteSend <<= 1;
			MySPI_W_SCK(0);
			if(MySPI_R_MISO() == 1)
			{
				byteSend |= 0x01;
			} 
			MySPI_W_SCK(1);
		}
		
#elif (MODESX == 3)//模式3
		
		for(i = 0;i < 8; i ++)
		{
			MySPI_W_SCK(0);
			/* 移出数据 */
			MySPI_W_MOSI(byteSend & 0x80);
			/* 移位 */
			byteSend <<= 1;
			MySPI_W_SCK(1);
			if(MySPI_R_MISO() == 1)
			{
				byteSend |= 0x01;
			} 
		}
		
#endif
		MySPI_Stop();
		return byteSend;
}

3.5 整个文件代码

3.5.1 MySPI.c 文件

#include "MySPI.h"
#define MODESX 0 //模式几?

/*
 *	重命名SPI的CS引脚的写函数
 *	参数(uint8_t bitValue):0 低电平 1 高电平
 */
void MySPI_W_SS(uint8_t bitValue)
{
		GPIO_WriteBit(MY_SPI_CS_GPIOX, MY_SPI_CS_PIN, (BitAction)bitValue);
}

/*
 *	重命名SPI的SCK引脚的写函数
 *	参数(uint8_t bitValue):0 低电平 1 高电平
 */
void MySPI_W_SCK(uint8_t bitValue)
{
		GPIO_WriteBit(MY_SPI_SCK_GPIOX, MY_SPI_SCK_PIN, (BitAction)bitValue);
}

/*
 *	重命名SPI的MOSI引脚的写函数
 *	参数(uint8_t bitValue):0 低电平 1 高电平
 */
void MySPI_W_MOSI(uint8_t bitValue)
{
		GPIO_WriteBit(MY_SPI_MOSI_GPIOX, MY_SPI_MOSI_PIN, (BitAction)bitValue);
}

/*
 *	重命名SPI的MISO引脚的读函数
 *	返回值(uint8_t):0 低电平 1 高电平
 */
uint8_t MySPI_R_MISO(void)
{
		return GPIO_ReadInputDataBit(MY_SPI_MISO_GPIOX, MY_SPI_MOSI_PIN);
}

/*
 *	我的SPI时序的初始化
 *	关于引脚定义在MySPI.h文件
 */
void MySPI_Init(void)
{
		GPIO_InitTypeDef GPIO_InitStucture;//定义结构体。引用结构体的所有变量
		
		RCC_APB2PeriphClockCmd(MY_SPI_CLK, ENABLE);//使能时钟

		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
		GPIO_InitStucture.GPIO_Pin = MY_SPI_CS_PIN;
		GPIO_InitStucture.GPIO_Speed = GPIO_Speed_50MHz;//GPIO_Speed_50MHz指给这个配置50MHz的
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Pin = MY_SPI_SCK_PIN;	
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Pin =  MY_SPI_MOSI_PIN;	
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_IPU;	//上拉输入
		GPIO_InitStucture.GPIO_Pin = MY_SPI_MISO_PIN;
		GPIO_Init(MY_SPI_MISO_GPIOX,&GPIO_InitStucture);
	
		/* 初始化后默认引脚电平设置 */
		MySPI_W_SS(1);
#if ((MODESX == 0) || (MODESX == 1))
		MySPI_W_SCK(0);
#elif ((MODESX == 2) || (MODESX == 3))
		MySPI_W_SCK(1);
#endif
}

/*
 *	SPI开始条件
 */
void MySPI_Start(void)
{
		MySPI_W_SS(0);
}

/*
 *	SPI结束条件
 */
void MySPI_Stop(void)
{
		MySPI_W_SS(1);
}

/*
 *	SPI数据交换函数,交换一个字节
 *	参数(uint8_t byteSend):要发送的数据
 *	返回值(uint8_t):返回读取到的一个字节
 */
uint8_t MySPI_SwapByte(uint8_t byteSend)
{
		uint8_t i;
		MySPI_Start();
#if (MODESX == 0)//模式0
		
		for(i = 0;i < 8; i ++)
		{
			/* 移出数据 */
			MySPI_W_MOSI(byteSend & 0x80);
			/* 移位 */
			byteSend <<= 1;
			MySPI_W_SCK(1);
			if(MySPI_R_MISO() == 1)
			{
				byteSend |= 0x01;
			} 
			MySPI_W_SCK(0);
		}

#elif (MODESX == 1)//模式1
		
		for(i = 0;i < 8; i ++)
		{
			MySPI_W_SCK(1);
			/* 移出数据 */
			MySPI_W_MOSI(byteSend & 0x80);
			/* 移位 */
			byteSend <<= 1;
			MySPI_W_SCK(0);
			if(MySPI_R_MISO() == 1)
			{
				byteSend |= 0x01;
			} 
		}
		
#elif (MODESX == 2)//模式2
		
		for(i = 0;i < 8; i ++)
		{
			/* 移出数据 */
			MySPI_W_MOSI(byteSend & 0x80);
			/* 移位 */
			byteSend <<= 1;
			MySPI_W_SCK(0);
			if(MySPI_R_MISO() == 1)
			{
				byteSend |= 0x01;
			} 
			MySPI_W_SCK(1);
		}
		
#elif (MODESX == 3)//模式3
		
		for(i = 0;i < 8; i ++)
		{
			MySPI_W_SCK(0);
			/* 移出数据 */
			MySPI_W_MOSI(byteSend & 0x80);
			/* 移位 */
			byteSend <<= 1;
			MySPI_W_SCK(1);
			if(MySPI_R_MISO() == 1)
			{
				byteSend |= 0x01;
			} 
		}
		
#endif
		MySPI_Stop();
		return byteSend;
}

3.5.2 MySPI.h文件

#ifndef __MYSPI_H
#define __MYSPI_H

#include "stm32f10x.h"

#define MY_SPI_CLK 	RCC_APB2Periph_GPIOA	//GPIO时钟线
//#define MY_SPI_CLK 	RCC_APB2Periph_GPIOB
//#define MY_SPI_CLK 	(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB)

#define MY_SPI_CS_GPIOX			GPIOA //从机片选线时钟源
#define MY_SPI_SCK_GPIOX		GPIOA //时钟总线时钟源
#define MY_SPI_MISO_GPIOX		GPIOA //主机输入线时钟源
#define MY_SPI_MOSI_GPIOX		GPIOA //主机输出线时钟源

#define MY_SPI_CS_PIN			GPIO_Pin_4 //从机片选线
#define MY_SPI_SCK_PIN		GPIO_Pin_5 //时钟总线
#define MY_SPI_MISO_PIN		GPIO_Pin_6 //主机输入线
#define MY_SPI_MOSI_PIN		GPIO_Pin_7 //主机输出线


/*
 *	我的SPI时序的初始化
 *	关于引脚定义在MySPI.h文件
 */
void MySPI_Init(void);

/*
 *	SPI开始条件
 */
void MySPI_Start(void);

/*
 *	SPI结束条件
 */
void MySPI_Stop(void);

/*
 *	SPI数据交换函数,交换一个字节
 *	参数(uint8_t byteSend):要发送的数据
 *	返回值(uint8_t):返回读取到的一个字节
 */
uint8_t MySPI_SwapByte(uint8_t byteSend);
#endif

四、硬件SPI基础代码

4.1 头文件的引脚宏定义

#define MY_SPI_CLK 	RCC_APB2Periph_GPIOA	//GPIO时钟线
//#define MY_SPI_CLK 	RCC_APB2Periph_GPIOB
//#define MY_SPI_CLK 	(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB)

#define MY_SPI_CS_GPIOX			GPIOA //从机片选线时钟源
#define MY_SPI_SCK_GPIOX		GPIOA //时钟总线时钟源
#define MY_SPI_MISO_GPIOX		GPIOA //主机输入线时钟源
#define MY_SPI_MOSI_GPIOX		GPIOA //主机输出线时钟源

#define MY_SPI_CS_PIN			GPIO_Pin_4 //从机片选线
#define MY_SPI_SCK_PIN		GPIO_Pin_5 //时钟总线
#define MY_SPI_MISO_PIN		GPIO_Pin_6 //主机输入线
#define MY_SPI_MOSI_PIN		GPIO_Pin_7 //主机输出线

4.2 SPI初始化

跟软件SPI不同的是,需要把除了MISO引脚外的SPI硬件资源引脚模式设置为复用推挽输出模式。

/*
 *	我的SPI时序的初始化
 *	关于引脚定义在MySPI.h文件
 */
void MySPI_Init(void)
{
		GPIO_InitTypeDef GPIO_InitStucture;//定义结构体。引用结构体的所有变量
		SPI_InitTypeDef	SPI_InitStucture;
	
		RCC_APB2PeriphClockCmd(MY_SPI_CLK, ENABLE);//使能时钟
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
	
		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
		GPIO_InitStucture.GPIO_Pin = MY_SPI_CS_PIN;
		GPIO_InitStucture.GPIO_Speed = GPIO_Speed_50MHz;//GPIO_Speed_50MHz指给这个配置50MHz的
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
		GPIO_InitStucture.GPIO_Pin = MY_SPI_SCK_PIN;	
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Pin =  MY_SPI_MOSI_PIN;	
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_IPU;	//上拉输入
		GPIO_InitStucture.GPIO_Pin = MY_SPI_MISO_PIN;
		GPIO_Init(MY_SPI_MISO_GPIOX,&GPIO_InitStucture);
	
		SPI_InitStucture.SPI_Mode = SPI_Mode_Master;//决定当前参数是主机还是从机
		SPI_InitStucture.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线全双工
		SPI_InitStucture.SPI_DataSize = SPI_DataSize_8b;//传输字节大小8bit
		SPI_InitStucture.SPI_FirstBit = SPI_FirstBit_MSB;//高位先行
		SPI_InitStucture.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//时钟分频系数 72KMz / 128 (APB1),36KMz / 128 (APB2)
		SPI_InitStucture.SPI_CPOL = SPI_CPOL_LOW;//时钟极性
		SPI_InitStucture.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位,第一个边沿采样
		SPI_InitStucture.SPI_NSS = SPI_NSS_Soft;//软件NSS
		SPI_InitStucture.SPI_CRCPolynomial = 7;//CRC效验数,默认7

		SPI_Init(SPI1, &SPI_InitStucture);
		
		SPI_Cmd(SPI1, ENABLE);
		/* 初始化后默认引脚电平设置 */
		MySPI_W_SS(1);
}

4.3 CS引脚的重封装

为了方便操作去制作的重封装函数

/*
 *	重命名SPI的CS引脚的写函数
 *	参数(uint8_t bitValue):0 低电平 1 高电平
 */
void MySPI_W_SS(uint8_t bitValue)
{
		GPIO_WriteBit(MY_SPI_CS_GPIOX, MY_SPI_CS_PIN, (BitAction)bitValue);
}

4.4 SPI的三个基本时序函数

4.4.1 开始时序

/*
 *	SPI开始条件
 */
void MySPI_Start(void)
{
		MySPI_W_SS(0);
}

4.4.2 结束时序

/*
 *	SPI结束条件
 */
void MySPI_Stop(void)
{
		MySPI_W_SS(1);
}

4.4.3 数据交换时序

/*
 *	SPI数据交换函数,交换一个字节
 *	参数(uint8_t byteSend):要发送的数据
 *	返回值(uint8_t):返回读取到的一个字节
 */
uint8_t MySPI_SwapByte(uint8_t byteSend)
{
		//1.等待TXE = 1 ,即如果发送寄存器不为空,我们就先不要着急写
		while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
		//2.发送数据
		SPI_I2S_SendData(SPI1, byteSend);
		//3.等待接收寄存器标志位 RXNE = 1,表示收到字节
		while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
		//4.接收数据
		return SPI_I2S_ReceiveData(SPI1);
}

4.5 整个文件代码

4.5.1 MySPI.c文件

#include "MySPI.h"

/*
 *	重命名SPI的CS引脚的写函数
 *	参数(uint8_t bitValue):0 低电平 1 高电平
 */
void MySPI_W_SS(uint8_t bitValue)
{
		GPIO_WriteBit(MY_SPI_CS_GPIOX, MY_SPI_CS_PIN, (BitAction)bitValue);
}

/*
 *	我的SPI时序的初始化
 *	关于引脚定义在MySPI.h文件
 */
void MySPI_Init(void)
{
		GPIO_InitTypeDef GPIO_InitStucture;//定义结构体。引用结构体的所有变量
		SPI_InitTypeDef	SPI_InitStucture;
	
		RCC_APB2PeriphClockCmd(MY_SPI_CLK, ENABLE);//使能时钟
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
	
		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
		GPIO_InitStucture.GPIO_Pin = MY_SPI_CS_PIN;
		GPIO_InitStucture.GPIO_Speed = GPIO_Speed_50MHz;//GPIO_Speed_50MHz指给这个配置50MHz的
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
		GPIO_InitStucture.GPIO_Pin = MY_SPI_SCK_PIN;	
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Pin =  MY_SPI_MOSI_PIN;	
		GPIO_Init(MY_SPI_CS_GPIOX,&GPIO_InitStucture);
	
		GPIO_InitStucture.GPIO_Mode = GPIO_Mode_IPU;	//上拉输入
		GPIO_InitStucture.GPIO_Pin = MY_SPI_MISO_PIN;
		GPIO_Init(MY_SPI_MISO_GPIOX,&GPIO_InitStucture);
	
		SPI_InitStucture.SPI_Mode = SPI_Mode_Master;//决定当前参数是主机还是从机
		SPI_InitStucture.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线全双工
		SPI_InitStucture.SPI_DataSize = SPI_DataSize_8b;//传输字节大小8bit
		SPI_InitStucture.SPI_FirstBit = SPI_FirstBit_MSB;//高位先行
		SPI_InitStucture.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//时钟分频系数 72KMz / 128 (APB1),36KMz / 128 (APB2)
		SPI_InitStucture.SPI_CPOL = SPI_CPOL_LOW;//时钟极性
		SPI_InitStucture.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位,第一个边沿采样
		SPI_InitStucture.SPI_NSS = SPI_NSS_Soft;//软件NSS
		SPI_InitStucture.SPI_CRCPolynomial = 7;//CRC效验数,默认7

		SPI_Init(SPI1, &SPI_InitStucture);
		
		SPI_Cmd(SPI1, ENABLE);
		/* 初始化后默认引脚电平设置 */
		MySPI_W_SS(1);
}

/*
 *	SPI开始条件
 */
void MySPI_Start(void)
{
		MySPI_W_SS(0);
}

/*
 *	SPI结束条件
 */
void MySPI_Stop(void)
{
		MySPI_W_SS(1);
}

/*
 *	SPI数据交换函数,交换一个字节
 *	参数(uint8_t byteSend):要发送的数据
 *	返回值(uint8_t):返回读取到的一个字节
 */
uint8_t MySPI_SwapByte(uint8_t byteSend)
{
		//1.等待TXE = 1 ,即如果发送寄存器不为空,我们就先不要着急写
		while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
		//2.发送数据
		SPI_I2S_SendData(SPI1, byteSend);
		//3.等待接收寄存器标志位 RXNE = 1,表示收到字节
		while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
		//4.接收数据
		return SPI_I2S_ReceiveData(SPI1);
}

4.5.2 MySPI.h文件

#ifndef __MYSPI_H
#define __MYSPI_H

#include "stm32f10x.h"

#define MY_SPI_CLK 	RCC_APB2Periph_GPIOA	//GPIO时钟线
//#define MY_SPI_CLK 	RCC_APB2Periph_GPIOB
//#define MY_SPI_CLK 	(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB)

#define MY_SPI_CS_GPIOX			GPIOA //从机片选线时钟源
#define MY_SPI_SCK_GPIOX		GPIOA //时钟总线时钟源
#define MY_SPI_MISO_GPIOX		GPIOA //主机输入线时钟源
#define MY_SPI_MOSI_GPIOX		GPIOA //主机输出线时钟源

#define MY_SPI_CS_PIN			GPIO_Pin_4 //从机片选线
#define MY_SPI_SCK_PIN		GPIO_Pin_5 //时钟总线
#define MY_SPI_MISO_PIN		GPIO_Pin_6 //主机输入线
#define MY_SPI_MOSI_PIN		GPIO_Pin_7 //主机输出线


/*
 *	我的SPI时序的初始化
 *	关于引脚定义在MySPI.h文件
 */
void MySPI_Init(void);

/*
 *	SPI开始条件
 */
void MySPI_Start(void);

/*
 *	SPI结束条件
 */
void MySPI_Stop(void);

/*
 *	SPI数据交换函数,交换一个字节
 *	参数(uint8_t byteSend):要发送的数据
 *	返回值(uint8_t):返回读取到的一个字节
 */
uint8_t MySPI_SwapByte(uint8_t byteSend);
#endif

五、总结

关于SPI的学习是跟着b站江协科技学习的,感谢江协科技的大佬给我的嵌入式学习指一条明路

关于SPI,我还需要加强了解和加强应用,所以我根据自己的理解去做了跟SPI有关一些的项目,去巩固自己的学习和加强自己的知识储备。

项目:

SPI协议应用——基于STM32的软件SPI驱动0.96寸OLED

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

跳河轻生的鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值