STM32 SPI NSS信号理解和DMA传输遇到的问题

    最近做个项目,用到了SPI,遇到一些问题。   

    SPI,四根线,MISO,MOSI,SCK,和NSS,这其中NSS用起来最容易踩坑。NSS是片选线,是用于选择从器件的引脚,可让SPI主器件与从器件进行单独通信,从而避免数据线上的竞争。

问题1:从机发送数据给主机

    要知道,SPI主机发数据,从机去收。但是从机发数据,主机可以不理会。因为主机控制着SCK线,从机若想要发送数据,只能去通知主机来“读”。怎么通知?

    从机若有数据要发送给主机,可以用一根 INT 线来通知,拉低这根线,主机检测到 INT线被拉低,就可以发送一个废字节,从而去交换从机要发送的有效数据。因为当主机往DR寄存器里面写数据时,就会自动开启SCK,在SCK的作用下,主从机的数据就会被交换。

问题2: 硬件,软件模式的选择

我们在配置SPI的时候,会配置NSS是由软件管理,还是硬件管理。看下图

        可以看到,左边NSS是硬件的引脚,是实实在在的引脚,右边的引脚是内部的NSS

        NSS相关的控制由CR1寄存器的 SSM、SSI 以及 CR2 寄存器的 SSOE 位来控制。

        SSM用来配置是硬件模式还是软件模式。SSI用来确定在软件模式下内NSS输入的极性,SSOE用来决定是否允许内部NSS信号送出的NSS引脚上。

        所谓硬件模式(SSM=0,二选一处0端有效),就是内部NSS的信号来自于外部NSS引脚,是确确实实的硬东西(引脚)送过来的。

        所谓软件模式(SSM=1,二选一处1端有效),内部NSS信号来自于内部SSI标志位,用户可以利用软件设置SSI,来控制内NSS。

        需要注意的是,这里的软件模式可能和你理解的软件模式不一样,这里的软件模式是指通过写SSI 位的方式去input 到内部的NSS。我认为这个软件模式的作用是当 STM32的 SPIx 外设作为从机的时候,通过上述的软件模式可以手动的去控制SPI 是否选通从而与主进行机通信(忽略掉主机给出的片选信号)。至于作为主机端的SPI  软件片选操作,下面有说明。

1:硬件模式

        我最先是用硬件的模式去控制,首先,要配置相关的NSS引脚,这个不能像软件模式一样随便用一个引脚。之后按照上面的NSS框图去配置,将内部NSS引到硬件的NSS引脚。我用的是标准库的库函数版本,需要注意的是,标准库中SPI的结构体SPI_InitTypeDef 中,没有SSOE的配置,这个SSOE位是在SPI_CR2寄存器中,所以配置SPI成硬件模式,在标准库下,其他的库我不清楚,一定要加上这句话。

	SPI1->CR2|=0X0004;        //打开SSOE位,允许内部NSS信号送出

这样,才能将内部的NSS引到硬件的NSS引脚上。做完这些之后,还会遇到一个问题,那就是在发送数据时,NSS引脚会正常的被拉低,但是无法正常拉高。这个是因为在硬件模式下,NSS只有在SPI失能时才会结束拉低。

所以,在每次发送数据之前要先使能SPI,发送完毕后要失能SPI,才能达到在发送数据期间,NSS线被正常的拉高拉低。

2:软件模式

        后来由于某些原因,主机又使用了软件模式,软件模式下,你需要随便找一个引脚进行配置,然后在每次发送之前将其拉低,发送结束后将其拉高。

        控制起来和我上面说的硬件模式差不多。但是硬件模式的速度是要快于软件模式的。软件模式的优势在于能够同时控制几个从机,通过不同的NSS来与不同的从机通信。

问题3:SPI收发通过DMA传输,会导致最后一个数据有误。

    这个问题当时困扰了好久,后来用逻辑分析仪去抓波形,才发现了原因,因为在发送一帧数据的时候,最后一个字节时,片选出了问题。片选被提前拉高。

    因为我是用DMA将数据传输到SPI的DR寄存器,在DMA将数据传输完成后,会去触发DMA传输完成中断,在中断中会去通知任务拉高NSS线(用的FreeRTOS),问题就出在这个地方,因为这个DMA传输完成中断,只是DMA传输完所有的数据,也就是都将数据传给DR寄存器后才会触发。并不是SPI传输结束才会触发。举个例子,我一帧数据15个字节。按理说应该是在15个字节发送完毕后才会拉高NSS线。但是用DMA传输的话,DMA在把最后一个字节丢给DR寄存器后,就会立即触发DMA传输完成中断,从而去拉高NSS线。但是此时DR寄存器中还有一个字节的数据。在从机端,检测到NSS线被拉高,自然会以为已经结束。也就会丢掉最后一个字节。

 解决办法很简单,就是在拉高之前,去判断下SPI是否忙,如下。

     while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET){}//Wait for SPI not busy
		 GPIO_SetBits(GPIOA,GPIO_Pin_4);

这样,才是正常的,在数据传输完后拉高片选线。达到结束一帧传输的目的。

你可能感兴趣的:(STM32,stm32,spi,dma,嵌入式)