打印
[STM32F4]

STM32F4的SPI

[复制链接]
646|17
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
SHOPQQ|  楼主 | 2023-8-23 11:13 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
SPI协议简介

SPI协议是由摩托罗拉公司提出的通讯协议,即串行外围设备接口。是一种高速全双工的通信总线。

SPI物理层

SPI协议使用3条总线以及片选线。3条总线分别是SCK、MOSI、MISO,片选线为SS(NSS、CS)


SS:从设备选择信号线,常称为片选信号线。还有NSS、CS标记。当多个SPI从设备与SPI主机相连时,其他信号SCK、MOSI、MISO都是并联到相同的SPI总线上的,所有的从设备都在使用这三条总线。但是每一条设备都有一个独立的SS信号线,该信号线独占主机的一个引脚,由多少个从设备,就有多少条片选信号线。当主机要选择从设备时,就将该设备的SS信号线拉低,表示选中该设备。SPI通讯以SS置低为开始信号,拉高作为结束信号

SCK:时钟信号线、

MOSI:主设备输出/从设备输入引脚

MISO:主设备输入/从设备输出引脚



使用特权

评论回复
评论
SHOPQQ 2023-8-23 11:16 回复TA
———————————————— 版权声明:本文为CSDN博主「晨阳雾思」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/wsqgoforit/article/details/128085806 
沙发
SHOPQQ|  楼主 | 2023-8-23 11:13 | 只看该作者
SPI协议层
SPI基本通讯过程

NSS、SCK、MOSI信号都由主机控制产生,MISO的信号由从机产生,主机通过该信号线读取从机的数据。MOSI和MISO的信号只在NSS为低电平的时候才有效,在SCK的每个时钟周期MOSI和MISO传输一位数据。

使用特权

评论回复
板凳
SHOPQQ|  楼主 | 2023-8-23 11:15 | 只看该作者
通讯的起始和停止信号
NSS信号线由高变低,是SPI通讯的起始信号。当从机检测到起始信号后,就知道自己被主机选中,开始准备与主机通信。
NSS信号线由低变高,是SPI通讯的停止信号。表示本次通讯结束,从机的选中状态被取消。

使用特权

评论回复
地板
SHOPQQ|  楼主 | 2023-8-23 11:16 | 只看该作者
数据有效性
SPI使用MOSI和MISO信号线来传输数据,使用SCK信号线进行数据传输。
MOSI和MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同步进行。
MOSI和MISO的数据在SCK的上升沿期间变化输出,在SCK的下降沿被采样。
SPI每次可以传输8位或16位为单位,不受限制。
MSB先行或LSB先行没有做硬性规定,但要保证两个SPI设备之间使用同样协定。一般采用MSB先行模式。

使用特权

评论回复
5
SHOPQQ|  楼主 | 2023-8-23 11:16 | 只看该作者
CPOL/CPHA以及通讯模式
时钟极性CPOL是指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(SPI通讯开始前,NSS线为高电平时SCK的状态)。CPOL=0时,SCK在空闲状态为低电平,CPOL=1则相反。

使用特权

评论回复
6
SHOPQQ|  楼主 | 2023-8-23 11:16 | 只看该作者
时钟相位CPHA是指数据的采样时刻,当CPHA=0,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样,当CPHA=1时,数据线信号将会在SCK时钟线的“偶数边沿”被采样

使用特权

评论回复
7
SHOPQQ|  楼主 | 2023-8-23 11:16 | 只看该作者

使用特权

评论回复
8
SHOPQQ|  楼主 | 2023-8-23 11:17 | 只看该作者
STM32的SPI特性以及架构
SPI外设简介
STM32的SPI外设可用作主机和从机,支持最高的SCK时钟频率f_pclk/2,完全支持SPI协议的四种模式,数据帧长度可设置为8位或16位,可设置MSB或LSB先行。
可以支持双线全双工、双向单线以及单线模式。其中双线单向模式可以同时使用MOSI和MISO数据线向一个方向传输数据。

使用特权

评论回复
9
SHOPQQ|  楼主 | 2023-8-23 11:25 | 只看该作者
SPI架构剖析

使用特权

评论回复
10
SHOPQQ|  楼主 | 2023-8-23 11:25 | 只看该作者
通讯引脚
STM32芯片有多个SPI外设,它们SPI通讯信号引到不同的GPIO引脚,使用时必须配置到这些指定的引脚。

使用特权

评论回复
11
SHOPQQ|  楼主 | 2023-8-23 11:26 | 只看该作者
时钟控制逻辑
通过控制寄存器CR的BR[0:2]位控制,可以实现2、4、8、16、32、64、128、256分频。

使用特权

评论回复
12
SHOPQQ|  楼主 | 2023-8-23 11:26 | 只看该作者
数据控制逻辑
SPI的MOSI以及MISO都连接到数据移位寄存器上,数据移位寄存器的内容来源于接收缓冲区、发送缓冲区以及MOSI、MISO线。
当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位通过数据线发送出去。
当从外部接收数据时,数据移位寄存器把数据线采样到的数据一位一位存储到“接收缓冲区”中。
通过写SPI的数据寄存器DR把数据填充到发送缓冲区中。通过读数据寄存器DR可以获取接收缓冲区中的内容。
数据帧长度->CR1的DFF位配置为8位或16位模式
MSB或LSB先行->LSBFIRST位

使用特权

评论回复
13
SHOPQQ|  楼主 | 2023-8-23 11:27 | 只看该作者
整体控制逻辑
控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据我们配置的”控制寄存器CR1/CR2“的参数而改变,基本的控制参数包括SPI模式、波特率、LSB先行、主从模式等
在SPI外设工作时,控制逻辑会根据外设的工作状态修改”状态寄存器SR“。我们可以通过读取状态寄存器的相关位来了解SPI的工作状态。
控制逻辑还根据要求,负责产生SPI中断信号、DMA请求以及控制NSS信号线
一般实际应用过程中,我们一般不使用SPI外设标准的NSS信号线,而是简单的使用普通的GPIO

使用特权

评论回复
14
SHOPQQ|  楼主 | 2023-8-23 11:27 | 只看该作者
通讯过程
主模式收发流程如下
1、控制NSS信号线,产生起始信号
2、把要发送的数据写入”数据寄存器DR“中,该数据会被存储到发送缓冲区
3、通讯开始,SCK时钟运转。MOSI把发送缓冲区里面的数据一位一位的传输出去,MISO把数据一位一位存储进接收缓冲区
4、发送完一帧数据时,“状态寄存去SR”的“TXE标志位”会被置1,表明传输完一帧,发送缓冲区已空。
接收完一帧数据时,“状态寄存去SR”的“RXNE标志位”会被置1,表明传输完一帧,接收缓冲区非空。
5、等待到“TXE标志位”为1时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可
等待到“RXNE标志位”为1时,通过读取“数据寄存器”可以获取接收缓冲区中的内容
假如我们使能了TXE或RXNE中断,TXE或RXNE置1时会产生SPI中断信号,进入到同一个中断服务函数中,我们可以在该中断程序中检查寄存器位来了解是哪一个事件,在分别处理。
可以使用DMA方式来收发”数据寄存器DR“中的数据。

使用特权

评论回复
15
SHOPQQ|  楼主 | 2023-8-23 11:28 | 只看该作者
SPI结构体以及具体实现代码详解
SPI初始化结构体
typedef struct
{
  uint16_t SPI_Direction;  //设置SPI的单双向模式
  uint16_t SPI_Mode;     //设置SPI的主从模式
  uint16_t SPI_DataSize;   //设置数据帧长度 8/16位
  uint16_t SPI_CPOL;     //设置时钟极性CPOL
  uint16_t SPI_CPHA;     //设置时钟相位CPHA       
  uint16_t SPI_NSS;      //设置NSS引脚是由硬件/软件控制
  uint16_t SPI_BaudRatePrescaler;   //设置时钟分频因子
  uint16_t SPI_FirstBit;           //设置MSB/LSB先行
  uint16_t SPI_CRCPolynomial;    //设置CRC校验的表达式
}SPI_InitTypeDef;

使用特权

评论回复
16
SHOPQQ|  楼主 | 2023-8-23 11:28 | 只看该作者
SPI配置大体逻辑
void SPI_Init(void){
    配置SPI的GPIO初始化;
        SPI_InitStructure SPI_InitStructure;
    配置SPI结构体里面的各个参数;
    SPI_Init(xx,&SPI_InitsStructure);
    SPI_Cmd(xx,ENABLE);
}

使用特权

评论回复
17
SHOPQQ|  楼主 | 2023-8-23 11:28 | 只看该作者
SPI接收/发送一个字节的数据
#define Bummy_Byte OXFF
//使用SPI发送一个字节的数据
u8 SPI_XX_SendByte(u8 byte){
    SPITimeout = SPIT_FLAG_TIMEOUT;

    while(SPI_I2S_GetFlagStatus(XX_SPI,SPI_I2S_FLAG_TXE) == RESET){
        if((SPITimeout--) == 0)
            return SPI_TIME_UserCallback(0);
    }
    SPI_I2S_SendData(XX_SPI,byte);
    SPITimeout = SPIT_FLAG_TIMEOUT;
    while(SPI_I2S_GetFlagStatus(XX_SPI,SPI_I2S_FLAG_RXNE) == RESET){
        if((SPITimeout--) == 0)
            return SPI_TIME_UserCallback(1);
    }
    return SPI_I2S_ReceiveData(FLASH_SPI);
}

//使用SPI读取一个字节的数据
//SPI的接收过程和发送过程实质是一样的
//Dummy_Byte 是一个任意值
u8 SPI_XX_ReadByte(void){
    return (SPI_XX_SendByte(Dummy_Byte));
}

使用特权

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

本版积分规则

9

主题

183

帖子

0

粉丝