【STM32】SPI通信外设详解
SPI通信协议和W25Q64存储器芯片解读笔记:
【STM32】SPI通信协议&W25Q64Flash存储器芯片(学习笔记)-CSDN博客
SPI通信外设
SPI外设简介
兼容I2S协议: 应用场景
三条主要总线
STM32F103C8T6 有三条主要总线,分别是 AHB(高级高性能总线)、APB1(高级外设总线 1)和 APB2(高级外设总线 2),各总线频率如下:
SPI框图
1. 引脚部分
2. 数据传输相关模块
3. 控制寄存器部分
大致分为两部分,左上角就是数据寄存器和移位寄存器配合部分;和串口、I2C的设计思路具有异曲同工之妙,主要为了实现数据流传输,右下角部分就是控制逻辑了;下面来看看框图细节:
首先,左上角核心部分,就是移位寄存器,右边的数据从低位,一位一位的从MOSI移出,然后MISO,一位一位的移入左边的数据高位,显然移位寄存器是一个右移的状态,所以目前图上表示的是低位先行的配置,对应右下角有一个LSBFLRST控制位,这一位可以控制是低位先行还是高位先行,可以查一下数据手册
目前框图的LSBFIRST状态应该是1,低位先行,如果LSBFIRST给0的话,就是高位先行,这个框图还要变动一下,就移位寄存器变为左移,输出,从左边移出去,输入,从右边移进来。然后就是两个缓冲区,这两个缓冲区实际上就是数据寄存器DR,下面发送缓冲区就是发送数据寄存器TDR,上面接收缓冲区就是接收数据寄存器RDR,和串口那里一样,TDR和RDR占用同一个地址,统一叫做DR;
连续发送数据流过程:在主设备中,将要发送的数据写入发送缓冲区(TDR)。当移位寄存器没有数据移位时,TDR的数据就会转入至移位寄存器进行移位,当发送缓冲区中的数据发送完成后,SPI 状态寄存器(SPI_SR)中的 TXE(发送缓冲区空)标志位置 1。然后移位寄存器工作
移位寄存器工作:发送缓冲区的数据会在 SCK 时钟信号的驱动下,一位一位地移入移位寄存器,然后通过 MOSI 引脚发送到从设备。同时,从设备在 SCK 时钟的同步下,通过 MISO 引脚将数据发送回主设备(如果是双向通信)。
数据接收与存储:在主设备接收数据时,从设备发送的数据在 SCK 时钟信号的同步下,通过 MISO 引脚一位一位地移入主设备的移位寄存器,然后再存入接收缓冲区。当接收缓冲区中有数据时,SPI_SR 中的 RXNE(接收缓冲区非空)标志位置 1。
数据读取:软件可以通过查询 RXNE 标志位或者利用接收缓冲区非空中断,从接收缓冲区(SPI_DR)中读取接收到的数据。如果是连续接收数据,需要及时处理已接收的数据,以便接收新的数据,保证接收过程的连续性。
数据传输设计思路SPI和I2C和串口的区别:设计思路区别
框图中的“波特率发生器”有什么作用?
1. 定义与基本功能
波特率发生器是一种用于生成特定频率时钟信号的电路模块。在 SPI(Serial Peripheral Interface,串行外设接口)通信中,它的主要功能是产生串行时钟信号 SCK(Serial Clock)。SCK 信号用于同步主设备和从设备之间的数据传输,确保数据能够按照预定的速率和时序准确地发送和接收。
2. 波特率的设置与控制
3. 对通信的影响
4. 与其他模块的协同工作
波特率发生器产生的 SCK 时钟信号与 SPI 的其他模块协同工作。在数据发送过程中,发送缓冲区的数据会在 SCK 时钟信号的驱动下,一位一位地通过 MOSI 引脚发送出去;在数据接收过程中,MISO 引脚上的数据会在 SCK 时钟信号的同步下,一位一位地被移入移位寄存器。同时,波特率发生器的工作也受到主控制电路的管理和协调,以确保整个 SPI 通信过程的稳定和准确。
NSS任何实现多主机切换功能?NSS多主机切换
SPI基本结构 
主模式全双工连续传输 
1. 时钟与数据传输
0xF1
、0xF2
、0xF3
,每个数据按位(b0~b7
)在 SCK 驱动下传输。0xA1
、0xA2
、0xA3
,同样按位同步接收。2. 关键标志位变化
SPI_DR
写入新数据来清除该标志(如写入0xF1
后,TXE 置 1,再写入0xF2
即清除)。0xA1
)后,RXNE 被硬件置 1。SPI_DR
数据来清除该标志(如读取0xA1
后,RXNE 标志清除)。3. 软件操作流程
0xF1
到SPI_DR
),等待 TXE=1(发送缓冲区空),再写入下一个数据(如0xF2
),循环直至数据发送完毕。SPI_DR
读取数据(如读取0xA1
),完成一次接收操作,循环处理后续数据。4. 整体时序逻辑
TXE
、RXNE
配合,确保数据连续发送和接收。BSY
标志则全程反映 SPI 外设的工作状态,直到所有数据传输结束才恢复空闲状态。通过这种机制,SPI 实现了主模式全双工连续数据传输的高效控制。非连续传输 
这张图展示了 STM32 SPI 外设工作在 非连续传输发送模式(BIDIMODE=0
且 RXONLY=0
)时,TXE
(发送缓冲区空标志)和 BSY
(忙标志)的变化过程,具体解析如下:
1. 基础配置与信号
CPOL=1
(时钟空闲状态为高电平)、CPHA=1
(数据在时钟第二个边沿采样),定义了 SPI 通信的时钟极性和相位。0xF1
、0xF2
、0xF3
,每个数据按 b0~b7
位在 SCK 时钟下逐位传输。2. 关键标志位变化
TXE
置 1,表示缓冲区空闲。SPI_DR
写入新数据来清除该标志(如写入 0xF1
后,TXE
置 1,写入 0xF2
时清除)。BSY
置 1,表示外设处于忙状态;所有传输结束后,BSY
自动清零。3. 非连续传输的软件操作特点
0xF1
到 SPI_DR
,启动第一次传输。TXE=1
后,较晚写入 0xF2
;再次等待 TXE=1
后,又较晚写入 0xF3
。BSY=0
,确认所有非连续的传输操作完全结束,SPI 外设回归空闲状态。4. 整体时序逻辑
图中体现了非连续传输的 “间隔性”:每次数据发送依赖软件主动写入新数据触发,且两次传输之间存在等待 TXE
标志的间隔。BSY
标志全程反映 SPI 工作状态,仅在数据传输过程中保持为 1,其余时间(如等待软件写入新数据阶段)可能短暂清零,直到所有传输完成最终清零。
非连续传输在SCK频率低时无影响;
在SCK频率高时有缺点:不同SCK频率不同间隙的示波器波形
最后看手册很重要:坚持到最后都牛逼
硬件SPI读写W25Q64
本节代码在软件SPI读写W25Q64芯片基础上做修改,代码参考软件SPI读写W25Q64芯片:【江协科技STM32】软件SPI读写W25Q64芯片(学习笔记)-CSDN博客
接线图
因为这里是硬件读写SPI,涉及STM32内部硬件外设的引脚,都要查询引脚定义表
引脚定义表
引脚复用:
硬件SPI
硬件SPI代码实际上就两部分:
1、SPI外设初始化代码
Ⅰ开启时钟,开启SPI和GPIO的时钟
Ⅱ 初始化GPIO口,其中,SCK和MOSI是由硬件外设控制的输出信号,所以配置为复用推挽输出;MISO是由硬件外设控制的输入信号,所以配置为上拉或者浮空输入。
Ⅲ 配置SPI外设,初始化SPI。
Ⅳ 使能SPI。
void HerSPI_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,开始运行
/*设置默认电平*/
HerSPI_W_SS(1); //SS默认高电平
}
SCK和MOSI是由硬件外设控制的输出信号,所以配置为复用输出,至于为什么,看这个:【STM32】I²CC通信外设&硬件I²CC读写MPU6050(学习笔记)_编缉i2c地址-CSDN博客
找到复用引脚输入输出标题有解释
2、SPI外设操作时序,完成交换应该字节的流程
①等待TXE为1,等待发送寄存器位空,发送寄存器不为空就不着急写
②写入数据到发送数据寄存器,开始产生时序
③等待RXNE=1,接收数据寄存器非空
④ 读取接收到的数据并返回
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); //等待RXNE 接收数据寄存器非空
return SPI_I2S_ReceiveData(SPI1); //读取接收到的数据并返回
}
库函数的使用本章涉及较少,具体可自行查看库函数:
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct); //初始化SPI
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState); //使能SPI运行
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);//发送数据,通过SPIx/I2Sx外设传输数据。
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx); //返回SPIx/I2Sx外设最近接收到的数据。FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG); 检查指定的SPI/I2S标志是否设置
作者:傍晚冰川