请教STM32的 SPI 同时中断收发问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了请教STM32的 SPI 同时中断收发问题相关的知识,希望对你有一定的参考价值。

请教STM32的 SPI 中断问题
STM32是从设备,主设备先发送一个数据包,然后等待接收返回的数据包
在 SPI2_IRQHandler 中,这样操作:
voidSPI2_IRQHandler(void)

SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,DISABLE);
if(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==SET)

buff=SPI_I2S_ReceiveData(SPI2);
printf("SPI2= _%x ",buff);
SPI_I2S_ClearFlag(SPI2,SPI_I2S_IT_RXNE);
SPI2_WriteByte(buff);

SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE);

SPI2接收到的数是正确的,但是为什么会SPI2发出去的数总会错位呢?
比如:主机发1,2,3,4,5,6
SPI2接收中断收到的是1,2,3,4,5,6
但是主机接收到的SPI2发送的数据却是0,1,2,3,4,5

参考技术A 因为spi2发送的数据需要在中断前放进dr。而你在中断中把数据放进dr,那么这个数据在下次中断才会发送出去。 参考技术B 需要帮你完成吗

STM32 cubeMX:使用中断触发SPI DMA中断

【中文标题】STM32 cubeMX:使用中断触发SPI DMA中断【英文标题】:STM32 cubeMX: triggering SPI DMA interrupt using interrupt 【发布时间】:2019-05-28 16:52:33 【问题描述】:

我目前正在练习使用 SPI+DMA 将数据发送到 SPI 显示器。 显示的数据顺序如下:

[pull CS low]->[pull D/C low]->[1 SPI byte of CMD]->[pull D/C high]->[n SPI byte of data]->[pull CS high ]。其中 D/C 引脚是 GPIO 引脚。

我的想法是先将 CS 和 D/C 拉低,然后通过HAL_SPI_Transmit_IT(); 发送 1 个字节的 CMD 并将 D/C 引脚拉高并在 SPI 中断程序中启动 DMA 传输。并且在 DMA TxComplete 中断中 CS 引脚会被拉高。

我的 SPI 设置为 8 位数据长度,DMA 设置为内存到外设和增量模式。

我正在使用cubeMX生成代码,这里大致是我的代码:

uint8_t displayData[DIS_DATA_BUFF_SIZE];

int main(void)

    ...init stuff from cubeMX
    cmdBuffer[0].cmd = 0xAA;
    cmdBuffer[0].amountOfData = 10;
    cmdBuffer[0].pDataStart = displayData;

    while (1)
    
        HAL_Delay(500);

        cmdBuffer[0].status = IN_USE;
        pExecuteCmd = &cmdBuffer[0];

        SPI_START();
        DIS_CMD_MODE_ON();
        HAL_SPI_Transmit_IT(&hspi2, &pExecuteCmd->cmd, 1);
    

这是我的 SPI 中断例程

void SPI2_IRQHandler(void)

  /* USER CODE BEGIN SPI2_IRQn 0 */
    uint8_t startDMA = 0;
    if(__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE))
        if(pExecuteCmd->status == EXE_CMD)
            DIS_CMD_MODE_OFF();
            if(pExecuteCmd->amountOfData == 0)
                SPI_END();
                pExecuteCmd->status = EMPTY;
            else
                pExecuteCmd->status = EXE_DATA;
                startDMA = 1;
            
        
        else if(pExecuteCmd->status == IN_USE)
             pExecuteCmd->status = EXE_CMD;
        
    

  /* USER CODE END SPI2_IRQn 0 */
    HAL_SPI_IRQHandler(&hspi2);
    if(startDMA)
    
    
        HAL_SPI_Transmit_DMA(&hspi2, pExecuteCmd->pDataStart,
                    pExecuteCmd->amountOfData);
    
  /* USER CODE BEGIN SPI2_IRQn 1 */

  /* USER CODE END SPI2_IRQn 1 */

这是我的 DMA 中断例程的最后一部分

void DMA1_Channel5_IRQHandler(void)

  /* USER CODE BEGIN DMA1_Channel5_IRQn 0 */
    if(__HAL_DMA_GET_FLAG(&hdma_spi2_tx, DMA_FLAG_TC5))
        SPI_END();
        pExecuteCmd->status = EMPTY;
    


  /* USER CODE END DMA1_Channel5_IRQn 0 */
    HAL_DMA_IRQHandler(&hdma_spi2_tx);
  /* USER CODE BEGIN DMA1_Channel5_IRQn 1 */

  /* USER CODE END DMA1_Channel5_IRQn 1 */

在我当前的尝试中,main 启动了 spi CMD 传输,我希望 DMA 传输将由HAL_SPI_Transmit_DMA() 触发。但是 DMA 只能启动一次,这是第一次传输。然后HAL_SPI_Transmit_DMA() 似乎因为hspi->State != HAL_SPI_STATE_READY 而返回HAL_BUSY

我不确定我在哪里做错了。任何人都可以提供任何提示,驱动基于中断的 DMA 传输的正确方法是什么?

谢谢。

更新1

我调查后得到了一个奇怪的结果。 由于我只有一个逻辑分析仪作为调试工具,因此我将引脚切换作为调试按摩。我在 SPI_IRQHandler 中放了一个如下:

void SPI2_IRQHandler(void)

/* USER CODE BEGIN SPI2_IRQn 0 */
    uint8_t startDMA = 0;
    if(__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE))
        if(pExecuteCmd->status == EXE_CMD)
            DIS_CMD_MODE_OFF();
            if(pExecuteCmd->amountOfData == 0)
                SPI_END();
                pExecuteCmd->status = EMPTY;
            else
                pExecuteCmd->status = EXE_DATA;
                startDMA = 1;
            
        
        else if(pExecuteCmd->status == IN_USE)
            pExecuteCmd->status = EXE_CMD;
        
    
    /* USER CODE END SPI2_IRQn 0 */
    HAL_SPI_IRQHandler(&hspi2);

    if(startDMA)
    
        if(hspi2.State == HAL_SPI_STATE_READY)

            HAL_GPIO_TogglePin(DIS_NRST_GPIO_Port, DIS_NRST_Pin);
            HAL_GPIO_TogglePin(DIS_NRST_GPIO_Port, DIS_NRST_Pin);
            //^^^^^^^toggle pin showing the state is READY^^^^^//
            HAL_SPI_Transmit_DMA(&hspi2, pExecuteCmd->pDataStart,
                            pExecuteCmd->amountOfData);
        
    

并且还在 HAL_SPI_Transmit_DMA() 的末尾放置了另一个切换引脚。 我把它放在函数的末尾。

HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)

  HAL_StatusTypeDef errorcode = HAL_OK;

  /* Check Direction parameter */
  assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE(hspi->Init.Direction));

  /* Process Locked */
  __HAL_LOCK(hspi);

  if(hspi->State != HAL_SPI_STATE_READY)
  
    errorcode = HAL_BUSY;
    goto error;
  

  if((pData == NULL) || (Size == 0U))
  

    errorcode = HAL_ERROR;
    goto error;
  

  /* Set the transaction information */
  hspi->State       = HAL_SPI_STATE_BUSY_TX;
  hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
  hspi->pTxBuffPtr  = (uint8_t *)pData;
  hspi->TxXferSize  = Size;
  hspi->TxXferCount = Size;

  /* Init field not used in handle to zero */
  hspi->pRxBuffPtr  = (uint8_t *)NULL;
  hspi->TxISR       = NULL;
  hspi->RxISR       = NULL;
  hspi->RxXferSize  = 0U;
  hspi->RxXferCount = 0U;

  /* Configure communication direction : 1Line */
  if(hspi->Init.Direction == SPI_DIRECTION_1LINE)
  
    SPI_1LINE_TX(hspi);
  

#if (USE_SPI_CRC != 0U)
  /* Reset CRC Calculation */
  if(hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  
    SPI_RESET_CRC(hspi);
  
#endif /* USE_SPI_CRC */

  /* Set the SPI TxDMA Half transfer complete callback */
  hspi->hdmatx->XferHalfCpltCallback = SPI_DMAHalfTransmitCplt;

  /* Set the SPI TxDMA transfer complete callback */
  hspi->hdmatx->XferCpltCallback = SPI_DMATransmitCplt;

  /* Set the DMA error callback */
  hspi->hdmatx->XferErrorCallback = SPI_DMAError;

  /* Set the DMA AbortCpltCallback */
  hspi->hdmatx->XferAbortCallback = NULL;

  /* Enable the Tx DMA Stream */
  HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)hspi->pTxBuffPtr, (uint32_t)&hspi->Instance->DR, hspi->TxXferCount);

  /* Check if the SPI is already enabled */
  if((hspi->Instance->CR1 &SPI_CR1_SPE) != SPI_CR1_SPE)
  
    /* Enable SPI peripheral */
    __HAL_SPI_ENABLE(hspi);
  

  /* Enable the SPI Error Interrupt Bit */
  SET_BIT(hspi->Instance->CR2, SPI_CR2_ERRIE);

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

error :
  /* Process Unlocked */
  __HAL_UNLOCK(hspi);
  HAL_GPIO_WritePin(DIS_DC_GPIO_Port, DIS_DC_Pin, RESET);
  HAL_GPIO_WritePin(DIS_DC_GPIO_Port, DIS_DC_Pin, SET);
  return errorcode;

结果: DMA 传输仅在第一次工作,然后没有数据通过 DMA 传输。而且我只切换了一次 DIS_DC_Pin,多次切换 DIS_NRST_Pin。这意味着,该进程确实在中断例程中进入了if(hspi2.State == HAL_SPI_STATE_READY),但没有进入HAL_SPI_Transmit_DMA()???

screen shot of logic analyzer

怎么会这样?

【问题讨论】:

调用HAL_SPI_Transmit_IT后你的SPI有输出数据吗?我希望您使用 HAL_Delay 的解决方案只是暂时的。使用中断然后希望它们在固定延迟后完成不是很有效。 您好,此代码仅用于测试机制,并非最终实现。是的,HAL_SPI_Transmit_IT() 确实按预期传输了 0xAA。但是对于 HAL_SPI_Transmit_DMA(),它只在启动后的第一个中断时起作用,然后 HAL_SPI_Transmit_DMA 不再做任何事情,它返回 HAL_BUSY。 顺便说一下,我在main中单独尝试了HAL_SPI_Transmit_DMA(),该函数本身起作用,它发出分配的数据量。但是当我按照我在帖子中描述的那样将它放入中断例程时它不起作用。 您是否尝试过调试 HAL_SPI_IRQHandler() 函数?该函数负责将 HAL 状态设置为例如HAL_OK 或 HAL_BUSY。如果此功能后的状态不正常,您的发送 dma 功能将无法正常工作。 谢谢你的意见,我做了更多的调试,我在原帖里有更新,你能看看吗? 【参考方案1】:

我在希望 SPI 传输“在后台”运行的状态机中遇到了同样的问题。我有/有一个 SPI Tx-Rx 状态机,其中下一个 Tx/Rx 由一次最后一次传输的 DMA Rx/Tx 完成回调中断触发。

症状是相同的:在第一次调用 SPI_Tx_DMA 后,SPI_Rx_DMA 序列 - 完全由一个 Tx 和一个 Rx 组成 - 当我尝试下一个 Tx 时,SPI 似乎被卡住并报告“HAL_BUSY”错误。

解决方案是检查 CubeMX 中的 SPI DMA 设置,并将 RX DMA 通道的 DMA 模式从循环更改为正常。我不知道为什么我最初选择“循环” - 可能是因为当我第一次在 CubeMX 中设置 SPI 时,这对我来说似乎有点“合理”......

我没有进一步调查,所以我无法提供复杂的答案,幕后发生了什么......

【讨论】:

以上是关于请教STM32的 SPI 同时中断收发问题的主要内容,如果未能解决你的问题,请参考以下文章

STM32 cubeMX:使用中断触发SPI DMA中断

STM32 HAL SPI 中断处理

STM32F0 在 SPI 接收中断时从 STOP 退出

STM32F4 SPI接收中断不起作用

spidma发送更新中断

请教STM32F103与DSP采用SPI通信的问题!