STM32学习笔记(15)SPI介绍

SPI介绍

SPI : Serial Peripheral interface ,即串行外围设备接口。 SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,主要应用在 EEPROM(一种存储器),FLASH,实时时钟, AD 转换器,还有数字信号处理器和数字信号解码器之间。

SPI 主要特点: 可以同时发出和接收串行数据; 可以当作主机或从机工作; 提供频率可
编程时钟; 发送结束中断标志; 写冲突保护; 总线竞争保护等。

SPI内部结构简明图

在这里插入图片描述
如图,SPI 接口一般使用 4 条线通信:

  • MISO :主设备数据输入,从设备数据输出。
  • MOSI :主设备数据输出,从设备数据输入。
  • SCLK: 时钟信号,由主设备产生。
  • CS :从设备片选信号,由主设备控制。

SPI接口框图

在这里插入图片描述

字节的输送

从图中可以看出, 主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存器
写入一个字节来发起一次传输。寄存器通过 MOSI 信号线将字节传送给从机(从最高位开始,取代从机的最高位),从机也将自己的移位寄存器中的内容通过 MISO 信号线返回给主机(从最低位开始,取代主机的最低位)。这样,两个移位寄存器中的内容就被交换。

外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

时钟极性与相位

时钟极性(CPOL)和相位(CPHA)可以进行配置。

时钟极性(CPOL)对传输协议没有重大的影响。(决定上升沿采集还是下降沿采集)
CPOL=0,串行同步时钟的空闲状态为低电平。
CPOL=1,串行同步时钟的空闲状态为高电平。

时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。
CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;
CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。

SPI 主模块和与之通信的外设备时钟相位和极性应该一致。
在这里插入图片描述

SPI特征

  • 3线全双工同步传输
  • 8或16位传输帧格式选择
  • 主或从操作(支持多主模式)
  • 8个主模式波特率预分频系数(最大为fpcux/2)
  • 从模式频率 (最大为fpcu/2)
  • 主模式和从模式的快速通信
  • 主模式和从模式下均可以由软件或硬件进行NSS管理:主从操作模式的动态改变
  • 可编程的时钟极性和相位
  • 可编程的数据顺序,MSB在前或LSB在前
  • 可触发中断的专用发送和接收标志
  • SPI总线忙状态标志
  • 支持可靠通信的硬件CRC
    -在发送模式下,CRC值可以被作为最后一个字节发送
    -在全双工模式中对接收到的最后-一个字节自动进行CRC校验
  • 可触发中断的主模式故障、 过载以及CRC错误标志
  • 支持DMA功能的1字节发送和接收缓冲器:产生发送和接受请求

STM32 SPI接口可配置为支持SPI协议或者支持I2S音频协议,默认是SPI模式。可以通过软件切换到|2S方式。

从选择(NSS)脚管理

Negative of Slave Select,也就是同步串行通讯的片选信号(低电平有效)

SS/CS(Slave Select/Chip Select):用于Master片选Slave,使被选中的Slave能够被Master访问;

NSS相当于主机的CS(自己理解的)

  • 软件NSS模式: 可以通过设置SPI _CR1寄存器的SSM位来使能这种模式。在这种模式下NSS引脚可以用作它用,而内部NSS信号电平可以通过写SPI_CR1的SSI位来驱动

  • 硬件NSS模式,分两种情况
    -NSS输出被使能:当STM32 工作为主SPI,并且NSS输出已经通过SPI _CR2寄存器的SSOE位使能,这时NSS引脚被拉低,所有NSS引脚与这个主SPI的NSS引脚相连并配置为硬件NSS的SPI设备,将自动变成从SPI设备。
    此时,当一个SPI设备需要发送广播数据,它必须拉低NSS信号,以通知所有其它的设备它是主设备;如果它不能拉低NSS,这意味着总线上有另外一个主设备在通信,这时将产生-一个
    硬件失败错误(Hard Fault)。
    -NSS输出被关闭:允许操作于多主环境。

部分状态标志

应用程序通过3个状态标志可以完全监控SPI总线的状态。

发送缓冲器空闲标志(TXE)

此标志为’1’时表明发送缓冲器为空,可以写下一个待发送的数据进入缓冲器中。当写入SPI _DR
时,TXE标志被清除。

接收缓冲器非空(RXNE)

此标志为’1’时表明在接收缓冲器中包含有效的接收数据。读SPI数据寄存器可以清除此标志。

忙(Busy)标志

BSY标志由硬件设置与清除(写入此位无效果),此标志表明SPI通信层的状态。

配置过程

1.配置相关引脚的复用功能,使能SPlx时钟

GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)

2.初始化SPIx,设置SPlx工作模式

SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct)

3.SPI传输数据

SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)//发送数据
SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)//接收数据

4.查看SPI传输状态

SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG)//类似上面的状态标志

相关结构体

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;//设置 NSS 信号由硬件(NSS 管脚)还是软件控制
uint16_t SPI_BaudRatePrescaler;//波特率预分频值
uint16_t SPI_FirstBit;//数据传输顺序是先MSB 位还是先LSB 位
uint16_t SPI_CRCPolynomial;// CRC 校验
}SPI_InitTypeDef;

相关配置

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_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;		//波特率预分频
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//数据传输开始位
	SPI_InitStructure.SPI_CRCPolynomial = 7;	
	SPI_Init(SPI2, &SPI_InitStructure);  //初始化SPIx寄存器
 
	SPI_Cmd(SPI2, ENABLE); //使能SPI外设
	
	SPI2_ReadWriteByte(0xff);//启动传输		 
}
//SPIx 读写一个字节(SPI想要读必须写)
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{		
	u8 retry=0;				 	
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
		{
		retry++;
		if(retry>200)return 0;
		}			  
	SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
	retry=0;

	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位
		{
		retry++;
		if(retry>200)return 0;
		}	  						    
	return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据					    
}

以W25Q128为例的读写数据

//pBuffer:数据存储区
//Read/writeAddr:开始读取的地址(24bit)
//NumByteToRead/write:要读取/写入的字节数(读取最大65535,写入不超过该页的剩余字节数)

void W25Q128_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
{ 
 	u16 i;   										    
	W25Q128_CS=0;                            	//使能器件   
    SPI2_ReadWriteByte(W25X_ReadData);         	//发送读取命令   
    SPI2_ReadWriteByte((u8)((ReadAddr)>>16));  	//发送24bit地址    
    SPI2_ReadWriteByte((u8)((ReadAddr)>>8));   
    SPI2_ReadWriteByte((u8)ReadAddr);   
    for(i=0;i<NumByteToRead;i++)
	{ 
        pBuffer[i]=SPI2_ReadWriteByte(0XFF);   	//循环读数  
    }
	W25Q128_CS=1;  				    	      
}  



void W25Q128_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
 	u16 i;  
    W25Q128_Write_Enable();                  	//SET WEL 
	W25Q128_CS=0;                            	//使能器件   
    SPI2_ReadWriteByte(W25X_PageProgram);      	//发送写页命令   
    SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); 	//发送24bit地址    
    SPI2_ReadWriteByte((u8)((WriteAddr)>>8));   
    SPI2_ReadWriteByte((u8)WriteAddr);   
    for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数  
	W25Q128_CS=1;                            	//取消片选 
	W25Q128_Wait_Busy();					   		//等待写入结束
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章