[HAL库] UART使用记录
标志位
- USART_SR_TC:发送完成标志(Transmission Complete);
轮询收发
不带后缀的UART收发函数 HAL_UART_Transmit 、HAL_UART_Receive 是阻塞的,其内部调用了 UART_WaitOnFlagUntilTimeout ,来等待UART_FLAG_TXE(发送出数据)、UART_FLAG_RXNE(接收到数据)。而相对的 HAL_UART_Transmit_IT、HAL_UART_Transmit_DMA 等收发函数是非阻塞函数,因此实际使用时最好加上判断,比如在发送结束的中断回调函数 HAL_UART_TxCpltCallback 设置全局标志位。
以轮询串口接收函数为例:
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout)
该函数是阻塞接收定长数据,其中的 pData 是存储数据的地址(或者说缓存区的首地址),Size 表示需接收多少个字节的数据(定长),接收寄存器接收到的字节数目达到后才是完成接收。“接收”数据本身是由硬件自动完成(正确配置了UART之后),该函数只是将RDR寄存器的数据转存到 pData 。在程序,TDR和RDR占同一地址,即发送和接收同属一个数据寄存器(DR),硬件上则为不同的寄存器;
UART句柄(UART_HandleTypeDef)中有以下与发送数据、接收数据相关的成员:
- RxXferSize:接收数据的大小(字节数,(接收函数内部赋值Size)
- RxXferCount:(函数中赋值Size,随着接受字节而递减)
中断收发
以中断接收函数为例:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
与轮询相比较,形参没有等待时间。pData 可以是缓存区数组首地址,依据 Size 存储串口接收到的数据。需要注意,只有接收到的字节数为 Size 时才算接收完毕,满足接收完成条件而进入 HAL_UART_RxCpltCallback 回调函数程序。接收结束后需要再次调用接收函数才能开启中断接收。
HAL库中断接收函数内部逻辑:
HAL_UART_Receive_IT(huart, pData, Size);
UART_Start_Receive_IT(huart, pData, Size);
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
HAL_UART_Receive_IT 函数内调用了 UART_Start_Receive_IT ,里面使能了RXNE、ERR等中断,相当于开启了接收中断。
需要注意的是,HAL库的串口中断处理函数内存在许多条件判断,想要正确通过 HAL_UART_IRQHandler 调用 HAL_UART_RxCpltCallback ,最好是要通过 HAL_UART_Receive_IT 来启用接收中断。
DMA收发
从HAL库DMA接收相关函数节选一些来初步解释内部逻辑:
/*DMA接收函数*/
HAL_UART_Receive_DMA(huart, pData, Size);
UART_Start_Receive_DMA(huart, pData, Size); //开启DMA接收
huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt; //设置结束传输回调函数
HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size); //开启DMA中断
/*DMA接收完成回调函数*/
static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma);
UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; //通过Parent找到与DMA数据流绑定的串口句柄
HAL_UART_RxCpltCallback(huart); //调用串口接收完成回调函数(由用户自定义功能)
/*DMA中断服务函数*/
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma);
if(hdma->XferCpltCallback != NULL){
hdma->XferCpltCallback(hdma); //调用传输完成中断(除此以外还有传输半完成中断)
}
串口的函数 HAL_UART_Receive_DMA 需要通过 huart->hdmarx 使用相关的DMA句柄,DMA的函数
另外,可以看到接收函数调用了 HAL_DMA_Start_IT ,相当于中断发出后HAL库内部进行DMA转运。因此使用DMA传输需要配置DMA的NVIC,(最好)调用 HAL_DMA_IRQHandler 以正确处理HAL库中断相关内容。
需要注意,调用传输函数 HAL_UART_Transmit_DMA 时很可能会出现 huart->gState != HAL_UART_STATE_READY 的情况,使得函数无法运行,具体原因不明确,可尝试直接调用之前手动置 HAL_UART_STATE_READY 。
IDLE中断
利用空闲中断,可实现不定长数据的接收。实现需要:
- 配置、开启UART中断,调用串口中断服务函数;
- 使用 HAL_UARTEx_ReceiveToIdle_DMA 来接收数据,其中形参 Size 表示可接收的最大字长,而非定长;
- 接收结束回调函数为 HAL_UARTEx_RxEventCallback ;