本节是介绍如何使用CubeMX配置STM32的串口,并通过中断的方式实现串口通信,包含发送和接受数据。
开发环境:CubeMX+MDK5.27
芯片型号:STM32F103ZET6
时间:2020/07/11
中断方式实现串口通信是比较常见的手段,尤其是对于串口接收数据。因为单片机并不知道什么时候会收到数据,所以这种操作最适合用中断来完成,而不是放在while(1)中一直判断是否收到数据。
实现步骤
第一步,在CubeMX中配置串口,如果上一篇已经配置过,那么只需要将NVIC Settings中的USART1 global interrupt的使能选项勾选就行了。
注:这里我们没有更改抢占优先级和子优先级,默认两个都为0,即图中的Preemption Priority和Sub Priority
第二步,修改main函数代码如下
int main(void)
{
/* USER CODE BEGIN 1 */
char str[12] = "Hello World\n";
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, (uint8_t*)Rx_Buff, 12);
HAL_UART_Transmit_IT(&huart1, (uint8_t*)str, sizeof(str));
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
与上节内容一致,首先在USER CODE BEGIN下定义了发送字符串str,在USER CODE BEGIN2下我们调用HAL_UART_Receive_IT使能串口中断接收,再调用HAL_UART_Transimit_IT发送字符串str也就是Hello World。
可以看到代码非常简单,但具体是如何实现数据接收的呢,我们慢慢分析,我们追踪HAL_UART_Receive_IT函数,会跳转到stm32f1xx_hal_uart.c的1248行,函数内容如下
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;
}
/* Process Locked */
__HAL_LOCK(huart);
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
/* Process Unlocked */
__HAL_UNLOCK(huart);
/* 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;
}
else
{
return HAL_BUSY;
}
}
此函数首先对Rx接收端的状态进行判断,如果RX处于准备状态,那么会判断pData和Size是否为空,如果我们传入的Rxbuff首地址为空,且Size也为0,那么会直接返回HAL_ERROR,接下来传入的Buff会赋值给huart->pRxbuffPtr
Buff的大小Size会被复制给huart->RxXferSize和huart->RxXferCount,后面则是一些中断标志位的使能。我们无需关心。这里我们只要关心pData和Size被传入到哪里就够了,很显然是被传给了huart结构体下的成员变量pRxBuffPtr以及RxXferCount。
分析完了串口接收中断使能函数,下面我们分析一下串口中断的执行流程。
1.使能中断接收,需要我们调用HAL_UART_Receive_IT函数
2.当中断发生,系统会自动调用USART1_IRQHandler函数来处理中断,该函数在stm32f1xx_it.c里
3.系统调用USART2_IRQHandler函数后,会执行HAL_UART_IRQHandler函数来处理中断,此函数为HAL库串口中断通用处理函数,且函数就在USART2_IRQHandler函数中,入口参数为串口号&huart1。
4.调用HAL_UART_IRQHandler函数后,在HAL_UART_IRQHandler函数中又会调用UART_Receive_IT函数来处理串口接收到的数据。
5.UART_Receive_IT函数处理完串口接收到的数据后,会调用HAL_UART_RxCpltCallback函数即中断回调函数,在这个中断回调函数中我们可以编写相应的代码完成一些功能。例如将接受到的数据发送出去。
以上便是整个串口中断的处理过程,函数是一层一层的调用的。可能会有点复杂,但慢慢分析其实并不困难。
下面我们重点分析UART_Receive_IT函数到底是如何处理串口接收到的数据的。**
首先我们点开stm32f1xx_it.c找到UART1_IRQHandler函数,可以发现其内部有一个HAL_UART_IRQHandler函数,我们追踪这个函数,在stm32f1xx_hal_uart.c的第2021行便是这个函数,我们看到第2033行注释的内容是UART in moded Receiver也就是说下面的代码表示串口在接收模式下工作,往下看到2036行就是UART_Receive_IT函数,这个就是我们最需要关心的函数,串口接收数据的处理就在这个函数中,我们追踪这个函数。下面是该函数的代码
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
uint16_t *tmp;
/* Check that a Rx process is ongoing */
if (huart->RxState == HAL_UART_STATE_BUSY_RX)
{
if (huart->Init.WordLength == UART_WORDLENGTH_9B)
{
tmp = (uint16_t *) huart->pRxBuffPtr;
if (huart->Init.Parity == UART_PARITY_NONE)
{
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
huart->pRxBuffPtr += 2U;
}
else
{
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
huart->pRxBuffPtr += 1U;
}
}
else
{
if (huart->Init.Parity == UART_PARITY_NONE)
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
}
else
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
}
}
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;
#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;
}
}
在该函数中首先进行串口状态的判断,如果Rx正在接收数据,那么会继续判断串口初始化时的参数,显然我们初始化串口时并未配置他的数据位为9位,也就是说huart->Init.WordLength并不等于UART_WORDLENGTH_9B
那么会运行else下的语句,而else下又有一个if else,我们在配置串口时并没有配置奇偶校验,所以huart->Init.Parity是等于UART_PARITY_NONE的,也就是说会执行if下的语句
*huart->pRxBuffptr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
这条语句会将DR寄存器的数据不断的传给pRxBuffptr,而下面if(—huart->RxXferCount == 0)会不断判断,是否接受完RxXferCount个数据,此时上面的HAL_UART_Receive_IT函数中所分析的就和这里对应上了。当接受完数据后在上述代码的第53行会调用中断回调函数,HAL_UART_RxCpltCallback函数,这就和我们刚刚分析的流程对应上了。
在前面介绍按键的文章中,我已介绍过中断回调函数,此处再说明一下,追踪上面的HAL_UART_RxCpltCallback函数,你会看到在stm32f1xx_hal_uart.c的第2212行有这个函数的定义,但是在函数的最前面加上了__weak,这是用来表示,如果你没有编写相同名字的函数,那么系统会调用默认定义的函数,如果你自己编写了同名的函数,那么只会调用你编写的那个函数。
第三步,编写中断回调函数,直接在main.c的USER CODE BEGIN 4下编写即可**
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
//将接收到的数据发送出去
HAL_UART_Transmit_IT(huart, (uint8_t*)Rx_Buff, sizeof(Rx_Buff));
//重新使能串口接收中断
HAL_UART_Receive_IT(&huart1,(uint8_t*)Rx_Buff,sizeof(Rx_Buff));
}
}
实验结果