1234下一页
返回列表 发新帖我要提问本帖赏金: 50.00元(功能说明)

[STM32F0] 寄存器才是YYDS,冲破HAL库的累赘臃肿!!![SPI+DMA的应用]

[复制链接]
10191|60
 楼主| chenyuanjiyi 发表于 2021-12-3 16:59 | 显示全部楼层 |阅读模式
本帖最后由 chenyuanjiyi 于 2021-12-9 17:47 编辑

#申请原创#

>关于SPI+DMA的使用,进行了两次测试了,之前测试过一次,结果一直没找到问题,现在明白该如何使用了,特此记录。

一、自身SPI时间

主芯片:STM32F072CBT6(48M)
RF芯片:SX1280(通过SPI通讯)
环境:cubemx生成+MDKV5
关于SPI的通常的应用很简单,特别是通过cubemx自动生成的代码,这里就不过多介绍,可自行百度搜索关于SPI的教程及相关资料
395161a9d8c1a9fdb.png
这张图片是通过cubemx生成的SPI代码的一个效果(NSS软件控制+SPI传输),上述是仿照
HAL_SPI_TransmitReceive()
此函数又重新修改创建了一个
BSP_SPI_TransmitReceive()
函数,通过调用BSP_SPI_TransmitReceive()函数,然后测出的波形(关于函数修改的,见下面的工程路径链接)。

至于为啥不用HAL自身提供的HAL_SPI_TransmitReceive()的函数,是因为我发现调用这个函数的时候会造成,每两个字节后的间距会比较远,例:字节0和字节1相隔比较近,但是字节1和字节2隔得挺远的。

说远了,回到主题,通过上面图片,可知字节与字节间距离大概为1.0us左右,并且NSS拉低拉高与数据传输的时间也大概1us,发送5个字节的数据,SPI至少需要9.46us。
二、对比SPI时间这个数据说实话我觉的也还挺不错的,直到我突然发现样机SPI的速度,两字节间距控制在ns级别,如图
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

上图同样是发送5字节的数据,字节间距控制在832ns,下拉NSS到数据输出248ns,数据输出完到上拉560ns,整体发送5字节需要5.7us。
对比我之前的参数,一个是字节间距一个是纳秒一个是毫秒,还有总的发送时间,样子发送时间大概就我的1/2。
因为我这个传输对时间要求较高,所以想着也将时间尽量减少。

三、尝试减少字节间距时间
1.初步测试思路
通过对比样机SPI的速度,发现最主要的是字节间距较短,那么缩短这个时间,我首先想到了的是SPI+DMA进行传输,然后就通过cubemx直接生成了一个SPI+DMA的配置,然后通过软件控制,具体测试代码如下:


  1. while (1)
  2.   {
  3.     /* USER CODE END WHILE */
  4.         HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);
  5.     HAL_SPI_TransmitReceive_DMA(&hspi1,txbuff,rxbuff,8);
  6.     HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_SET);
  7.    
  8.     HAL_GPIO_TogglePin(LED_Red_GPIO_Port,LED_Red_Pin);
  9.     HAL_Delay(50);
  10.     /* USER CODE BEGIN 3 */
  11.   }



在while中周期进行读写SPI数据,但是发现数据不对,测试现象如下
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

在上图的测试现象中,NSS已经拉低了,但是要等一段时间才会进行数据传输,而且数据还未传输完成,NSS就拉高了,这个现象就有问题了。
针对NSS引脚和数据传输错开的现象,增加了一个等待传输完再拉高的代码,
  1. while (1)
  2.   {
  3.     /* USER CODE END WHILE */
  4.         HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET);
  5.     HAL_SPI_TransmitReceive_DMA(&hspi1,txbuff,rxbuff,8);
  6.     while(__HAL_DMA_GET_COUNTER(&hdma_spi1_rx)!=0);
  7.     HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_SET);
  8.    
  9.     HAL_GPIO_TogglePin(LED_Red_GPIO_Port,LED_Red_Pin);
  10.     HAL_Delay(50);
  11.     /* USER CODE BEGIN 3 */
  12.   }

结果如下:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

现在数据传输是在NSS拉低范围内了,但是NSS拉低到数据传输有9.96us,数据传输完到NSS拉高也有11.312us,字节间距倒是减少了不少,至少证明SPI+DMA这条路应该是没问题的,可以减少数据传输的时间。

2.再次测试
针对上一次测试,发现NSS的拉高拉低到数据传输需要较长的时间,我就想会不会是软件控制NSS太慢了,另外由于SPI是DMA进行传输的,就有可能导致SPI传输和控制NSS的操作没达到同步,所以就将SPI+DMA的软件控制NSS  改成了  硬件控制NSS 。
  1. /* SPI1 init function */
  2. void MX_SPI1_Init(void)
  3. {

  4.         hspi1.Instance = SPI1;
  5.         hspi1.Init.Mode = SPI_MODE_MASTER;                        //设置SPI工作模式,设置为主模式
  6.         hspi1.Init.Direction = SPI_DIRECTION_2LINES;              //设置SPI单向或者双向的数据模式:SPI设置为双线模式
  7.         hspi1.Init.DataSize = SPI_DATASIZE_8BIT;                  //设置SPI的数据大小:SPI发送接收8位帧结构
  8.         hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;                //串行同步时钟的空闲状态为低电平
  9.         hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;                    //串行同步时钟的第一个跳变沿(上升或下降)数据被采样
  10.          hspi1.Init.NSS = SPI_NSS_HARD_INPUT;                            //NSS信号由软件控制    SPI_NSS_HARD_OUTPUT   SPI_NSS_SOFT
  11.         hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;   //定义波特率预分频的值:波特率预分频值为4
  12.         hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;                   //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
  13.        hspi1.Init.TIMode = SPI_TIMODE_DISABLE;                   //关闭TI模式
  14.        hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;   //关闭硬件CRC校验
  15.        hspi1.Init.CRCPolynomial = 7;                             //CRC值计算的多项式
  16.        hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  17.        hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;//SPI_NSS_PULSE_DISABLE;             //
  18.    
  19.         if (HAL_SPI_Init(&hspi1) != HAL_OK)
  20.        {
  21.                  _Error_Handler(__FILE__, __LINE__);
  22.        }
  23. }


  24. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
  25. {

  26.   GPIO_InitTypeDef GPIO_InitStruct;
  27.   if(spiHandle->Instance==SPI1)
  28.   {
  29.   /* USER CODE BEGIN SPI1_MspInit 0 */

  30.   /* USER CODE END SPI1_MspInit 0 */
  31.     /* SPI1 clock enable */
  32.     __HAL_RCC_SPI1_CLK_ENABLE();
  33.   
  34.     /**SPI1 GPIO Configuration   
  35.     PB3     ------> SPI1_SCK
  36.     PB4     ------> SPI1_MISO
  37.     PB5     ------> SPI1_MOSI
  38.     */
  39. GPIO_InitStruct.Pin = GPIO_PIN_15;
  40. GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  41. GPIO_InitStruct.Pull = GPIO_NOPULL;
  42. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  43. GPIO_InitStruct.Alternate = GPIO_AF0_SPI1;
  44. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  45.       
  46.     GPIO_InitStruct.Pin = A7106_SCK_Pin|A7106_MISO_Pin|A7106_MOSI_Pin;
  47.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  48.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  49.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  50.     GPIO_InitStruct.Alternate = GPIO_AF0_SPI1;
  51.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  52.     /* SPI1 DMA Init */
  53.     /* SPI1_TX Init */
  54.     hdma_spi1_tx.Instance = DMA1_Channel3;                          //通道选择
  55.     hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;             //存储器到外设
  56.     hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;                 //外设非增量模式
  57.     hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;                     //存储器增量模式
  58.     hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;    //外设数据长度:8位
  59.     hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;       //存储器数据长度:8位
  60.     hdma_spi1_tx.Init.Mode = DMA_NORMAL;                            //DMA普通模式
  61.     hdma_spi1_tx.Init.Priority = DMA_PRIORITY_HIGH;
  62.     if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
  63.     {
  64.       _Error_Handler(__FILE__, __LINE__);
  65.     }

  66.     __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi1_tx);

  67.     /* SPI1_RX Init */
  68.     hdma_spi1_rx.Instance = DMA1_Channel2;
  69.     hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
  70.     hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
  71.     hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
  72.     hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  73.     hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  74.     hdma_spi1_rx.Init.Mode = DMA_NORMAL;
  75.     hdma_spi1_rx.Init.Priority = DMA_PRIORITY_HIGH;
  76.     if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK)
  77.     {
  78.       _Error_Handler(__FILE__, __LINE__);
  79.     }

  80.     __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi1_rx);

  81.   /* USER CODE BEGIN SPI1_MspInit 1 */

  82.   /* USER CODE END SPI1_MspInit 1 */
  83.   }
  84. }

硬件控制NSS,想着让其自动拉高拉低
但是测试发现,每发一个字节NSS引脚就自动拉高了,这就导致了读取的数据是有问题的,具体现象如下:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg
上图的现象,每个字节都进行拉高了,那对于一包数据大于1字节的话,那么就会有问题的,
SPI的数据 = 外设地址 + 该地址的命令(我的理解)
所以针对上图,我想着就如何让NSS拉高的操作和发送的数据相关联,即不让他每个字节都自动拉高
为此,研究了好久,同时也发过好几个帖子,都没有找到相关的资料

-------------------------------卡主了---------------------------------------------------------------------------------------------
在这个阶段卡了挺久的,后面也就没管SPI+DMA的应用了,直到后面空闲了在接着重新拿起这个SPI+DMA的操作。

3.间隔两个月后的再次尝试
在我发的关于SPI+DMA的求助帖子上,我看到推荐说用软件NSS,同时我也仔细研究了下样机的SPI波形,发现他有两路SPI数据,
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

如图,可以得知他采用的是软件控制NSS,因为有两个NSS引脚,并且公用同一路数据引脚,
那么样机采用的是软件控制,达到的SPI数据的速度提升,那么照理来说我们也能通过软件控制NSS,实现SPI+DMA的操作。

但是在初步测试时,软件控制NSS,出现错位问题,所以对软件控制还是有些怀疑的,直到我在帖子上看到   正常来讲,那个NSS每个字节抬高一个时钟时间是正常的操作,每个字节NSS抬高是正常操作,那么我想用硬件控制NSS,让其跟随传输的数据进行自动拉高拉低就行不通了,思路有问题了。

现在开始专研软件控制NSS,达到SPI+DMA的操作了,在测试1中,暴露的问题是NSS拉高的太快了,那么换个思路,能不能让他传输完成就自动拉高呢,这是可以的,DMA中断中正好就有一个传输完成的标志,那我就利用上这个标志,在中断中让其拉高NSS,
  1. void DMA1_Channel2_3_IRQHandler(void)
  2. {
  3.   /* USER CODE BEGIN DMA1_Channel2_3_IRQn 0 */

  4.   /* USER CODE END DMA1_Channel2_3_IRQn 0 */
  5.   HAL_DMA_IRQHandler(&hdma_spi1_rx);
  6.   HAL_DMA_IRQHandler(&hdma_spi1_tx);
  7.   
  8.   /* USER CODE BEGIN DMA1_Channel2_3_IRQn 1 */
  9. if(__HAL_DMA_GET_IT_SOURCE(&hdma_spi1_rx, DMA_IT_TC)){
  10.   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
  11.   }
  12.   /* USER CODE END DMA1_Channel2_3_IRQn 1 */
  13. }

在调用HAL_SPI_TransmitReceive_DMA()函数前,拉低NSS,在DMA的传输完成中断进行拉高NSS,然后测试,效果是可以的,数据传输完后就立刻拉高了,就是NSS拉低到数据传输的时间较长,那么都可以在中断里拉高NSS了,那么在开启DMA传输前,拉低NSS不也是可以的吗。。。。

所以我跳到HAL_SPI_TransmitReceive_DMA()函数里,在开启DMA前,增加了拉低NSS的操作
  1. HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,uint16_t Size)
  2. {
  3. ******************省略***********
  4.   /* Enable the SPI Error Interrupt Bit */
  5.   __HAL_SPI_ENABLE_IT(hspi, (SPI_IT_ERR));
  6. HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
  7.   /* Enable Tx DMA Request */
  8.   SET_BIT(hspi->Instance->CR2, SPI_CR2_TXDMAEN);

  9. error :
  10.   /* Process Unlocked */
  11.   __HAL_UNLOCK(hspi);
  12.   return errorcode;
  13. }

现在NSS拉低的操作,放到HAL提供的HAL_SPI_TransmitReceive_DMA()函数里;
NSS拉高的操作,在DMA传输完成中断中;
测试效果:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

这个效果是不错的,字节间距是208ns,拉低到数据传输656ns,数据传输完到拉高1.328us,发送5个字节需要5.952us的时间,这个时间相比于之前已经提升挺多的了,就是数据传输完到拉高时间稍微有点长。

四、实现SPI+DMA传输(减少传输时间)
在最后一次测试中,时间已经不错了,就是稍微有点瑕疵,于是在进行修改,在
HAL_DMA_IRQHandler(&hdma_spi1_tx);
里面对NSS进行拉高
  1. void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
  2. {

  3. ***************省略**************
  4. /* Transfer Complete Interrupt management ***********************************/
  5.   else if ((RESET != (flag_it & (DMA_FLAG_TC1 << hdma->ChannelIndex))) && (RESET != (source_it & DMA_IT_TC)))
  6.   {
  7.           if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
  8.           {
  9.                   /* Disable the transfer complete  & transfer error interrupts */
  10.                   /* if the DMA mode is not CIRCULAR */
  11.                   hdma->Instance->CCR &= ~(DMA_IT_TC | DMA_IT_TE);
  12.                   
  13.                   /* Change the DMA state */
  14.                   hdma->State = HAL_DMA_STATE_READY;
  15.           }
  16.          
  17.           /* Clear the transfer complete flag */
  18.           hdma->DmaBaseAddress->IFCR = DMA_FLAG_TC1 << hdma->ChannelIndex;
  19.    
  20.     /*nss up*/
  21.     HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
  22.    
  23.           /* Process Unlocked */
  24.           __HAL_UNLOCK(hdma);
  25.          
  26.           if(hdma->XferCpltCallback != NULL)
  27.           {
  28.                   /* Transfer complete callback */
  29.                   hdma->XferCpltCallback(hdma);
  30.           }
  31.   }
  32.   ***************省略**************
  33.   }

发现将NSS放在这里,时间是更短的
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

数据传输完到拉高NSS,已经降低到560ns了,为了适配其他的DMA中断,本想在HAL_DMA_IRQHandler()里面增加 if 判断,再拉高NSS,但是发现增加了判断,数据传输完到拉高NSS这里的时间又变成了us,于是索性复制HAL_DMA_IRQHandler()这个函数,在里面进行拉高NSS的操作,并对函数重命名,只能SPI_DMA调用。

实现步骤一
创建属于自己的HAL_DMA_SPI读写函数
  1. /*
  2. * 复制HAL_SPI_TransmitReceive_DMA()函数,在开启DMA前,拉低NSS引脚
  3. */
  4. HAL_StatusTypeDef HAL_SPI_MY_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,uint16_t Size)
  5. {
  6. ***************************
  7. ******************省略**************
  8.   /* Set the SPI Tx DMA transfer complete callback as NULL because the communication closing is performed in DMA reception complete callback  */

  9.   hspi->hdmatx->XferHalfCpltCallback = NULL;
  10.   hspi->hdmatx->XferCpltCallback     = NULL;
  11.   hspi->hdmatx->XferErrorCallback    = NULL;
  12.   hspi->hdmatx->XferAbortCallback    = NULL;


  13.   /* Enable the Tx DMA Stream/Channel  */
  14.   HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)hspi->pTxBuffPtr, (uint32_t)&hspi->Instance->DR, hspi->TxXferCount);
  15.   
  16. /* Check if the SPI is already enabled */
  17.   if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
  18.   {
  19.     /* Enable SPI peripheral */
  20.     __HAL_SPI_ENABLE(hspi);
  21.   }
  22.   /* Enable the SPI Error Interrupt Bit */
  23.   __HAL_SPI_ENABLE_IT(hspi, (SPI_IT_ERR));

  24. HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);

  25.   /* Enable Tx DMA Request */
  26.   SET_BIT(hspi->Instance->CR2, SPI_CR2_TXDMAEN);

  27. error :
  28.   /* Process Unlocked */
  29.   __HAL_UNLOCK(hspi);
  30.   return errorcode;
  31. }

这个函数只是单纯复制HAL_SPI_TransmitReceive_DMA()函数,然后在开启DMA前,拉低NSS引脚,这样就能尽量减少对库的修改,同时在NSS更换引脚后,也可以更方便修改NSS引脚

实现步骤二
创建属于自己的HAL_DMA_SPI中断函数
  1. /*
  2. * 在it.c中,要先调用
  3.   HAL_DMA_IRQHandler(&hdma_spi1_rx);
  4.   HAL_SPI1_TX_DMA_IRQHandler(&hdma_spi1_tx);//在此函数中会对NSS引脚拉高
  5. */
  6. void HAL_SPI1_TX_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
  7. {
  8. *****************省略*************
  9.   /* Transfer Complete Interrupt management ***********************************/
  10.   else if ((RESET != (flag_it & (DMA_FLAG_TC1 << hdma->ChannelIndex))) && (RESET != (source_it & DMA_IT_TC)))
  11.   {
  12.           if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
  13.           {
  14.                   /* Disable the transfer complete  & transfer error interrupts */
  15.                   /* if the DMA mode is not CIRCULAR */
  16.                   hdma->Instance->CCR &= ~(DMA_IT_TC | DMA_IT_TE);
  17.       
  18.                   /* Change the DMA state */
  19.                   hdma->State = HAL_DMA_STATE_READY;
  20.           }
  21.         
  22.           /* Clear the transfer complete flag */
  23.           hdma->DmaBaseAddress->IFCR = DMA_FLAG_TC1 << hdma->ChannelIndex;

  24.     /*nss up*/
  25.     HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);

  26.           /* Process Unlocked */
  27.           __HAL_UNLOCK(hdma);

  28.           if(hdma->XferCpltCallback != NULL)
  29.           {
  30.                   /* Transfer complete callback */
  31.                   hdma->XferCpltCallback(hdma);
  32.           }
  33.   }
  34. *****************省略

  35. }  

这个函数是复制 HAL_DMA_IRQHandler()函数,然后增加NSS的操作。
【注意】:

  • 在void DMA1_Channel2_3_IRQHandler(void)中,要先调
    HAL_DMA_IRQHandler(&hdma_spi1_rx); 再调用HAL_SPI1_TX_DMA_IRQHandler(&hdma_spi1_tx);函数
    (调换位置时会出现数据还没传输完,NSS就拉高的操作)
  • 对于SPI_DMA的通道优先级尽量设置高点,这样可以更好的保证SPI传输时间短

实现效果
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

发送5个字节大概需要5.16us,NSS上下拉时间在600ns左右:
----------------时间:2021.12.02
五、关于上述实现的出现的bug
1.读取出现数据错误问题 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_19,color_FFFFFF,t_70,g_se,x_16.jpg

在while中正常应该是读出0xA9B7为SX1280的ID,但是实际读出的却是0xB7A9;
主要是SPI的波形没问题,也确实是先读到的0xA9,再读的0xB7;有些奇怪,于是想会不会是DMA太快了,导致单片机读取数据有问题??
于是我在调用SPI_DMA的读写函数后加上个等待,如下
  1. #define SX1280_SPI_WR(pTxData, pRxData, Size)        HAL_SPI_MY_TransmitReceive_DMA(&hspi1, pTxData,pRxData,Size)
  2. /*
  3. 函数功能:读字节
  4. 参数:写入的数据  读取的数据 大小
  5. */
  6. void SpiInOut( uint8_t *txBuffer, uint8_t *rxBuffer, uint16_t size )
  7. {
  8.     SX1280_SPI_WR(txBuffer,rxBuffer,size);
  9. while(__HAL_DMA_GET_COUNTER(&hdma_spi1_rx)!=0)  
  10. }

最终测试发现数据是正确的,读到的SX1280_ID=0xA9B7

2.两包数据之间的间距太长
刚开始也有说到,此次主要是为了减短时间,而在 一、自身SPI的图里,字节间距是比较长的,现在两个字节间的时间是缩短了,但是我发现在使用SPI_DMA读写两包数据间的时间还是比较长的,并且比采用BSP_SPI读写函数的时间还要长,那不是没缩短什么时间吗?
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

可以看到两包数据间隔27.5us,这时间未免有点长了
于是我就开始研究,如何缩短两包数据间的时间

六、研究如何缩短时间
关于这个时间,我猜测很可能是HAL引起的,因为我代码上就跑了个SPI_DMA的读写函数,并且HAL的执行效率较低,所以怀疑是不是HAL执行的东西太多了导致的。

1.对HAL_SPI_MY_TransmitReceive_DMA()函数初步优化
因为HAL_SPI_MY_TransmitReceive_DMA()这个函数我是直接挪用的HAL的,然后在开启DMA前,拉低了下NSS引脚而已,所以我就想着能不能对此函数进行精简。
于是我对这个函数进行删除操作,因为里面有许多的一些判断,我根据我SPI_DMA的配置,将我没设置到代码都给删掉,最终变成了这样
  1. HAL_StatusTypeDef HAL_SPI_MY_TransmitReceive_DMA0(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,uint16_t Size)
  2. {
  3.   HAL_StatusTypeDef errorcode = HAL_OK;

  4.   /* Reset the threshold bit */
  5.   CLEAR_BIT(hspi->Instance->CR2, SPI_CR2_LDMATX | SPI_CR2_LDMARX);

  6.   /* Set fiforxthresold according the reception data length: 8bit */
  7.   SET_BIT(hspi->Instance->CR2, SPI_RXFIFO_THRESHOLD);

  8.   /* Enable the Rx DMA Stream/Channel  */
  9. //  HAL_DMA_Start_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->DR, (uint32_t)pRxData, Size);
  10. {
  11.     /* Disable the peripheral */
  12.     hdma_spi1_rx.Instance->CCR &= ~DMA_CCR_EN;

  13.     /* Configure the source, destination address and the data length */  
  14.     //DMA_SetConfig(hdma_spi1_rx, (uint32_t)&hspi->Instance->DR, (uint32_t)pRxData, Size);
  15. {
  16.   /* Clear all flags */
  17.   hdma_spi1_rx.DmaBaseAddress->IFCR  = (DMA_FLAG_GL1 << hdma_spi1_rx.ChannelIndex);

  18.   /* Configure DMA Channel data length */
  19.   hdma_spi1_rx.Instance->CNDTR = Size;
  20.   
  21.   /* Peripheral to Memory */
  22.   {
  23.     /* Configure DMA Channel source address */
  24.     hdma_spi1_rx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  25.     /* Configure DMA Channel destination address */
  26.     hdma_spi1_rx.Instance->CMAR = (uint32_t)pRxData;
  27.   }
  28. }
  29.     /* Enable the transfer complete, & transfer error interrupts */
  30.     /* Half transfer interrupt is optional: enable it only if associated callback is available */
  31.     hdma_spi1_rx.Instance->CCR |= (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE);

  32.    /* Enable the Peripheral */
  33.    hdma_spi1_rx.Instance->CCR |= DMA_CCR_EN;
  34. }

  35.   /* Enable Rx DMA Request */
  36.   SET_BIT(hspi->Instance->CR2, SPI_CR2_RXDMAEN);


  37.   /* Enable the Tx DMA Stream/Channel  */
  38. //  HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)pTxData, (uint32_t)&hspi->Instance->DR,Size);
  39. {
  40.     /* Disable the peripheral */
  41.     hdma_spi1_tx.Instance->CCR &= ~DMA_CCR_EN;

  42.     /* Configure the source, destination address and the data length */  
  43. // DMA_SetConfig(hdma_spi1_rx, (uint32_t)&hspi->Instance->DR, (uint32_t)pRxData, Size);
  44. {
  45.   /* Clear all flags */
  46.   hdma_spi1_tx.DmaBaseAddress->IFCR  = (DMA_FLAG_GL1 << hdma_spi1_rx.ChannelIndex);

  47.   /* Configure DMA Channel data length */
  48.   hdma_spi1_tx.Instance->CNDTR = Size;
  49.   /* Memory to Peripheral */
  50.   {   
  51.     /* Configure DMA Channel destination address */
  52.     hdma_spi1_tx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  53.     /* Configure DMA Channel source address */
  54.     hdma_spi1_tx.Instance->CMAR = (uint32_t)pTxData;
  55.   }
  56. }
  57.       /* Enable the transfer complete, & transfer error interrupts */
  58.       /* Half transfer interrupt is optional: enable it only if associated callback is available */
  59.       hdma_spi1_tx.Instance->CCR |= (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE);

  60.       HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
  61.       /* Enable the Peripheral */
  62.       hdma_spi1_tx.Instance->CCR |= DMA_CCR_EN;
  63. }
  64.   /* Check if the SPI is already enabled */
  65.   if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
  66.   {
  67.     /* Enable SPI peripheral */
  68.     __HAL_SPI_ENABLE(hspi);
  69.   }
  70.   /* Enable the SPI Error Interrupt Bit */
  71.   __HAL_SPI_ENABLE_IT(hspi, (SPI_IT_ERR));

  72.   /* Enable Tx DMA Request */
  73.   SET_BIT(hspi->Instance->CR2, SPI_CR2_TXDMAEN);

  74.   return errorcode;
  75. }

然后进行测试,测试结果如下,可以看到虽然两包数据间的时间减少了,但还是有个16.48us,这个时间对于我还是太长了,因为我采用BSP_SPI读写函数时,两包数据间也就2-3us左右,这个时间还是明显偏长。
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

2.进一步优化
如何在进一步,缩短时间,再对此函数进行优化? 将其改成直接操作寄存器?

在此需特别感谢 21ic论坛的版主 @呐咯密密
在我调试过程中给予我特别多的帮助。

在他的帮助下,将上述的读写函数,再进一步优化,
  1. HAL_StatusTypeDef HAL_SPI_MY_TransmitReceive_DMA0(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,uint16_t Size)
  2. {
  3.   HAL_StatusTypeDef errorcode = HAL_OK;
  4.   
  5.           hspi->Instance->CR2 |= SPI_CR2_TXDMAEN;
  6.         hspi->Instance->CR2 |= SPI_CR2_RXDMAEN;
  7.         hdma_spi1_tx.Instance->CCR &= ~DMA_CCR_EN;
  8.     hdma_spi1_tx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  9.     hdma_spi1_tx.Instance->CMAR = (uint32_t)pTxData;        
  10.         hdma_spi1_tx.Instance->CNDTR = Size;
  11. hdma_spi1_tx.Instance->CCR |= (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE);
  12.   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
  13.         hdma_spi1_tx.Instance->CCR |= DMA_CCR_EN;
  14.         hdma_spi1_rx.Instance->CCR &= ~DMA_CCR_EN;
  15.     hdma_spi1_rx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  16.     hdma_spi1_rx.Instance->CMAR = (uint32_t)pRxData;        
  17.         hdma_spi1_rx.Instance->CNDTR = Size;
  18.   hdma_spi1_rx.Instance->CCR |= (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE);
  19.         hdma_spi1_rx.Instance->CCR |= DMA_CCR_EN;
  20.   

  21.     /* Enable SPI peripheral */
  22. //    __HAL_SPI_ENABLE(hspi);
  23.   hspi->Instance->CR1 |= SPI_CR1_SPE;
  24.   return errorcode;
  25. }

现在已经全改成寄存器操作了,进行测试

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

结果还有16us的时间????
啥情况,读写函数已经删减到寄存了,还这么多时间???

3.对中断进行优化
这时听大哥说,他用的SPI+DMA不带中断的,这时我才突然反应过来,在中断里也是用的HAL调用的呀,这里也要进行优化!!!
  1. /*while中操作,只执行读写函数*/
  2. uint8_t txbuff[5]={0x19,0x01,0x53,0x00,0x00};
  3. uint8_t rxbuff[5];
  4.   /* Infinite loop */
  5.   /* USER CODE BEGIN WHILE */
  6.   while (1)
  7.   {
  8.     HAL_SPI_MY_TransmitReceive_DMA0(&hspi1, txbuff,rxbuff,5);
  9.     HAL_SPI_MY_TransmitReceive_DMA0(&hspi1, txbuff,rxbuff,5);
  10. //    SX1280_ID = Radio.GetFirmwareVersion();//获取芯片信息 0xA9B7 可用于测试SPI的读写情况
  11.    
  12.     HAL_GPIO_TogglePin(LED_Red_GPIO_Port,LED_Red_Pin);
  13.       HAL_Delay(50);
  14.     /* USER CODE END WHILE */
  15.    
  16.     /* USER CODE BEGIN 3 */
  17.   }
  1. /*SPI_DMA的读写函数*/
  2. HAL_StatusTypeDef HAL_SPI_MY_TransmitReceive_DMA0(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,uint16_t Size)
  3. {
  4.   hspi->Instance->CR2 |= SPI_CR2_TXDMAEN;
  5.         hspi->Instance->CR2 |= SPI_CR2_RXDMAEN;
  6.         hdma_spi1_tx.Instance->CCR &= ~DMA_CCR_EN;
  7.     hdma_spi1_tx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  8.     hdma_spi1_tx.Instance->CMAR = (uint32_t)pTxData;        
  9.         hdma_spi1_tx.Instance->CNDTR = Size;
  10. //hdma_spi1_tx.Instance->CCR |= (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE);
  11.   
  12.   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
  13.         hdma_spi1_tx.Instance->CCR |= DMA_CCR_EN;
  14.         hdma_spi1_rx.Instance->CCR &= ~DMA_CCR_EN;
  15.     hdma_spi1_rx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  16.     hdma_spi1_rx.Instance->CMAR = (uint32_t)pRxData;        
  17.         hdma_spi1_rx.Instance->CNDTR = Size;
  18.   hdma_spi1_rx.Instance->CCR |= (DMA_IT_TC);//| DMA_IT_HT | DMA_IT_TE);
  19.         hdma_spi1_rx.Instance->CCR |= DMA_CCR_EN;

  20.   hspi->Instance->CR1 |= SPI_CR1_SPE;
  21.   }
  1. /*DMA中断*/
  2. void DMA1_Channel2_3_IRQHandler(void)
  3. {
  4.   /* USER CODE BEGIN DMA1_Channel2_3_IRQn 0 */

  5.   /* USER CODE END DMA1_Channel2_3_IRQn 0 */
  6. //  HAL_DMA_IRQHandler(&hdma_spi1_rx);
  7. //  HAL_DMA_IRQHandler(&hdma_spi1_tx);
  8.   /* USER CODE BEGIN DMA1_Channel2_3_IRQn 1 */
  9. if(__HAL_DMA_GET_IT_SOURCE(&hdma_spi1_rx, DMA_IT_TC)){
  10.   hdma_spi1_rx.Instance->CCR &= ~(DMA_IT_TC );
  11.   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
  12.   }
  13.   /* USER CODE END DMA1_Channel2_3_IRQn 1 */
  14. }

现在对上述进行优化了,然后进行测试
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

可以看到两包数据间的间距是1.16us了,虽然说现在数据错误,但是至少说明,修改中断里的操作是没问题的。

4.最终优化结果
现在再进一步测试,去掉中断(我本想是数据传输完后,立刻拉高NSS引脚,但是现在看来是我对TC中断有误解了,因为上图的测试现象说明,触发了TC中断,但是数据并没有传输完)
f9242ca07d3a49c581484ea8d0811e3c.png

现在关闭中断,等待传输完后在拉高NSS,将读写函数进行如下修改
  1. void HAL_SPI_MY_TransmitReceive_DMA0(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,uint16_t Size)
  2. {

  3.   hspi->Instance->CR2 |= SPI_CR2_TXDMAEN;
  4.         hspi->Instance->CR2 |= SPI_CR2_RXDMAEN;
  5.         hdma_spi1_tx.Instance->CCR &= ~DMA_CCR_EN;
  6.     hdma_spi1_tx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  7.     hdma_spi1_tx.Instance->CMAR = (uint32_t)pTxData;        
  8.         hdma_spi1_tx.Instance->CNDTR = Size;
  9. //hdma_spi1_tx.Instance->CCR |= (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE);
  10.   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
  11.   
  12.         hdma_spi1_tx.Instance->CCR |= DMA_CCR_EN;
  13.         hdma_spi1_rx.Instance->CCR &= ~DMA_CCR_EN;
  14.     hdma_spi1_rx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  15.     hdma_spi1_rx.Instance->CMAR = (uint32_t)pRxData;        
  16.         hdma_spi1_rx.Instance->CNDTR = Size;
  17.   
  18. //  hdma_spi1_rx.Instance->CCR |= (DMA_IT_TC);//| DMA_IT_HT | DMA_IT_TE);
  19.   
  20.         hdma_spi1_rx.Instance->CCR |= DMA_CCR_EN;
  21.   

  22.   hspi->Instance->CR1 |= SPI_CR1_SPE;

  23.   while((hspi->Instance->SR & SPI_SR_RXNE)!=RESET);
  24.   while((hspi->Instance->SR & SPI_SR_BSY)!=RESET);
  25.   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
  26.   }

只对SPI_DMA读写函数进行修改,在while中还是发送那些指令,在读写函数中不开启中断,增加等待

while((hspi->Instance->SR & SPI_SR_RXNE)!=RESET);while((hspi->Instance->SR & SPI_SR_BSY)!=RESET);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
测试结果:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_17,color_FFFFFF,t_70,g_se,x_16.jpg

上图中NSS拉低到开始传输数据560ns,数据传输完成到NSS拉高432ns,两包数据间隔是2.09us

七、最终测试数据及配置
1.测试数据
在这可以将我第 五.1中数据错误的修改去掉






  1. #define SX1280_SPI_WR(pTxData, pRxData, Size)        HAL_SPI_MY_TransmitReceive_DMA(&hspi1, pTxData,pRxData,Size)
  2. /*
  3. 函数功能:读字节
  4. 参数:写入的数据  读取的数据 大小
  5. */
  6. void SpiInOut( uint8_t *txBuffer, uint8_t *rxBuffer, uint16_t size )
  7. {
  8.     SX1280_SPI_WR(txBuffer,rxBuffer,size);
  9. //while(__HAL_DMA_GET_COUNTER(&hdma_spi1_rx)!=0)  
  10. }
同时配置成上述读取的数据正确的
  1. while (1)
  2.   {
  3.     HAL_SPI_MY_TransmitReceive_DMA0(&hspi1, txbuff,rxbuff,5);
  4.     HAL_SPI_MY_TransmitReceive_DMA0(&hspi1, txbuff,rxbuff,5);
  5.     /*这里发送了两包数据,正常返回 0xA9B7*/
  6.     SX1280_ID = Radio.GetFirmwareVersion();//获取芯片信息 0xA9B7 可用于测试SPI的读写情况
  7.    
  8.     HAL_GPIO_TogglePin(LED_Red_GPIO_Port,LED_Red_Pin);
  9.       HAL_Delay(50);
  10.     /* USER CODE END WHILE */
  11.    
  12.     /* USER CODE BEGIN 3 */
  13.   }

关于上述while中的测试现象如下
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCY57yY6YeM55qE6K6w5b-G,size_20,color_FFFFFF,t_70,g_se,x_16.jpg

前两包数据间距是2.08us,后面两包数据间距是6.8us,这是因为读取SX1280_ID是通过调用SX1280自身的库,然后再调用我封装的那个SPI_DMA读写函数。

2.配置
  1. /*main函数*/
  2. int main(void)
  3. {
  4.   HAL_Init();
  5.   SystemClock_Config();
  6.   MX_GPIO_Init();
  7.   MX_DMA_Init();
  8.   MX_SPI1_Init();

  9.   Radio.Init( &Callbacks );//初始化回调函数
  10.   Radio.SetRegulatorMode( USE_DCDC ); // 也可以设置为LDO模式但消耗更多功率 USE_DCDC  USE_LDO
  11. uint8_t txbuff[5]={0x19,0x01,0x53,0x00,0x00};
  12. uint8_t rxbuff[5];
  13.   while (1)
  14.   {
  15.     HAL_SPI_MY_TransmitReceive_DMA(&hspi1, txbuff,rxbuff,5);
  16.     HAL_SPI_MY_TransmitReceive_DMA(&hspi1, txbuff,rxbuff,5);
  17.    
  18.     SX1280_ID = Radio.GetFirmwareVersion();//获取芯片信息 0xA9B7 可用于测试SPI的读写情况
  19.    
  20.     HAL_GPIO_TogglePin(LED_Red_GPIO_Port,LED_Red_Pin);
  21.     HAL_Delay(50);
  22.   }
  23. }
  1. /*DMA配置*/
  2. void MX_DMA_Init(void)
  3. {
  4.   __HAL_RCC_DMA1_CLK_ENABLE();//开启DMA时钟即可

  5.   /* 不需要配置DMA的优先级,因为没用到DMA中断 */
  6. //  HAL_NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0, 0);
  7. //  HAL_NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
  8. }
  1. /*SPI配置*/
  2. void MX_SPI1_Init(void)
  3. {

  4.   hspi1.Instance = SPI1;
  5.   hspi1.Init.Mode = SPI_MODE_MASTER;
  6.   hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  7.   hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  8.   hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  9.   hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  10.   hspi1.Init.NSS = SPI_NSS_SOFT;
  11.   hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  12.   hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  13.   hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  14.   hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  15.   hspi1.Init.CRCPolynomial = 7;
  16.   hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  17.   hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
  18.   if (HAL_SPI_Init(&hspi1) != HAL_OK)
  19.   {
  20.     Error_Handler();
  21.   }

  22. }

  23. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
  24. {
  25.   GPIO_InitTypeDef GPIO_InitStruct = {0};
  26.   if(spiHandle->Instance==SPI1)
  27.   {
  28.     /* SPI1 clock enable */
  29.     __HAL_RCC_SPI1_CLK_ENABLE();
  30.     __HAL_RCC_GPIOA_CLK_ENABLE();
  31.     __HAL_RCC_GPIOB_CLK_ENABLE();
  32.     /**SPI1 GPIO Configuration   
  33.     PB3     ------> SPI1_SCK
  34.     PB4     ------> SPI1_MISO
  35.     PB5     ------> SPI1_MOSI
  36.     */
  37.   
  38.     GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5;
  39.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  40.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  41.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  42.     GPIO_InitStruct.Alternate = GPIO_AF0_SPI1;
  43.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  44.     /* SPI1 DMA Init */
  45.     /* SPI1_TX Init */
  46.     hdma_spi1_tx.Instance = DMA1_Channel3;
  47.     hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
  48.     hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
  49.     hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
  50.     hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  51.     hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  52.     hdma_spi1_tx.Init.Mode = DMA_NORMAL;
  53.     hdma_spi1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
  54.     if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
  55.     {
  56.       Error_Handler();
  57.     }
  58.     __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi1_tx);

  59.     /* SPI1_RX Init */
  60.     hdma_spi1_rx.Instance = DMA1_Channel2;
  61.     hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
  62.     hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
  63.     hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
  64.     hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  65.     hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  66.     hdma_spi1_rx.Init.Mode = DMA_NORMAL;
  67.     hdma_spi1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
  68.     if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK)
  69.     {
  70.       Error_Handler();
  71.     }
  72.     __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi1_rx);

  73.     /* 未用到SPI中断,也可不用配置 */
  74. //    HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
  75. //    HAL_NVIC_EnableIRQ(SPI1_IRQn);
  76.   }
  77. }
  1. /*SPI_DMA读写函数*/
  2. void HAL_SPI_MY_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,uint16_t Size)
  3. {
  4.   hspi->Instance->CR2 |= SPI_CR2_TXDMAEN;
  5.         hspi->Instance->CR2 |= SPI_CR2_RXDMAEN;
  6.         hdma_spi1_tx.Instance->CCR &= ~DMA_CCR_EN;
  7.   hdma_spi1_tx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  8.   hdma_spi1_tx.Instance->CMAR = (uint32_t)pTxData;        
  9.         hdma_spi1_tx.Instance->CNDTR = Size;

  10.   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
  11.   
  12.         hdma_spi1_tx.Instance->CCR |= DMA_CCR_EN;
  13.         hdma_spi1_rx.Instance->CCR &= ~DMA_CCR_EN;
  14.   hdma_spi1_rx.Instance->CPAR = (uint32_t)&hspi->Instance->DR;
  15.   hdma_spi1_rx.Instance->CMAR = (uint32_t)pRxData;        
  16.         hdma_spi1_rx.Instance->CNDTR = Size;
  17.         hdma_spi1_rx.Instance->CCR |= DMA_CCR_EN;
  18.   
  19.   hspi->Instance->CR1 |= SPI_CR1_SPE;

  20.   while((hspi->Instance->SR & SPI_SR_RXNE)!=RESET);
  21.   while((hspi->Instance->SR & SPI_SR_BSY)!=RESET);
  22.   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
  23. }

关于具体代码,稍后放到github上(附件上传不了)
关于如何减少时间,大家有更好的方法,欢迎讨论联系,谢谢!
最后再次感谢 21ic论坛的版主 @呐咯密密
----------------时间:2021.12.03
关于测试工程,github链接:https://github.com/Gu-Yue-Hu/SPI_DMA_HAL.git














   

打赏榜单

21小跑堂 打赏了 50.00 元 2021-12-03
理由:恭喜通过原创文章审核!请多多加油哦!

 楼主| chenyuanjiyi 发表于 2021-12-17 09:38 | 显示全部楼层
本帖最后由 chenyuanjiyi 于 2022-2-23 09:15 编辑

补充一点:针对ST不同系列芯片,上面的SPI读写函数不能直接使用,有一些区别

今天在对F4系列芯片采用上述SPI读写函数是有问题的,后经修改,如下,才能正常使用
  1. void HAL_SPI_MY_TransmitReceive_DMA0(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,uint16_t Size)
  2. {
  3. SPI_DMA_Base_Registers *regs = (SPI_DMA_Base_Registers *)hdma_spi2_rx.StreamBaseAddress;

  4. hdma_spi2_rx.Instance->CR &=  ~DMA_SxCR_EN;
  5. hdma_spi2_rx.Instance->PAR = (uint32_t)&hspi->Instance->DR;
  6. hdma_spi2_rx.Instance->M0AR = (uint32_t)pRxData;
  7. hdma_spi2_rx.Instance->NDTR = Size;
  8.   regs->IFCR = 0x3FU << hdma_spi2_rx.StreamIndex;
  9. hdma_spi2_rx.Instance->CR |= DMA_SxCR_EN;
  10. hspi->Instance->CR2 |= SPI_CR2_RXDMAEN;
  11.   

  12. SPI_DMA_Base_Registers *regs1 = (SPI_DMA_Base_Registers *)hdma_spi2_tx.StreamBaseAddress;

  13. hdma_spi2_tx.Instance->CR &= ~DMA_SxCR_EN;
  14. hdma_spi2_tx.Instance->PAR = (uint32_t)&hspi->Instance->DR;
  15. hdma_spi2_tx.Instance->M0AR = (uint32_t)pTxData;
  16. hdma_spi2_tx.Instance->NDTR = Size;
  17. regs1->IFCR = 0x3FU << hdma_spi2_tx.StreamIndex;
  18. // hdma_spi2_tx.Instance->CR  |= DMA_IT_TC ;
  19. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);  
  20. hdma_spi2_tx.Instance->CR |= DMA_SxCR_EN;


  21. hspi->Instance->CR1 |= SPI_CR1_SPE;
  22. hspi->Instance->CR2 |= SPI_CR2_TXDMAEN;

  23. regs->IFCR = DMA_FLAG_TCIF0_4 << hdma_spi2_rx.StreamIndex;
  24. regs->IFCR = 0x3FU << hdma_spi2_rx.StreamIndex;
  25. regs1->IFCR = DMA_FLAG_TCIF0_4 << hdma_spi2_tx.StreamIndex;
  26. regs1->IFCR = 0x3FU << hdma_spi2_tx.StreamIndex;

  27. while((hspi->Instance->SR & SPI_SR_RXNE)!=RESET);
  28. while((hspi->Instance->SR & SPI_SR_BSY)!=RESET);


  29. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);   
  30. }
对比F0系列的,增加了
egs->IFCR = DMA_FLAG_TCIF0_4 << hdma_spi2_rx.StreamIndex;
regs->IFCR = 0x3FU << hdma_spi2_rx.StreamIndex;
regs1->IFCR = DMA_FLAG_TCIF0_4 << hdma_spi2_tx.StreamIndex;
regs1->IFCR = 0x3FU << hdma_spi2_tx.StreamIndex;

这些操作。

总结:
对于HAL上使用SPI_DMA读写函数时,按照帖子上思路来进行修改是没问题的,测试时,首先对HAL_SPI_TransmitReceive_DMA()函数做减法操作,再对HAL_DMA_IRQHandler();做减法操作,最终根据帖子中F0系列修改后的SPI_DMA读写函数格式进行修改,记得使用时先对函数进行读写验证,不仅要看他的波形,还要看他实际的读写的数据是否正确,才能最终使用!!

关于采用HAL还是寄存器,我的想法是通过cubemx(HAL库),自动生成相关的配置,然后在需要高速、效率的时候,将部分HAL库的内容改成使用寄存器操作,这样是相对简单点的,当然改完需要测试有没有问题。



香水城 发表于 2021-12-3 17:10 | 显示全部楼层
总结后更有收获!
香水城 发表于 2021-12-3 17:11 | 显示全部楼层
本帖最后由 香水城 于 2021-12-3 17:32 编辑

总结后更有收获!


关于SPI+DMA的使用问题

呐咯密密 发表于 2021-12-3 17:16 | 显示全部楼层
寄存器永远的神!哈哈哈
 楼主| chenyuanjiyi 发表于 2021-12-3 17:23 | 显示全部楼层
呐咯密密 发表于 2021-12-3 17:16
寄存器永远的神!哈哈哈

哈哈  感谢大佬的帮助
andreilei 发表于 2021-12-4 15:55 | 显示全部楼层
公开课《电子的奇妙世界》用定格动画的方式讲述发生在电子世界的故事,呈现元器件们的奇妙冒险!
整容二极管
晶振的噩梦
谁杀死了LED
保险丝的葬礼
妄自尊大的发光二极管
————
电子的奇妙世界,视频公开课
https://open.21ic.com/open/lesson/5766
6552918 发表于 2021-12-4 22:14 | 显示全部楼层
讲解的很细致,nice!
liaojihua2010 发表于 2021-12-6 08:47 | 显示全部楼层
一早就认真看完了你的帖子,不错的总结,这个习惯很好的。
hi0712 发表于 2021-12-10 08:49 | 显示全部楼层
哎 从汇编过来 一直到hal库的飘过
Gavin3389 发表于 2021-12-10 09:00 | 显示全部楼层
写的真好,这种实验的思路很值得学习
不过,看倒后面,有点跟不上了,
我可能只适合软件NSS
laocuo1142 发表于 2021-12-13 08:21 | 显示全部楼层
哈哈,我最近也在调SX1280,现在HAL库是主流啊
tpgf 发表于 2022-1-3 16:31 | 显示全部楼层
有的缩写我看不懂啊
磨砂 发表于 2022-1-3 16:33 | 显示全部楼层
我 还是喜欢用hal库
晓伍 发表于 2022-1-3 16:34 | 显示全部楼层
看个人习惯了
八层楼 发表于 2022-1-3 16:37 | 显示全部楼层
YYDS是什么意思啊
观海 发表于 2022-1-3 16:38 | 显示全部楼层
各有各的有点吧
 楼主| chenyuanjiyi 发表于 2022-1-4 09:18 | 显示全部楼层
HAL确实要方便,在加上cubemx,点几下就配置好了,要是寄存器的话,单纯配置就要耗费比较多的时间了;
根据需求来,像上面那个DMA_SPI的,HAL的运行时间就相对慢点,所以就直接对HAL提供的SPI_DMA读写函数进行删减优化了,逐渐减成寄存器操作了
122541325 发表于 2022-1-4 15:19 | 显示全部楼层
用寄存器肯定是比库的效率高了,我之前的公司还不准用寄存器,只是前期开发慢了点,我还是比较喜欢用库,库函数解决不了了,然后会从寄存器上想办法
kiwis66 发表于 2022-1-5 09:13 | 显示全部楼层
离开寄存器已经好久~
您需要登录后才可以回帖 登录 | 注册

本版积分规则

16

主题

134

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部