打印
[应用相关]

STM32 SPI 驱动

[复制链接]
276|13
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
SPI 简介
SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola
首先在其 MC68HCXX 系列处理器上定义的。 SPI 接口主要应用在 EEPROMFLASH,实时时
钟,
AD 转换器,还有数字信号处理器和数字信号解码器之间。 SPI,是一种高速的,全双工,
同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为
PCB 的布局
上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信
协议,
STM32 也有 SPI 接口。 下面我们看看 SPI 的内部简明图  


使用特权

评论回复
沙发
hanzhen654|  楼主 | 2020-3-26 18:39 | 只看该作者
SPI 接口一般使用 4 条线通信:
MISO 主设备数据输入,从设备数据输出。
MOSI 主设备数据输出,从设备数据输入。
SCLK 时钟信号,由主设备产生。
CS 从设备片选信号,由主设备控制。

使用特权

评论回复
板凳
hanzhen654|  楼主 | 2020-3-26 18:43 | 只看该作者
主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器
写入一个字节来发起一次传输。寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的
移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换。
外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,
若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

使用特权

评论回复
地板
hanzhen654|  楼主 | 2020-3-26 18:43 | 只看该作者
SPI 主要特点有: 可以同时发出和接收串行数据; 可以当作主机或从机工作; 提供频率可
编程时钟; 发送结束中断标志; 写冲突保护; 总线竞争保护等。
SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串
行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果
CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电
平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果
CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串
行同步时钟的第二个跳变沿(上升或下降)数据被采样。 SPI 主模块和与之通信的外设备时钟
相位和极性应该一致。

使用特权

评论回复
5
hanzhen654|  楼主 | 2020-3-26 18:44 | 只看该作者
配置相关引脚的复用功能,使能 SPI2 时钟
我们要用 SPI2,第一步就要使能 SPI2 的时钟。其次要设置 SPI2 的相关引脚为复用输出,
这样才会连接到 SPI2 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使
用的是 PB13、 14、 15 这 3 个(SCK.、 MISO、 MOSI, CS 使用软件管理方式),所以设置这三
个为复用 IO。
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//PORTB 时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE );//SPI2 时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 GPIOB

使用特权

评论回复
6
hanzhen654|  楼主 | 2020-3-26 18:45 | 只看该作者
初始化 SPI2,设置 SPI2 工作模式
接下来我们要初始化 SPI2,设置 SPI2 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟
极性及采样方式。并设置 SPI2 的时钟频率(最大 18Mhz),以及数据的格式(MSB 在前还是
LSB 在前)。 这在库函数中是通过 SPI_Init 函数来实现的。
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
跟其他外设初始化一样,第一个参数是 SPI 标号,这里我们是使用的 SPI2。 下面我们来看看第
二个参数结构体类型 SPI_InitTypeDef 的定义:
typedef struct
{
uint16_t SPI_Direction;
uint16_t SPI_Mode;
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;
uint16_t SPI_CPHA;
uint16_t SPI_NSS;
uint16_t SPI_BaudRatePrescaler;
uint16_t SPI_FirstBit;
uint16_t SPI_CRCPolynomial;
}SPI_InitTypeDef;

使用特权

评论回复
7
hanzhen654|  楼主 | 2020-3-26 18:46 | 只看该作者
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主 SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // SPI 发送接收 8 位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二个跳变沿数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由软件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //预分频 256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据指定的参数初始化外设 SPIx 寄存器

使用特权

评论回复
8
hanzhen654|  楼主 | 2020-3-26 18:46 | 只看该作者
使能 SPI2
初始化完成之后接下来是要使能 SPI2 通信了, 在使能 SPI2 之后,我们就可以开始 SPI 通
讯了。 使能 SPI2 的方法是:
SPI_Cmd(SPI2, ENABLE); //使能 SPI 外设

使用特权

评论回复
9
hanzhen654|  楼主 | 2020-3-26 18:47 | 只看该作者
SPI 传输数据
通信接口当然需要有发送数据和接受数据的函数,固件库提供的发送数据函数原型为:
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
这个函数很好理解,往 SPIx 数据寄存器写入数据 Data,从而实现发送。
固件库提供的接受数据函数原型为:
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
这个函数也不难理解,从 SPIx 数据寄存器读出接受到的数据。

使用特权

评论回复
10
hanzhen654|  楼主 | 2020-3-26 18:49 | 只看该作者
查看 SPI 传输状态
在 SPI 传输过程中,我们经常要判断数据是否传输完成,发送区是否为空等等状态,这是
通过函数 SPI_I2S_GetFlagStatus 实现的,这个函数很简单就不详细讲解,判断发送是否完成的
方法是:
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);

使用特权

评论回复
11
hanzhen654|  楼主 | 2020-3-26 19:04 | 只看该作者
//SPI 口初始化
//这里针是对 SPI2 的初始化
void SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//PORTB 时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE );//①SPI2 时钟使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //①初始化 GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15 上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置 SPI 全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置 SPI 工作模式:设置为主 SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 8 位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//选择了串行时钟的稳态:时钟悬空高
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由硬件管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //预分频 256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //②根据指定的参数初始化外设 SPIx 寄存器
SPI_Cmd(SPI2, ENABLE); //③使能 SPI 外设
SPI2_ReadWriteByte(0xff); //④启动传输
}

使用特权

评论回复
12
hanzhen654|  楼主 | 2020-3-26 19:05 | 只看该作者
//SPI 速度设置函数
//SpeedSet://SPI_BaudRatePrescaler_256 256 分频 (SPI 281.25K@sys 72M)
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
SPI2->CR1&=0XFFC7;
SPI2->CR1|=SPI_BaudRatePrescaler; //设置 SPI2 速度
SPI_Cmd(SPI2,ENABLE);
}

使用特权

评论回复
13
hanzhen654|  楼主 | 2020-3-26 19:14 | 只看该作者
/SPIx 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //等待发送区空
{
retry++;
if(retry>200)return 0;
}
SPI_I2S_SendData(SPI2, TxData); //通过外设 SPIx 发送一个数据
retry=0;
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //等待接收
//完一个 byte
{ retry++;
if(retry>200)return 0;
}
return SPI_I2S_ReceiveData(SPI2); //返回通过 SPIx 最近接收的数据
}

使用特权

评论回复
14
hanzhen654|  楼主 | 2020-3-26 19:14 | 只看该作者
此部分代码主要初始化 SPI,这里我们选择的是 SPI2,所以在 SPI2_Init 函数里面,其相关
的操作都是针对 SPI2 的,其初始化步骤和我们上面介绍步骤 1-5 一样,我们在代码中也使用了
①~⑤标注。在初始化之后,我们就可以开始使用 SPI2 了,在 SPI2_Init 函数里面,把 SPI2 的
波特率设置成了最低(36Mhz, 256 分频为 140.625KHz)。在外部函数里面,我们通过
SPI2_SetSpeed 来设置 SPI2 的速度,而我们的数据发送和接收则是通过 SPI2_ReadWriteByte 函
数来实现的。 SPI2_SetSpeed 函数我们是通过寄存器设置方式来实现的,因为固件库并没有提供
单独的设置分频系数的函数 ,当然,我们也可以勉强的调用 SPI_Init 初始化函数来实现分频系
数修改。 要读懂这段代码,可以直接查找中文参考手册中 SPI 章节的寄存器 CR1 的描述即可。
这里特别注意, SPI 初始化函数的最后有一个启动传输,这句话最大的作用就是维持 MOSI
为高电平,而且这句话也不是必须的, 可以去掉。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

73

主题

1766

帖子

2

粉丝