1.1 SPI介绍(硬件配置)
-
首先介绍一下SPI缩写的意思
SPI是英语Serial Peripheral Interface的缩写,顾名思义就是串行外围设备接口。SPI是一种高速的、全双工、同步通信总线,标准的SPI也仅仅使用4个引脚,常用于单片机和 EEPROM、FLASH、实时时钟、数字信号处理器等器件的通信。SPI是全双工同步通信,所以他的读写数据是同时发生的,所以下面的代码把读写数据放到一个函数里了
-
SPI通信原理
工作原理:
SPI通信,它主要是主从方式通信。这种模式通常只有一个主机和一个或者多个从机,标准的SPI是4根线,分别是SS(片选信号)、SCLK(时钟信号)、MOSI(主机发送数据 Master Output/Slave Input) MISO(主机接受数据Master Input/Slave Output)。我们,就是通过这四根线,来进行SPI通信的,首先由SCS(片选信号)选择设备,然后由主机产生时钟信号,最后通过传输和读取数据线进行数据的读取,现在这里简单介绍一下,下面会通过代码进行详细的解释
1.2 SPI的代码讲解
-
介绍一下他的结构体
{ uint16_t SPI_Direction; /*!< 指定SPI为单向或双向数据模式。 该参数可以是 @ref SPI_data_direction 中的一个值 */ uint16_t SPI_Mode; /*!< 指定SPI工作模式。 该参数可以是 @ref SPI_mode 中的一个值 */ uint16_t SPI_DataSize; /*!< 指定SPI数据大小。 该参数可以是 @ref SPI_data_size 中的一个值 */ uint16_t SPI_CPOL; /*!< 指定串行时钟的稳定状态。 该参数可以是 @ref SPI_Clock_Polarity 中的一个值 */ uint16_t SPI_CPHA; /*!< 指定时钟有效边沿用于位捕获。 该参数可以是 @ref SPI_Clock_Phase 中的一个值 */ uint16_t SPI_NSS; /*!< 指定NSS信号是由硬件(NSS引脚)管理还是由软件使用SSI位管理。 该参数可以是 @ref SPI_Slave_Select_management 中的一个值 */ uint16_t SPI_BaudRatePrescaler; /*!< 指定时钟预分频器值,该值将用于配置发送和接收SCK时钟。 该参数可以是 @ref SPI_BaudRate_Prescaler 中的一个值。 @note 通信时钟来源于主时钟。从机时钟不需要设置。 */ uint16_t SPI_FirstBit; /*!< 指定数据传输是从MSB位开始还是从LSB位开始。 该参数可以是 @ref SPI_MSB_LSB_transmission 中的一个值 */ uint16_t SPI_CRCPolynomial; /*!< 指定用于CRC计算的多项式。也就是检验位是第几位 */ }SPI_InitTypeDef;
-
时序问题
SPI通信的主机就是我们的单片机,在读写数据时序的过程中,有四种模式。要了解这四种模式,首先我们得学习两个名词,也就是我们写代码时进行操作的寄存器的名字
CPOL(时钟极性) 用于控制空闲状态下的电平状态 若CPOL被清'0',则表示空闲状态下SCLK为低电平,反之 SCLK则为高电平
CPHA(时钟相位)
若CPHA被置为1,SCLK时钟的第二个边沿(0为下降沿,1为上升沿),进行数据位的采集,数据则在第二个时钟边沿 被锁存
若CPHA被置为0,SCLK时钟的第1个边沿(0为下降沿,1为上升沿),进行数据位的采集,数据则在第1个时钟边沿被 锁存
-
下面这张图详细的展示了如何这两个寄存器的相互使用
根据这个图片我们可以详细的看出来四种时序,画箭头的地方就是表示采集数据
根据这个图对端口进行配置
-
SPI进行初始化配置了
void SPI2_Init() { GPIO_InitTypeDef GPIO_Init_Struct; //首先对GPIO口进行初始化 SPI_InitTypeDef SPI_Init_Struct; //对SPI进行初始化 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能GPIOB的时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI1,ENABLE); //使能SPI2的时钟 GPIO_Init_Struct.GPIO_Mode = GPIO_Mode_AF_PP; //对这三个引脚都设置成复用推挽输出 GPIO_Init_Struct.GPIO_Pin = GPIO_Pin_4 |GPIO_Pin_5 | GPIO_Pin_7; GPIO_Init_Struct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_Init_Struct); GPIO_Init_Struct.GPIO_Mode = GPIO_Mode_IPU; //配置MISO GPIO_Init_Struct.GPIO_Pin = GPIO_Pin_6; GPIO_Init_Struct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_Init_Struct); //设置波特率预分频为256 SPI_Init_Struct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //第二个边沿捕获 SPI_Init_Struct.SPI_CPHA = SPI_CPHA_2Edge; //一般都选择为高电平 SPI_Init_Struct.SPI_CPOL = SPI_CPOL_High; //校验位是第几位 SPI_Init_Struct.SPI_CRCPolynomial = 7; //数据模式为8位 SPI_Init_Struct.SPI_DataSize = SPI_DataSize_8b; //选择双向数据模式,双向都可以收发 SPI_Init_Struct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_Init_Struct.SPI_FirstBit = SPI_FirstBit_MSB; //设置SPI为高位先行 SPI_Init_Struct.SPI_Mode = SPI_Mode_Master; //选择主模式 SPI_Init_Struct.SPI_NSS = SPI_NSS_Soft; //设置为软件模式 //这个NSS就是用来选择设备的,因为是软件控制所以不用连接SSC的端口了 SPI_Init(SPI2,&SPI_Init_Struct); SPI_Cmd(SPI2,ENABLE); //使能 //初始化时把SS拉高让他不工作,当他拉低是就开始工作了 GPIO_SetBits(GPIOB,GPIO_Pin_6); }
-
想必大家还不算了解主模式和从模式的区别吧,下面来介绍一下
-
主模式
-
控制通信:在主模式下,SPI设备负责启动和终止数据传输,并且控制整个SPI通信的速度和时序。
-
时钟信号:主设备产生SPI所需的时钟信号(SCK),并将其发送给从设备。
-
数据传输:主设备可以决定何时开始或结束数据交换。
-
从模式
-
响应通信:在从模式下,SPI设备响应来自主设备的命令。它不主动发起通信。
-
同步时钟:从设备接收由主设备产生的时钟信号(SCK),并根据该信号来同步自己的数据传输。
-
被动操作:从设备只能在接收到主设备的有效信号后才能执行数据传输。
为什么通常选择主模式
-
控制权:在主模式下,主设备负责产生时钟信号(SCK),从而控制数据的传输速率和整个通信流程。这意味着主设备可以自由地选择何时开始和结束数据传输,以及选择与哪个从设备进行通信。
-
初始化:主设备通常是一个微控制器或处理器,它可以初始化SPI通信,并根据需要选择不同的从设备进行交互。从设备则处于等待状态,直到被主设备选中。
-
灵活性:主模式下的设备可以根据需要更改通信速度、数据大小和其他配置参数,以适应不同类型的从设备或应用需求。
-
简化设计:对于从设备而言,无需复杂的时钟产生电路,只需要响应主设备产生的时钟信号即可。这有助于简化从设备的设计和降低成本。
-
接下来就是写读字节,因为SPI通信时全双工同步通信,所以读写字节要同时发生
uint8_t SPI_ReadWriteByte(uint8_t data) { u8 i = 0; //SPI_I2S_GetFlagStatus这个是获取当前状态的函数,下面则是再询问当前状态寄存器是否为空 //为空则发送数据,不为空则等待 while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE) == 0){ i++; if(i>200) { return 0; //超时处理,假如读取失败,跳出防止堵塞程序 } } //当寄存器为空时,发送数据 SPI_I2S_SendData(SPI2,data); //读取数据 i = 0; while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == 0){ i++; if(i>200) { return 0; //超时处理 } //当寄存器为空时 返回收到的数据 } return SPI_I2S_ReceiveData(SPI2); }
//接下来还得有一个控制传输速率的函数 void Set_Speed(uint8_t SPI_BaudRatePrescaler) { //检查输入的波特率 是否符合分频值 assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler)); // SPI2->CR1&=0XFFC7;//比特位 BR[2:0] 清零,确保它们的初始状态为0 SPI2->CR1 |= SPI_BaudRatePrescaler; //对寄存器进行位操作 SPI_Cmd(SPI2,ENABLE); //使能 } ![](D:\typora_picture\image-20240818231919861.png)
这两张图即可说明为什么控制波特率的函数那样写
过几天,会介绍一下SPI在实际项目中的应用