我目前正在练习使用SPI + DMA将数据发送到SPI显示器。 显示的数据顺序如下:
[拉低CS]-> [拉低D / C]-> [CMD的1个SPI字节]-> [拉高D / C]-> [n个SPI数据字节]-> [拉高CS] ]。 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 */
}
在我当前的尝试中,主设备开始spi CMD传输,我希望DMA传输将由HAL_SPI_Transmit_DMA()
触发。但是DMA只能启动一次,这是第一次发送。然后HAL_SPI_Transmit_DMA()
似乎由于HAL_BUSY
而返回hspi->State != HAL_SPI_STATE_READY
。
我不确定我做错了什么。谁能提供任何提示,驱动基于中断的DMA传输的正确方法是什么?
谢谢。
研究后得到一个奇怪的结果。 由于我只有逻辑分析仪作为调试工具,因此我将引脚切换设置为调试消息。我将一个放在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()
???
这怎么发生?
答案 0 :(得分:0)
我在状态机中遇到了同样的问题,我希望 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 时,这对我来说似乎有点“合理”......
我没有进一步调查,所以我不能提供一个复杂的答案,幕后发生了什么......