个人感觉,做嵌入式,底层就是datasheet,顶层就是数理逻辑。
不管什么芯片,当我们遇到问题时,通过查阅datasheet或上官网基本上都能找到解决方法。然而,这些基本都是英文。所以,英文好对做研发是有很大益处的。不过好在有翻译工具,如:有道(我就是用有道划的):
C语言虽然没有class,但有struct。我们可以多用struct。ST库和ucos中就包含许多struct。
程序结构多用状态机或switch。个人感觉,状态机看起来很有条理,很清晰,后期修改维护也方便。
鄙人浅见,请大家不吝赐教。
进入正题:UART以DMA方式接收和发送的函数调用顺序:
循环模式接收:
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
==》DMA1_Channelx_IRQHandler(void)
==》HAL_DMA_IRQHandler(&hdma_usartx_rx)
==》UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
==》HAL_UART_RxCpltCallback(huart)
当然这当中还会调用传输 Half 中断,这里就不讨论了。
正常模式发送:
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
==》DMA1_Channelx_IRQHandler(void)
==》HAL_DMA_IRQHandler(&hdma_usartx_tx)
==》UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)
==》USART3_IRQHandler(void)
==》HAL_UART_IRQHandler(&huart3)
==》UART_EndTransmit_IT(huart)
==》HAL_UART_RxCpltCallback(huart)
这里只分析 HAL_UART_Receive_DMA 的源码,其它的大家自己分析:
/*UART以DMA方式接收指定长度数据到指定缓冲区*/
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
uint32_t *tmp;
/* Check that a Rx process is not already ongoing */
/* 检查huart->RxState是否处于接收空闲 状态。
当这一状态标志非READY时,会跳过DMA接收参数设置,直接返回HAL_BUSY。
其它的UART接收函数也会检查这个状态,所以,哪个先调用就执行哪个。*/
if(huart->RxState == HAL_UART_STATE_READY)
{
if((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(huart);
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
/* Set the UART DMA transfer complete callback */
/*设置DMA传输完成的回调函数。当DMA以循环方式传输时会调用UART接收完成中断的回调函数;
以Normal方式传输时会关闭UART的DMA通道,并使能UART传输完成中断,触发UART传输完成中断,
设置huart->RxState为READY,并调用UART接收完成中断的回调函数。
所以,不管DMA按循环或正常模式传输,到最后都会调用UART接收完成中断的回调函数*/
huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;
/* Set the UART DMA Half transfer complete callback */
huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;
/* Set the DMA error callback */
huart->hdmarx->XferErrorCallback = UART_DMAError;
/* Set the DMA abort callback */
huart->hdmarx->XferAbortCallback = NULL;
/* Enable the DMA channel */
/*以中断方式打开DMA传输。所以CubeMx中DMA中断默认是打开的切不可更改*/
tmp = (uint32_t*)&pData;
HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t*)tmp, Size);
/* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
__HAL_UART_CLEAR_OREFLAG(huart);
/* Process Unlocked */
__HAL_UNLOCK(huart);
/* Enable the UART Parity Error Interrupt */
SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
SET_BIT(huart->Instance->CR3, USART_CR3_EIE);
/* Enable the DMA transfer for the receiver request by setting the DMAR bit
in the UART CR3 register */
SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
下面是我做的测试:一、Cube配置
1、DMA接收要用 循环模式,只需调用一次接收函数即可,重复调用也只有第一次调用有效。若是 正常模式,需要在每次接收完成后重复调用。
2、DMA发送要用 正常模式,需在每次发送时重复调用。还要打开UART中断,否则即使重复调用DMA发送函数也只有第一次发送有效,其余不会执行(UART发送状态一直处于 BUSY)。若是 循环模式,则会连续不断地发送,不会停止。
以上两点大家可以自己改改测试。
二、Keil例程编写
三、测试
可以看到,发送数 = 接收数。大家可以做更大数据的短间隔长时间测试,看看会不会出错。