笔者在使用SPI驱动LCD屏幕时,屏幕一直不亮。
在一系列调试中,笔者换回了LCD屏幕例程中的HAL库代码,屏幕成功点亮。于是发现LL库在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事件:
之后又了解了一下TXP事件:
这二人正好对应着一个感性的流程:也就是空了就放入数据,传输完成就结束本次传输。
去手册看看SPI的配置方法:
我们忽略掉STM32CubeMX可以给我生成好CFG寄存器的代码,以及笔者本次不使用DMA和CRC,故我们可以发现重点有——对 SPI_CR2 寄存器进行写操作以选择传输的长度,如果值未知,则必须将 TSIZE 编程为零。
也就是:
对应到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的步骤:
对于笔者想实现的功能的重点为——当 SPI 处于使能状态,CSTART 位置 1 且 TxFIFO 非空时,或者对 TxFIFO 执行下一次写操 作时,全双工模式(或任何只发送模式)下的主器件开始通信。
算是一个坑点吧,部分人认为直接LL_SPI_Enable
就能开始收发了,其实还需要置位CSTART 。
记住一个细节,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 数据发送的过程:
可以看到——发送处于激活状态时,如果应用软件有足够的空间来将至少一个完整的数据包(通过 SPI_CFG1 寄存器的 FTHVL[3:0] 位进 行定义)推入到发送 FIFO 中,则该标志由硬件置 1。
我们可以观察TXP的状态来判断发送 FIFO 中的空间是否为空。
知道了这些我们再去看看在笔者测试中已经实现功能的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,又回头查询手册发现:
SPI禁止时,是可以清空FIFO内容的。
按照笔者的个人配置情况简化HAL库的分支情况:
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配置完成后可直接使用。