目录
引言
学习的内容是基于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有关一些的项目,去巩固自己的学习和加强自己的知识储备。
项目: