串口使用(HAL库)
串口的初始化通过cube MX可以直接生成,可以减轻很多工作。在使用串口之前根据引脚图选择正确的串口引脚进行复用。如下所示:

这里选择串口2,也就是PA2作为TX,PA3作为RX。并连接好引脚。单片机的TX连其他设备的RX,RX连其他设备的TX.
1、初始化串口
初始化串口本身这个设备,设置波特率,数据单元长度等。
c
void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
2、初始化引脚
以下是cube MX生成的代码,可以看到初始化了串口和引脚的时钟。初始化引脚的复用以及输入。
c
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART2)
{
/* USER CODE BEGIN USART2_MspInit 0 */
//串口的时钟初始化
/* USER CODE END USART2_MspInit 0 */
/* USART2 clock enable */
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART2 interrupt Init */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspInit 1 */
/* USER CODE END USART2_MspInit 1 */
}
}
这个函数生成后没有看到在main中调用,实际上在串口HAL_UART_Init这个函数执行的时候就已经调用了。
3、中断优先级和使能中断
从上面的最后的代码中,对串口的中断优先级进行了设置,并使能中断。
c
/* USART2 interrupt Init */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspInit 1 */
/* USER CODE END USART2_MspInit 1 */
4、 接收中断回调函数
如下所示,串口的中断函数可以通过重新改写以下的函数实现。

实际上如果使用cube MX生成代码,在stm32f1xx_it.c文件中已经定义好了。我们可以直接吧逻辑写在这个函数中,但是在HAL库中其实早已经定义了弱函数,俗称"钩子"。我们可以重新定义这个函数来实现我们的客制化。HAL_UART_IRQHandler执行的后面会调用这个钩子。

如下所示重新钩子:
c
/*
*所有串口的钩子都是一个,所以要判断是哪一个串口
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
//接收一个字节的数据
if(huart->Instance == USART2){
if(rx_len>=128 || rx_buffer[rx_len-1]=='\n'){
rx_buffer[rx_len]='\0';
HAL_UART_Transmit(huart,(uint8_t *)rx_buffer,rx_len,500);
rx_len=0;
}
HAL_UART_Receive_IT(huart,(uint8_t *)&rx_buffer[rx_len++],1);
}
}
这个HAL_UART_Receive_IT函数在主文件中至少要先调用一次,后面才能接收到中断,在触发中断之后又要调用这个函数接收下一次的数据。,如下所示:是我在main中第一次调用

5、HAL库相关函数具体实现分析
5.1 HAL_UART_Transmit函数
c
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
const uint8_t *pdata8bits;
const uint16_t *pdata16bits;
uint32_t tickstart = 0U;
/* Check that a Tx process is not already ongoing */
if (huart->gState == HAL_UART_STATE_READY) //查看外设是否已经都是初始化了的
{
if ((pData == NULL) || (Size == 0U))//传输的数据是否有效
{
return HAL_ERROR;
}
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState = HAL_UART_STATE_BUSY_TX;//设置标志位为可以传输
/* Init tickstart for timeout management */
tickstart = HAL_GetTick();//获取当前时间戳,用来计算超时
huart->TxXferSize = Size;//发送的字节
huart->TxXferCount = Size;//现在剩余的需要发送的字节
/* In case of 9bits/No Parity transfer, pData needs to be handled as a uint16_t pointer
*这种情况的最后一个比特用来区分地址或者数据,一般用于多机通讯,常用的是8比特的情况
*/
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
pdata8bits = NULL;
pdata16bits = (const uint16_t *) pData; //不同的数据长度需要使用不同的类型
}
else
{
pdata8bits = pData;
pdata16bits = NULL;
}
/*如果发送数据不是为0就开始发送数据 */
while (huart->TxXferCount > 0U)
{
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)//发送的数据寄存器为空
{
huart->gState = HAL_UART_STATE_READY;
return HAL_TIMEOUT;
}
if (pdata8bits == NULL)//9bit的方式将数据放入数据寄存器
{
huart->Instance->DR = (uint16_t)(*pdata16bits & 0x01FFU);
pdata16bits++;
}
else //通过8比特的方式放入寄存器
{
huart->Instance->DR = (uint8_t)(*pdata8bits & 0xFFU);
pdata8bits++;
}
huart->TxXferCount--;
}
/*这里硬件发现TXE设置之后没有再向数据寄存器中写入内容,于是就判定已经写完,设置TC为1表示发送完成 */
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)//发送完成寄存器标志位设置
{
huart->gState = HAL_UART_STATE_READY;
return HAL_TIMEOUT;
}
/* At end of Tx process, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
5.2 HAL_UART_Receive_IT
c
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Set Reception type to Standard reception */
huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;//标准接收方式
return (UART_Start_Receive_IT(huart, pData, Size));
}
else
{
return HAL_BUSY;
}
}
接着调用UART_Start_Receive_IT继续使能中断
c
HAL_StatusTypeDef UART_Start_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
if (huart->Init.Parity != UART_PARITY_NONE)
{
/* Enable the UART Parity Error Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_PE);
}
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
/* Enable the UART Data Register not empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); //使能寄存器写入中断
return HAL_OK;
}
当中断产生的时候,通过异常向量表对应的函数,如下所示:
c
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
HAL_UART_IRQHanler函数:
c
oid HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR); //读取状态寄存器和控制寄存器
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags = 0x00U;
uint32_t dmarequest = 0x00U;
/* If no error occurs */
errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET) //没有错误中断产生
{
/* UART in mode Receiver -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))//有接收中断并接收中断使能了
{
UART_Receive_IT(huart);//继续接收数据
return;
}
}
继续看数据的接收,接收了寄存器中的数据并调用了钩子函数
c
tatic HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
uint8_t *pdata8bits;
uint16_t *pdata16bits;
/* Check that a Rx process is ongoing */
if (huart->RxState == HAL_UART_STATE_BUSY_RX)
{
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
pdata8bits = NULL;
pdata16bits = (uint16_t *) huart->pRxBuffPtr;
*pdata16bits = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
huart->pRxBuffPtr += 2U;
}
else
{
pdata8bits = (uint8_t *) huart->pRxBuffPtr;
pdata16bits = NULL;
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) || ((huart->Init.WordLength == UART_WORDLENGTH_8B) && (huart->Init.Parity == UART_PARITY_NONE)))
{
*pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
}
else
{
*pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
}
huart->pRxBuffPtr += 1U;
}
if (--huart->RxXferCount == 0U)
{
/* Disable the UART Data Register not empty Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);
/* Disable the UART Parity Error Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_PE);
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_DISABLE_IT(huart, UART_IT_ERR);
/* Rx process is completed, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
/* Initialize type of RxEvent to Transfer Complete */
huart->RxEventType = HAL_UART_RXEVENT_TC;
/* Check current reception Mode :
If Reception till IDLE event has been selected : */
if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
{
/* Set reception type to Standard */
huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;
/* Disable IDLE interrupt */
ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
/* Check if IDLE flag is set */
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
{
/* Clear IDLE flag in ISR */
__HAL_UART_CLEAR_IDLEFLAG(huart);
}
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx Event callback*/
huart->RxEventCallback(huart, huart->RxXferSize);
#else
/*Call legacy weak Rx Event callback*/
HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
else
{
/* Standard reception API called */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx complete callback*/
huart->RxCpltCallback(huart);
#else
/*Call legacy weak Rx complete callback*/
HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
return HAL_OK;
}
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
除了以上中断方式接收数据的方式,也可以使用阻塞方式UART_receive方式或者DMA方式接收,DMA方式就是可以等数据接收指定长度或者串口空闲的时候再触发一个DMA中断去接收数据,CPU不用每一次数据都进行处理。