[STM32] STM32纯硬件SPI主/从模式 库函数版(硬件NSS,SPI Master/Slave)

陌路散爱 提交于 2019-11-28 20:14:32

1. STM32 SPI

1.1 STM32的SPI接口

SPI可以设置为主、从两种模式,并且支持全双工模式,而配置为主、从模式或软件、硬件NSS,在操作上有很大的区别。由于一个项目需求,笔者对STM32的硬件模式和主从模式进行了一些研究,走了很多弯路,也查询了很多资料,现在终于调通了,因此写一篇文章记录调试心得,以及很多需要注意的地方。

以下是STM32 SPI接口的介绍:

  • 3线全双工同步传输;

  • 8或16位传输帧格式选择;

  • 主或从操作,支持多主模式;

  • 主模式和从模式下均可以由软件或硬件进行NSS管理:主/从操作模式的动态改变;

  • 可编程的时钟极性和相位;

  • 可编程的数据顺序,MSB在前或LSB在前;

  • 可触发中断的专用发送和接收标志;

  • SPI总线忙状态标志;

  • 支持可靠通信的硬件CRC;

  • 可触发中断的主模式故障、过载以及CRC错误标志;

  • 支持DMA功能的1字节发送和接收缓冲器:产生发送和接受请求。

本文主要探讨主模式和从模式NSS硬件和软件管理。

2. SPI Master 初始化及测试

2.1 硬件NSS模式

以下是初始化代码

void SPI1_Configuration(void)
{
	SPI_InitTypeDef SPI_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
/************打开时钟*************/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);  
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); 
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO, ENABLE ); 
	
/************引脚配置*************/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;  //SPI_NSS
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_SetBits(GPIOA,GPIO_Pin_4);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;		//SPI_SCK
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;		//SPI_MISO
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //浮空输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;        //SPI_MOSI
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

/************SPI初始化配置*************/
	SPI_Cmd(SPI1, DISABLE);	 //必须先禁用,才能改变MODE
	SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //两线全双工
	SPI_InitStructure.SPI_Mode =SPI_Mode_Master; //主
	SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b; //8位
	SPI_InitStructure.SPI_CPOL =SPI_CPOL_Low; //CPOL=0
	SPI_InitStructure.SPI_CPHA =SPI_CPHA_1Edge; //CPHA=0
	SPI_InitStructure.SPI_NSS =SPI_NSS_Hard; //硬件NSS
	SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_64; //64分频
	SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB; //高位在前
	SPI_InitStructure.SPI_CRCPolynomial =7; //CRC7
	SPI_Init(SPI1,&SPI_InitStructure);
	
// 	SPI_Cmd(SPI1, ENABLE);			//先不打开SPI
 	SPI_SSOutputCmd(SPI1, ENABLE);   		//SPI的NSS引脚控制开启

}

SPI配置为主模式,采用硬件NSS有几点需要注意,若采用硬件NSS,一定要把NSS引脚输出设置为GPIO_Mode_AF_PP,否则程序无法正确控制。

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

2.2 软件NSS模式

软件NSS的初始化步骤大同小异,有几个地方不一样,需要注意一下

①NSS引脚修改为GPIO_Mode_Out_PP

    /************引脚配置*************/
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;  //SPI_NSS
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	GPIO_SetBits(GPIOA,GPIO_Pin_4);

②将NSS配置改为SPI_NSS_Soft,然后打开SPI,屏蔽SPI_SSOutputCmd()这个函数。

    /************SPI初始化配置*************/
    	SPI_Cmd(SPI1, DISABLE);	 //必须先禁用,才能改变MODE
    	SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //两线全双工
    	SPI_InitStructure.SPI_Mode =SPI_Mode_Master; //主
    	SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b; //8位
    	SPI_InitStructure.SPI_CPOL =SPI_CPOL_Low; //CPOL=0
    	SPI_InitStructure.SPI_CPHA =SPI_CPHA_1Edge; //CPHA=0
    	SPI_InitStructure.SPI_NSS =SPI_NSS_Soft; //软件NSS
    	SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_64; //64分频
    	SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB; //高位在前
    	SPI_InitStructure.SPI_CRCPolynomial =7; //CRC7
    	SPI_Init(SPI1,&SPI_InitStructure);
    	
     	SPI_Cmd(SPI1, ENABLE);			//打开SPI
     	//SPI_SSOutputCmd(SPI1, ENABLE);   		//SPI的NSS引脚控制开启

2.3 主函数

2.3.1 硬件NSS模式

硬件NSS模式的操作步骤和软件NSS模式可谓天差地别,首先初始化的时候不需要打开SPI,而且需要单独配置打开NSS引脚,否则NSS引脚会一直输出低电平,无法控制。
其次是操作步骤,硬件NSS模式下,每一次数据读写都需要先打开SPI,操作完成后再关闭SPI,必须要按照这个步骤来,否则NSS引脚一直会是低电平。
这也许就是很多人认为硬件NSS有BUG,无法正确选通,而我也在网上查了很多资料,发现很少有人用硬件NSS。

    int main()
    { 
    	u8 SPI_TX=10,SPI_RX=0; 		//
    	SPI1_Configuration();				//spi初始化
    	while(1)	  
    	{
       		SPI_Cmd(SPI1,ENABLE); 		//启动SPI
       		
    		/**************向从设备发送一个字节*************/
    		while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
    		SPI_I2S_SendData(SPI1,SPI_TX);	
    		
          	/**************保存将接收到的数据*************/
    		while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);
    		SPI_RX = SPI_I2S_ReceiveData(SPI1);
    		
    		SPI_Cmd(SPI1,DISABLE); 		//关闭SPI
    	  	
    		SPI_TX++;			//发送的值+1
     		delay_100ms(10);		//延时1秒
    		if(SPI_TX>50)		{SPI_TX=10;}
    	}
    }
  • 效果测试

SPI-NSS-HARD-TX
上图是刚刚的代码运行的效果,用逻辑分析仪抓取的SPI波形。
从设备同样是STM32,设置为硬件从模式,采用硬件NSS控制,设置方法后文会讲。
可以看到NSS能正确拉低,主设备能正确收到从设备返回的数据。

从设备设置的是收到什么就会返回什么,由于SPI的特性是在发送的同时接收数据,且从设备不能主动向主设备发送数据,因此在发送新数据的时候才能收到上此次发送的旧数据。

2.3.2 软件NSS模式

由于代码差不多,就只贴while(1)的函数

    while(1)	  
    {
       	GPIO_ResetBits(GPIOA,GPIO_Pin_4); 		//拉低CS
		/**************向从设备发送一个字节*************/
		while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
		SPI_I2S_SendData(SPI1,SPI_TX);	
		
      	/**************保存将接收到的数据*************/
		while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);
		SPI_RX = SPI_I2S_ReceiveData(SPI1);
		
		GPIO_SetBits(GPIOA,GPIO_Pin_4); 		//拉高CS 
		
		SPI_TX++;			//发送的值+1
 		delay_100ms(10);		//延时1秒
		if(SPI_TX>50)	 {SPI_TX=10;}
	}
  • 效果测试
    SPI-NSS-SOFT-TX

软件NSS模式下,SPI是常开的,读写数据的时候需要人为控制CS引脚
可以看到CS线在最后一个CLK脉冲发送完成后就立即拉高了,和硬件模式有一点区别

如果去掉这两个函数会怎样呢?

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);

主函数改为:

    while(1)	  
    {
       	GPIO_ResetBits(GPIOA,GPIO_Pin_4); 		//拉低CS
		/**************向从设备发送一个字节*************/
		SPI_I2S_SendData(SPI1,SPI_TX);	
		
      	/**************保存将接收到的数据*************/
		SPI_RX = SPI_I2S_ReceiveData(SPI1);
		
		delay_us(2);			//延时2微秒
		GPIO_SetBits(GPIOA,GPIO_Pin_4); 		//拉高CS 
		
		SPI_TX++;			//发送的值+1
 		delay_100ms(10);		//延时1秒
		if(SPI_TX>50)	 {SPI_TX=10;}
	}
  • 效果测试

SPI-NSS-SOFT-TX2
可以看到,程序似乎没有按照预想顺序进行,延时函数好像没有在SPI_I2S_ReceiveData()结束后才执行。
这也是使用硬件SPI常犯的错误,因为在使用硬件SPI时,外设在进行数据读写的时候是不占用内核时间的,内核把数据丢给外设寄存器就完事了,内核只需要等待外设返回结束标志位,刚才屏蔽掉的两个函数就是在等待外设返回结束标志,而这个等待时间其实也可以做很多事情。
经过测试,内核对外设的操作只用了大概0.4uS。
若不注意这点,可能会出现SPI正在读取芯片数据过程中,芯片的CS线被拉高,芯片可能就停止发送数据了,导致最后读取的数据异常。

以上就是SPI Master模式中的硬件NSS和软件NSS的设置和控制,接下来是SPI Slave模式的设置步骤。

3. SPI Slave 初始化及测试

未完待续

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!