单片机
返回首页

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

2022-02-25 来源:eefocus

最近做个项目,用到了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);


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

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 用数字电路CD4069制作的万能遥控轻触开关

  • 红外线探测报警器

  • 短波AM发射器电路设计图

  • RS-485基础知识:处理空闲总线条件的两种常见方法

  • 带有短路保护系统的5V直流稳压电源电路图

  • 基于ICL296的大电流开关稳压器电源电路

    相关电子头条文章