记录STMH7使用LL库查询方式发送SPI的坑

文章目录

  • 一、前言背景
  • 二、从官方Demo中寻找启发
    • (一)官方Demo中SPI发送部分
    • (二)参考手册
  • 三、仿照HAL库完成LL库SPI查询方式的编写
  • 参考资料

一、前言背景

笔者在使用SPI驱动LCD屏幕时,屏幕一直不亮。
在一系列调试中,笔者换回了LCD屏幕例程中的HAL库代码,屏幕成功点亮。于是发现LL库在SPI上的使用确实有很多认知上的缺失,现在更新一篇博客以此记录。

二、从官方Demo中寻找启发

(一)官方Demo中SPI发送部分

在网上冲浪时,了解到STM32官方的NUCLEO-743ZI里面有LL库的SPI的Demo,是使用的发送TXP中断的方式进行发送。

	/* Lock GPIO for master to avoid glitches on the clock output */
	LL_SPI_EnableGPIOControl(SPI1);
	LL_SPI_EnableMasterRxAutoSuspend(SPI1);
	
	/* Set number of date to transmit */
	LL_SPI_SetTransferSize(SPI1, SPIx_NbDataToTransmit);
	
	/* Enable SPI1 */
	LL_SPI_Enable(SPI1);
	
	/* Enable TXP Interrupt */
	LL_SPI_EnableIT_TXP(SPI1);
	
	/* Enable RXP Interrupt */
	LL_SPI_EnableIT_RXP(SPI1);
	
	/* Enable SPI Errors Interrupt */
	LL_SPI_EnableIT_CRCERR(SPI1);
	LL_SPI_EnableIT_UDR(SPI1);
	LL_SPI_EnableIT_OVR(SPI1);
	LL_SPI_EnableIT_EOT(SPI1);
	
	/* 省略 */
	
	LL_SPI_StartMasterTransfer(SPI1);

之后在中断服务函数中加入:

void SPI1_IRQHandler(void)
{

   /* 省略 */
    /* Check TXP flag value in ISR register */
    if((LL_SPI_IsActiveFlag_TXP(SPI1) && LL_SPI_IsEnabledIT_TXP(SPI1)))
    {
      /* Call function Reception Callback */
      SPI1_Tx_Callback();
      return;
    }
    /* Check EOT flag value in ISR register */
    if(LL_SPI_IsActiveFlag_EOT(SPI1) && LL_SPI_IsEnabledIT_EOT(SPI1))
    {
      /* Call function Reception Callback */
      SPI1_EOT_Callback();
      return;
    }
}
   /* 省略 */

编写中断回调函数

/**
  * @brief  Function called from SPI1 IRQ Handler when TXP flag is set
  *         Function is in charge  to transmit byte on SPI lines.
  * @param  None
  * @retval None
  */
void  SPI1_Tx_Callback(void)
{
  /* Write character in Data register.
   * TXP flag is cleared by filling data into TXDR register */
  LL_SPI_TransmitData8(SPI1, SPIx_TxBuffer[SPI1_TransmitIndex++]);
}
/**
  * @brief  Function called from SPI1 IRQ Handler when EOT flag is set
  *         Function is in charge of transfer close down.
  * @param  None
  * @retval None
  */
void  SPI1_EOT_Callback(void)
{
  LL_SPI_Disable(SPI1);
  LL_SPI_DisableIT_TXP(SPI1);
  LL_SPI_DisableIT_RXP(SPI1);
  LL_SPI_DisableIT_CRCERR(SPI1);
  LL_SPI_DisableIT_OVR(SPI1);
  LL_SPI_DisableIT_UDR(SPI1);
  LL_SPI_DisableIT_EOT(SPI1);
}

从中深受启发,于是去细查手册。

(二)参考手册

先去了解了一下EOT事件:
记录STMH7使用LL库查询方式发送SPI的坑_第1张图片
之后又了解了一下TXP事件:记录STMH7使用LL库查询方式发送SPI的坑_第2张图片
这二人正好对应着一个感性的流程:也就是空了就放入数据,传输完成就结束本次传输。

去手册看看SPI的配置方法:
记录STMH7使用LL库查询方式发送SPI的坑_第3张图片
我们忽略掉STM32CubeMX可以给我生成好CFG寄存器的代码,以及笔者本次不使用DMA和CRC,故我们可以发现重点有——对 SPI_CR2 寄存器进行写操作以选择传输的长度,如果值未知,则必须将 TSIZE 编程为零。
也就是:
记录STMH7使用LL库查询方式发送SPI的坑_第4张图片
对应到LL库函数,可以据此找到:

/**
  * @brief  Set transfer size
  * @note    Count is the number of frame to be transferred
  * @rmtoll CR2          TSIZE         LL_SPI_SetTransferSize
  * @param  SPIx SPI Instance
  * @param  Count 0..0xFFFF
  * @retval None
  */
__STATIC_INLINE void LL_SPI_SetTransferSize(SPI_TypeDef *SPIx, uint32_t Count)
{
  MODIFY_REG(SPIx->CR2, SPI_CR2_TSIZE, Count);
}

然后是使能SPI的步骤:
记录STMH7使用LL库查询方式发送SPI的坑_第5张图片
对于笔者想实现的功能的重点为——当 SPI 处于使能状态,CSTART 位置 1 且 TxFIFO 非空时,或者对 TxFIFO 执行下一次写操 作时,全双工模式(或任何只发送模式)下的主器件开始通信。
算是一个坑点吧,部分人认为直接LL_SPI_Enable就能开始收发了,其实还需要置位CSTART 。
记录STMH7使用LL库查询方式发送SPI的坑_第6张图片
记住一个细节,CSTART位是硬件清零的,所以我们使用时只需要执行CSTART 置位操作。

对应LL库代码为:

/**
  * @brief  Start effective transfer on wire for Master configuration
  * @rmtoll CR1          CSTART        LL_SPI_StartMasterTransfer
  * @param  SPIx SPI Instance
  * @retval None
  */
__STATIC_INLINE void LL_SPI_StartMasterTransfer(SPI_TypeDef *SPIx)
{
  SET_BIT(SPIx->CR1, SPI_CR1_CSTART);
}

也就回到了Demo中使用LL_SPI_StartMasterTransfer(SPI1);,正是使能发送中重要的一环。

最后再看看SPI 数据发送的过程:记录STMH7使用LL库查询方式发送SPI的坑_第7张图片
可以看到——发送处于激活状态时,如果应用软件有足够的空间来将至少一个完整的数据包(通过 SPI_CFG1 寄存器的 FTHVL[3:0] 位进 行定义)推入到发送 FIFO 中,则该标志由硬件置 1。
我们可以观察TXP的状态来判断发送 FIFO 中的空间是否为空。

三、仿照HAL库完成LL库SPI查询方式的编写

知道了这些我们再去看看在笔者测试中已经实现功能的HAL库是如何操作的:

/**
  * @brief  Transmit an amount of data in blocking mode.
  * @param  hspi   : pointer to a SPI_HandleTypeDef structure that contains
  *                  the configuration information for SPI module.
  * @param  pData  : pointer to data buffer
  * @param  Size   : amount of data to be sent
  * @param  Timeout: Timeout duration
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
#if defined (__GNUC__)
  __IO uint16_t *ptxdr_16bits = (__IO uint16_t *)(&(hspi->Instance->TXDR));
#endif /* __GNUC__ */

  uint32_t tickstart;
  HAL_StatusTypeDef errorcode = HAL_OK;

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

  /* Lock the process */
  __HAL_LOCK(hspi);

  /* Init tickstart for timeout management*/
  tickstart = HAL_GetTick();

  if (hspi->State != HAL_SPI_STATE_READY)
  {
    errorcode = HAL_BUSY;
    __HAL_UNLOCK(hspi);
    return errorcode;
  }

  if ((pData == NULL) || (Size == 0UL))
  {
    errorcode = HAL_ERROR;
    __HAL_UNLOCK(hspi);
    return errorcode;
  }

  /* 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  = NULL;
  hspi->RxXferSize  = (uint16_t) 0UL;
  hspi->RxXferCount = (uint16_t) 0UL;
  hspi->TxISR       = NULL;
  hspi->RxISR       = NULL;

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

  /* Set the number of data at current transfer */
  MODIFY_REG(hspi->Instance->CR2, SPI_CR2_TSIZE, Size);

  /* Enable SPI peripheral */
  __HAL_SPI_ENABLE(hspi);

  if (hspi->Init.Mode == SPI_MODE_MASTER)
  {
    /* Master transfer start */
    SET_BIT(hspi->Instance->CR1, SPI_CR1_CSTART);
  }

  /* Transmit data in 32 Bit mode */
  if (hspi->Init.DataSize > SPI_DATASIZE_16BIT)
  {
    /* Transmit data in 32 Bit mode */
    while (hspi->TxXferCount > 0UL)
    {
      /* Wait until TXP flag is set to send data */
      if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXP))
      {
        *((__IO uint32_t *)&hspi->Instance->TXDR) = *((uint32_t *)hspi->pTxBuffPtr);
        hspi->pTxBuffPtr += sizeof(uint32_t);
        hspi->TxXferCount--;
      }
      else
      {
        /* Timeout management */
        if ((((HAL_GetTick() - tickstart) >=  Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
        {
          /* Call standard close procedure with error check */
          SPI_CloseTransfer(hspi);

          /* Unlock the process */
          __HAL_UNLOCK(hspi);

          SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_TIMEOUT);
          hspi->State = HAL_SPI_STATE_READY;
          return HAL_TIMEOUT;
        }
      }
    }
  }
  /* Transmit data in 16 Bit mode */
  else if (hspi->Init.DataSize > SPI_DATASIZE_8BIT)
  {
    /* Transmit data in 16 Bit mode */
    while (hspi->TxXferCount > 0UL)
    {
      /* Wait until TXP flag is set to send data */
      if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXP))
      {
        if ((hspi->TxXferCount > 1UL) && (hspi->Init.FifoThreshold > SPI_FIFO_THRESHOLD_01DATA))
        {
          *((__IO uint32_t *)&hspi->Instance->TXDR) = *((uint32_t *)hspi->pTxBuffPtr);
          hspi->pTxBuffPtr += sizeof(uint32_t);
          hspi->TxXferCount -= (uint16_t)2UL;
        }
        else
        {
#if defined (__GNUC__)
          *ptxdr_16bits = *((uint16_t *)hspi->pTxBuffPtr);
#else
          *((__IO uint16_t *)&hspi->Instance->TXDR) = *((uint16_t *)hspi->pTxBuffPtr);
#endif /* __GNUC__ */
          hspi->pTxBuffPtr += sizeof(uint16_t);
          hspi->TxXferCount--;
        }
      }
      else
      {
        /* Timeout management */
        if ((((HAL_GetTick() - tickstart) >=  Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
        {
          /* Call standard close procedure with error check */
          SPI_CloseTransfer(hspi);

          /* Unlock the process */
          __HAL_UNLOCK(hspi);

          SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_TIMEOUT);
          hspi->State = HAL_SPI_STATE_READY;
          return HAL_TIMEOUT;
        }
      }
    }
  }
  /* Transmit data in 8 Bit mode */
  else
  {
    while (hspi->TxXferCount > 0UL)
    {
      /* Wait until TXP flag is set to send data */
      if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXP))
      {
        if ((hspi->TxXferCount > 3UL) && (hspi->Init.FifoThreshold > SPI_FIFO_THRESHOLD_03DATA))
        {
          *((__IO uint32_t *)&hspi->Instance->TXDR) = *((uint32_t *)hspi->pTxBuffPtr);
          hspi->pTxBuffPtr += sizeof(uint32_t);
          hspi->TxXferCount -= (uint16_t)4UL;
        }
        else if ((hspi->TxXferCount > 1UL) && (hspi->Init.FifoThreshold > SPI_FIFO_THRESHOLD_01DATA))
        {
#if defined (__GNUC__)
          *ptxdr_16bits = *((uint16_t *)hspi->pTxBuffPtr);
#else
          *((__IO uint16_t *)&hspi->Instance->TXDR) = *((uint16_t *)hspi->pTxBuffPtr);
#endif /* __GNUC__ */
          hspi->pTxBuffPtr += sizeof(uint16_t);
          hspi->TxXferCount -= (uint16_t)2UL;
        }
        else
        {
          *((__IO uint8_t *)&hspi->Instance->TXDR) = *((uint8_t *)hspi->pTxBuffPtr);
          hspi->pTxBuffPtr += sizeof(uint8_t);
          hspi->TxXferCount--;
        }
      }
      else
      {
        /* Timeout management */
        if ((((HAL_GetTick() - tickstart) >=  Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U))
        {
          /* Call standard close procedure with error check */
          SPI_CloseTransfer(hspi);

          /* Unlock the process */
          __HAL_UNLOCK(hspi);

          SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_TIMEOUT);
          hspi->State = HAL_SPI_STATE_READY;
          return HAL_TIMEOUT;
        }
      }
    }
  }

  /* Wait for Tx (and CRC) data to be sent */
  if (SPI_WaitOnFlagUntilTimeout(hspi, SPI_FLAG_EOT, RESET, tickstart, Timeout) != HAL_OK)
  {
    SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
  }

  /* Call standard close procedure with error check */
  SPI_CloseTransfer(hspi);

  /* Unlock the process */
  __HAL_UNLOCK(hspi);

  hspi->State = HAL_SPI_STATE_READY;

  if (hspi->ErrorCode != HAL_SPI_ERROR_NONE)
  {
    return HAL_ERROR;
  }
  return errorcode;
}

我们发现HAL库在操作时反复EnableSPI和DisableSPI,又回头查询手册发现:
记录STMH7使用LL库查询方式发送SPI的坑_第8张图片
SPI禁止时,是可以清空FIFO内容的。

按照笔者的个人配置情况简化HAL库的分支情况:

  • 8位数据传输
  • 只需发送
  • FIFO的THRESHOLD位1DATA(相对于没有)
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
/* 省略 */
  hspi->pTxBuffPtr  = (uint8_t *)pData;
  hspi->TxXferSize  = Size;
  hspi->TxXferCount = Size;
  SPI_1LINE_TX(hspi);
  /* Set the number of data at current transfer */
  MODIFY_REG(hspi->Instance->CR2, SPI_CR2_TSIZE, Size);
  /* Enable SPI peripheral */
  __HAL_SPI_ENABLE(hspi);
   /* Master transfer start */
   SET_BIT(hspi->Instance->CR1, SPI_CR1_CSTART);
 
   while (hspi->TxXferCount > 0UL)
   {
   /* 省略 */
     /* Wait until TXP flag is set to send data */
     if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXP))
     {
        *((__IO uint8_t *)&hspi->Instance->TXDR) = *((uint8_t *)hspi->pTxBuffPtr);
        hspi->pTxBuffPtr += sizeof(uint8_t);
        hspi->TxXferCount--;
     }
     else
     {
       /* Timeout management */
      /*	超时处理  * / 
         return HAL_TIMEOUT;
       }
     }
   }

  /* Wait for Tx (and CRC) data to be sent */
  if (SPI_WaitOnFlagUntilTimeout(hspi, SPI_FLAG_EOT, RESET, tickstart, Timeout) != HAL_OK)
  {
    SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
  }

  /* Call standard close procedure with error check */
  SPI_CloseTransfer(hspi);

  if (hspi->ErrorCode != HAL_SPI_ERROR_NONE)
  {
    return HAL_ERROR;
  }
  return errorcode;
}

去掉超时处理的话,翻译成LL库也就是:

__STATIC_INLINE void LCD_SPI_SendBytes(const uint8_t* data, uint16_t size)
{
    LL_SPI_SetHalfDuplexDirection(LCD_SPI_HANDEL, LL_SPI_HALF_DUPLEX_TX);
    LL_SPI_SetTransferSize(LCD_SPI_HANDEL, size);
    LL_SPI_Enable(LCD_SPI_HANDEL);
    LL_SPI_StartMasterTransfer(LCD_SPI_HANDEL);
    while (size > 0U)
    {
        if (LL_SPI_IsActiveFlag_TXP(LCD_SPI_HANDEL))
        {
              LL_SPI_TransmitData8(LCD_SPI_HANDEL, *((const uint8_t *)data));
            data += sizeof(uint8_t);
            size--;
        }
    }
    while (LL_SPI_IsActiveFlag_EOT(LCD_SPI_HANDEL) == RESET);
    LL_SPI_ClearFlag_EOT(LCD_SPI_HANDEL);
    LL_SPI_ClearFlag_TXTF(LCD_SPI_HANDEL);
    LL_SPI_Disable(LCD_SPI_HANDEL);
}

此函数无需别的什么使能,STM32CubeMX配置完成后可直接使用。

参考资料

  1. 《STM32H743参考手册中文版.pdf》
  2. 《ST7789V3_SPEC_Preliminary_V0.0_200102.pdf》
  3. NUCLEO-743ZI_Examples_LL/SPI/SPI_FullDuplex_ComIT

你可能感兴趣的:(跟我一起写STM32,stm32,单片机,mcu,信息与通信)