一、前言
1、简介
回顾上一篇UART发送当中,已经讲解了如何实现UART的发送操作了,接下来这一篇将会继续讲解如何实现UART的接收操作。
2、UART简介
嵌入式开发中,UART串口通信协议是我们常用的通信协议之一,全称叫做通用异步收发传输器(Universal Asynchronous Receiver/Transmitter)。
3、准备工作
在UART详解中已经有了详细的说明,按照里面的说明即可。
注:
建议每次编写好一个相关功能且测试功能成功使用后,保存备份并压缩成一份Demo例程,方便日后有需要的时候可以直接使用。
例如:
二、CubeMx配置及函数说明
说明:
如果有看过我写的UART发送的兄弟姐妹们应该会知道,在UART发送和UART详解中的CubeMx配置都是一样的。
但这一次不同,会在原本配置CubeMx的基础上,添加一些UART的中断配置来实现中断接收操作。
1、CubeMx配置
1)按照UART详解配置UART(若配置过,可以继续使用)
2)使能串口中断
3)设置中断优先级(如果没开启其他中断,那就默认即可,直接跳过)
4)代码生成(点击前最好把原本的工程关掉,不然有可能会有问题)
2、函数说明
1)CubeMx生成的UART初始化(在usart.c中)
说明:
会与上一篇UART发送的UART初始化有所不同,因为在这一篇我们开启了中断处理,简单了解一下即可。
1 UART_HandleTypeDef huart1; 2 3 /* USART1 init function */ 4 5 void MX_USART1_UART_Init(void) 6 { 7 8 huart1.Instance = USART1; 9 huart1.Init.BaudRate = 115200; 10 huart1.Init.WordLength = UART_WORDLENGTH_8B; 11 huart1.Init.StopBits = UART_STOPBITS_1; 12 huart1.Init.Parity = UART_PARITY_NONE; 13 huart1.Init.Mode = UART_MODE_TX_RX; 14 huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; 15 huart1.Init.OverSampling = UART_OVERSAMPLING_16; 16 if (HAL_UART_Init(&huart1) != HAL_OK) 17 { 18 Error_Handler(); 19 } 20 21 } 22 23 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) 24 { 25 26 GPIO_InitTypeDef GPIO_InitStruct = {0}; 27 if(uartHandle->Instance==USART1) 28 { 29 /* USER CODE BEGIN USART1_MspInit 0 */ 30 31 /* USER CODE END USART1_MspInit 0 */ 32 /* USART1 clock enable */ 33 __HAL_RCC_USART1_CLK_ENABLE(); 34 35 __HAL_RCC_GPIOA_CLK_ENABLE(); 36 /**USART1 GPIO Configuration 37 PA9 ------> USART1_TX 38 PA10 ------> USART1_RX 39 */ 40 GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10; 41 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 42 GPIO_InitStruct.Pull = GPIO_PULLUP; 43 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; 44 GPIO_InitStruct.Alternate = GPIO_AF7_USART1; 45 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 46 47 /* USART1 interrupt Init */ 48 HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); 49 HAL_NVIC_EnableIRQ(USART1_IRQn); 50 /* USER CODE BEGIN USART1_MspInit 1 */ 51 52 /* USER CODE END USART1_MspInit 1 */ 53 } 54 } 55 56 void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle) 57 { 58 59 if(uartHandle->Instance==USART1) 60 { 61 /* USER CODE BEGIN USART1_MspDeInit 0 */ 62 63 /* USER CODE END USART1_MspDeInit 0 */ 64 /* Peripheral clock disable */ 65 __HAL_RCC_USART1_CLK_DISABLE(); 66 67 /**USART1 GPIO Configuration 68 PA9 ------> USART1_TX 69 PA10 ------> USART1_RX 70 */ 71 HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10); 72 73 /* USART1 interrupt Deinit */ 74 HAL_NVIC_DisableIRQ(USART1_IRQn); 75 /* USER CODE BEGIN USART1_MspDeInit 1 */ 76 77 /* USER CODE END USART1_MspDeInit 1 */ 78 } 79 }UART init
2)CubeMx生成的UART中断处理函数(在stm32f4xx_it.c中)
说明:
当USART1发生中断事件时,程序会进行该函数,所以我们会在这个函数编写好程序,来处理我们的中断事件。
1 /** 2 * @brief This function handles USART1 global interrupt. 3 */ 4 void USART1_IRQHandler(void) 5 { 6 /* USER CODE BEGIN USART1_IRQn 0 */ 7 8 /* USER CODE END USART1_IRQn 0 */ 9 HAL_UART_IRQHandler(&huart1); 10 /* USER CODE BEGIN USART1_IRQn 1 */ 11 12 /* USER CODE END USART1_IRQn 1 */ 13 }
3)HAL库函数HAL_UART_Transmit(在stm32f4xx_hal_uart.c中)
说明:
该函数能够通过huart串口发送Size位pData数据。
参数说明:
huart :选择用来发送的UART串口
pData :指向将要发送的数据的指针
Size :发送数据的大小
Timeout:超时时间
1 /** 2 * @brief Sends an amount of data in blocking mode. 3 * @param huart Pointer to a UART_HandleTypeDef structure that contains 4 * the configuration information for the specified UART module. 5 * @param pData Pointer to data buffer 6 * @param Size Amount of data to be sent 7 * @param Timeout Timeout duration 8 * @retval HAL status 9 */ 10 HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) 11 { 12 uint16_t *tmp; 13 uint32_t tickstart = 0U; 14 15 /* Check that a Tx process is not already ongoing */ 16 if (huart->gState == HAL_UART_STATE_READY) 17 { 18 if ((pData == NULL) || (Size == 0U)) 19 { 20 return HAL_ERROR; 21 } 22 23 /* Process Locked */ 24 __HAL_LOCK(huart); 25 26 huart->ErrorCode = HAL_UART_ERROR_NONE; 27 huart->gState = HAL_UART_STATE_BUSY_TX; 28 29 /* Init tickstart for timeout managment */ 30 tickstart = HAL_GetTick(); 31 32 huart->TxXferSize = Size; 33 huart->TxXferCount = Size; 34 while (huart->TxXferCount > 0U) 35 { 36 huart->TxXferCount--; 37 if (huart->Init.WordLength == UART_WORDLENGTH_9B) 38 { 39 if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) 40 { 41 return HAL_TIMEOUT; 42 } 43 tmp = (uint16_t *) pData; 44 huart->Instance->DR = (*tmp & (uint16_t)0x01FF); 45 if (huart->Init.Parity == UART_PARITY_NONE) 46 { 47 pData += 2U; 48 } 49 else 50 { 51 pData += 1U; 52 } 53 } 54 else 55 { 56 if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) 57 { 58 return HAL_TIMEOUT; 59 } 60 huart->Instance->DR = (*pData++ & (uint8_t)0xFF); 61 } 62 } 63 64 if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK) 65 { 66 return HAL_TIMEOUT; 67 } 68 69 /* At end of Tx process, restore huart->gState to Ready */ 70 huart->gState = HAL_UART_STATE_READY; 71 72 /* Process Unlocked */ 73 __HAL_UNLOCK(huart); 74 75 return HAL_OK; 76 } 77 else 78 { 79 return HAL_BUSY; 80 } 81 }HAL_UART_Transmit
4)HAL库函数HAL_UART_Receive(在stm32f4xx_hal_uart.c中)
说明:
该函数能够通过huart串口接收Size位pData数据。
参数说明:
- huart :选择用来接收的UART串口
- pData :指向将要存放数据的指针
- Size :接收数据的大小
- Timeout:超时时间
1 /** 2 * @brief Receives an amount of data in blocking mode. 3 * @param huart Pointer to a UART_HandleTypeDef structure that contains 4 * the configuration information for the specified UART module. 5 * @param pData Pointer to data buffer 6 * @param Size Amount of data to be received 7 * @param Timeout Timeout duration 8 * @retval HAL status 9 */ 10 HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) 11 { 12 uint16_t *tmp; 13 uint32_t tickstart = 0U; 14 15 /* Check that a Rx process is not already ongoing */ 16 if (huart->RxState == HAL_UART_STATE_READY) 17 { 18 if ((pData == NULL) || (Size == 0U)) 19 { 20 return HAL_ERROR; 21 } 22 23 /* Process Locked */ 24 __HAL_LOCK(huart); 25 26 huart->ErrorCode = HAL_UART_ERROR_NONE; 27 huart->RxState = HAL_UART_STATE_BUSY_RX; 28 29 /* Init tickstart for timeout managment */ 30 tickstart = HAL_GetTick(); 31 32 huart->RxXferSize = Size; 33 huart->RxXferCount = Size; 34 35 /* Check the remain data to be received */ 36 while (huart->RxXferCount > 0U) 37 { 38 huart->RxXferCount--; 39 if (huart->Init.WordLength == UART_WORDLENGTH_9B) 40 { 41 if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) 42 { 43 return HAL_TIMEOUT; 44 } 45 tmp = (uint16_t *) pData; 46 if (huart->Init.Parity == UART_PARITY_NONE) 47 { 48 *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF); 49 pData += 2U; 50 } 51 else 52 { 53 *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF); 54 pData += 1U; 55 } 56 57 } 58 else 59 { 60 if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) 61 { 62 return HAL_TIMEOUT; 63 } 64 if (huart->Init.Parity == UART_PARITY_NONE) 65 { 66 *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF); 67 } 68 else 69 { 70 *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F); 71 } 72 73 } 74 } 75 76 /* At end of Rx process, restore huart->RxState to Ready */ 77 huart->RxState = HAL_UART_STATE_READY; 78 79 /* Process Unlocked */ 80 __HAL_UNLOCK(huart); 81 82 return HAL_OK; 83 } 84 else 85 { 86 return HAL_BUSY; 87 } 88 }HAL_UART_Receive
三、代码编写:实现UART接收
1、直接接收(不建议)
1)在main主函数中定义一个变量,负责接收数据
1 /* USER CODE BEGIN 1 */ 2 unsigned char uRx_Data = 0; 3 /* USER CODE END 1 */
2)在main主函数while循环中调用HAL库UART接收函数
1 /* Infinite loop */ 2 /* USER CODE BEGIN WHILE */ 3 while (1) 4 { 5 /* 判断是否接收成功 */ 6 if(HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000) == HAL_OK) 7 { 8 /* 将接收成功的数据通过串口发出来 */ 9 HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff); 10 } 11 12 /* USER CODE END WHILE */ 13 14 /* USER CODE BEGIN 3 */ 15 } 16 /* USER CODE END 3 */
整个main函数如下:
1 /** 2 * @brief The application entry point. 3 * @retval int 4 */ 5 int main(void) 6 { 7 /* USER CODE BEGIN 1 */ 8 unsigned char uRx_Data = 0; 9 /* USER CODE END 1 */ 10 11 12 /* MCU Configuration--------------------------------------------------------*/ 13 14 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 15 HAL_Init(); 16 17 /* USER CODE BEGIN Init */ 18 19 /* USER CODE END Init */ 20 21 /* Configure the system clock */ 22 SystemClock_Config(); 23 24 /* USER CODE BEGIN SysInit */ 25 26 /* USER CODE END SysInit */ 27 28 /* Initialize all configured peripherals */ 29 MX_GPIO_Init(); 30 MX_USART1_UART_Init(); 31 /* USER CODE BEGIN 2 */ 32 33 /* USER CODE END 2 */ 34 35 /* Infinite loop */ 36 /* USER CODE BEGIN WHILE */ 37 while (1) 38 { 39 /* 判断是否接收成功 */ 40 if(HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000) == HAL_OK) 41 { 42 /* 将接收成功的数据通过串口发出来 */ 43 HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff); 44 } 45 46 /* USER CODE END WHILE */ 47 48 /* USER CODE BEGIN 3 */ 49 } 50 /* USER CODE END 3 */ 51 }
3)编译、下载烧写
4)实现效果(接收到数据后,调用UART发送函数将数据发送到电脑)
说明:
这种接收方式是直接在main函数里的while循环里不断接收,会严重占用程序的进程,且接收较长的数据时,会发生接收错误,如下:
2、中断接收(接收并发送)(不推荐)
1)在HAL_UART_MspInit(在usart.c中)使能接收中断
1 /* USER CODE BEGIN USART1_MspInit 1 */ 2 __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE); 3 /* USER CODE END USART1_MspInit 1 */
整个HAL_UART_MspInit函数如下:
1 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) 2 { 3 4 GPIO_InitTypeDef GPIO_InitStruct = {0}; 5 if(uartHandle->Instance==USART1) 6 { 7 /* USER CODE BEGIN USART1_MspInit 0 */ 8 9 /* USER CODE END USART1_MspInit 0 */ 10 /* USART1 clock enable */ 11 __HAL_RCC_USART1_CLK_ENABLE(); 12 13 __HAL_RCC_GPIOA_CLK_ENABLE(); 14 /**USART1 GPIO Configuration 15 PA9 ------> USART1_TX 16 PA10 ------> USART1_RX 17 */ 18 GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10; 19 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 20 GPIO_InitStruct.Pull = GPIO_PULLUP; 21 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; 22 GPIO_InitStruct.Alternate = GPIO_AF7_USART1; 23 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 24 25 /* USART1 interrupt Init */ 26 HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); 27 HAL_NVIC_EnableIRQ(USART1_IRQn); 28 /* USER CODE BEGIN USART1_MspInit 1 */ 29 __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE); 30 /* USER CODE END USART1_MspInit 1 */ 31 } 32 }
2)在USART1_IRQHandler(在stm32f4xx_it.c中)定义一个变量,负责接收数据
1 unsigned char uRx_Data = 0;
3)在USART1_IRQHandler(在stm32f4xx_it.c中)调用HAL库的UART接收函数以及发送函数
1 /* -1- 接收 */ 2 HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000); 3 /* -2- 将接收成功的数据通过串口发出去 */ 4 HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff);
整个USART1_IRQHandler(在stm32f4xx_it.c中)函数如下:
1 /** 2 * @brief This function handles USART1 global interrupt. 3 */ 4 void USART1_IRQHandler(void) 5 { 6 /* USER CODE BEGIN USART1_IRQn 0 */ 7 unsigned char uRx_Data; 8 9 /* -1- 接收 */ 10 HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000); 11 /* -2- 将接收成功的数据通过串口发出去 */ 12 HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff); 13 14 /* USER CODE END USART1_IRQn 0 */ 15 HAL_UART_IRQHandler(&huart1); 16 /* USER CODE BEGIN USART1_IRQn 1 */ 17 18 /* USER CODE END USART1_IRQn 1 */ 19 }
4)编译、下载烧写
5)实现效果(接收到数据后,调用UART发送函数将数据发送到电脑)
说明:
相对于前面的直接接收方式,该中断接收方式就显得特别人性化了,在没有什么特别事件的时候,单片机会按照原本的程序运行着,等到有数据从UART串口发送过来时,会马上进入UART串口的中断处理函数中,完成相应的中断处理操作,完成后会退出中断函数,并继续原本在进行的程序,这样就不会占用单片机程序太多的进程了。
但仍会发生前面直接接收方式的接收异常状况,主要原因是,在中断处理函数中,我们在接收了数据后并紧接着作出发送的操作,这会出现一个状况,还没来得及将上一次接收到的数据发送出去,就进入下一次接收的中断,然而导致失去了一些数据了。
3、中断接收(先接收完,后处理)(推荐)
说明:
这种接收方式,是在方式2的基础上稍作改进的,较于前两种接收方式,是更好的一种接收方式,不会给原本的程序进程造成太大影响。还可以先接收全部数据(提示:通过定义一个较大的数组来存储),再将数据进行处理,这样能确保接收数据的完整性,并能将数据进行有效的处理、分析。
既然这种方式明显会好一点,那为什么一开始不用这个方式呢?因为通过前面两种方法,可以更容易明白UART接收的操作。
而这次就只要在方式2的基础上作出一些简单的修改就可以了。
1)在HAL_UART_MspInit(在usart.c中)使能接收中断(与方式2相同)
1 /* USER CODE BEGIN USART1_MspInit 1 */ 2 __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE); 3 /* USER CODE END USART1_MspInit 1 */
整个HAL_UART_MspInit(在usart.c中)函数如下:
1 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) 2 { 3 4 GPIO_InitTypeDef GPIO_InitStruct = {0}; 5 if(uartHandle->Instance==USART1) 6 { 7 /* USER CODE BEGIN USART1_MspInit 0 */ 8 9 /* USER CODE END USART1_MspInit 0 */ 10 /* USART1 clock enable */ 11 __HAL_RCC_USART1_CLK_ENABLE(); 12 13 __HAL_RCC_GPIOA_CLK_ENABLE(); 14 /**USART1 GPIO Configuration 15 PA9 ------> USART1_TX 16 PA10 ------> USART1_RX 17 */ 18 GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10; 19 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 20 GPIO_InitStruct.Pull = GPIO_PULLUP; 21 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; 22 GPIO_InitStruct.Alternate = GPIO_AF7_USART1; 23 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 24 25 /* USART1 interrupt Init */ 26 HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); 27 HAL_NVIC_EnableIRQ(USART1_IRQn); 28 /* USER CODE BEGIN USART1_MspInit 1 */ 29 __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE); 30 /* USER CODE END USART1_MspInit 1 */ 31 } 32 }
2)在USART1_IRQHandler(在stm32f4xx_it.c中)定义三个静态变量
1 static unsigned char uRx_Data[1024] = {0} ; //存储数组 2 static unsigned char * pRx_Data = uRx_Data; //指向存储数组将要存储数据的位 3 static unsigned char uLength = 0 ; //接收数据长度
3)在USART1_IRQHandler(在stm32f4xx_it.c中)调用HAL库的UART接收函数以及发送函数
注:
如下的第2、3步都可以根据自身要求进行改进。
- 第2步:判断接收结束条件,这个可以根据自己想要接收何种类型的数据而定。
- 第3步:数据处理,大家可以在这一步执行自己想要对数据做的一些操作,我这里只是将接收到的数据重新发送出去而已。
1 /* -1- 接收数据 */ 2 HAL_UART_Receive(&huart1, pRx_Data, 1, 1000); 3 4 /* -2- 判断数据结尾 */ 5 if(*pRx_Data == '\n') 6 { 7 /* -3- 将接收成功的数据通过串口发出去 */ 8 HAL_UART_Transmit(&huart1, uRx_Data, uLength, 0xffff); 9 10 /* -4- 初始化指针和数据长度 */ 11 pRx_Data = uRx_Data; //重新指向数组起始位置 12 uLength = 0; //长度清零 13 } 14 /* -5- 若未结束,指针往下一位移动,长度自增一 */ 15 else 16 { 17 pRx_Data++; 18 uLength++; 19 }
整个USART1_IRQHandler(在stm32f4xx_it.c中)函数如下:
1 /** 2 * @brief This function handles USART1 global interrupt. 3 */ 4 void USART1_IRQHandler(void) 5 { 6 /* USER CODE BEGIN USART1_IRQn 0 */ 7 static unsigned char uRx_Data[1024] = {0} ; //存储数组 8 static unsigned char * pRx_Data = uRx_Data; //指向存储数组将要存储数据的位 9 static unsigned char uLength = 0 ; //接收数据长度 10 11 /* -1- 接收数据 */ 12 HAL_UART_Receive(&huart1, pRx_Data, 1, 1000); 13 14 /* -2- 判断数据结尾 */ 15 if(*pRx_Data == '\n') 16 { 17 /* -3- 将接收成功的数据通过串口发出去 */ 18 HAL_UART_Transmit(&huart1, uRx_Data, uLength, 0xffff); 19 20 /* -4- 初始化指针和数据长度 */ 21 pRx_Data = uRx_Data; //重新指向数组起始位置 22 uLength = 0; //长度清零 23 } 24 /* -5- 若未结束,指针往下一位移动,长度自增一 */ 25 else 26 { 27 pRx_Data++; 28 uLength++; 29 } 30 31 32 /* USER CODE END USART1_IRQn 0 */ 33 HAL_UART_IRQHandler(&huart1); 34 /* USER CODE BEGIN USART1_IRQn 1 */ 35 36 /* USER CODE END USART1_IRQn 1 */ 37 }
4)编译、下载烧写
5)实现效果(接收到数据后,调用UART发送函数将数据发送到电脑)
四、结尾
1、总结
这一篇博客带来的是两种简单的接收方式(方式1:直接接收、方式2:中断接收1),还有一种接收方式(方式3:中断接收2),并实现了接收的操作。
但前面两种方式是不推荐的,因为在接收数据的时候,建议程序只在负责接收程序,直至接收完毕为止,数据接收完毕再进入自己处理数据的函数内。
整体来说,自我感觉还是讲解得比较清楚得,如果还有对于此篇博客不懂之处,可以在下方评论留言提问,我会尽快回复的。
2、后续
待补充……
~
~
~
~
感谢阅读~
欢迎大家关注我的博客,一起分享嵌入式知识~