串口是什么
串行接口简称串口,也称 串行通信接口或 串行通讯接口(通常指 COM接口),是采用串行通信方式的扩展接口。串行接口 (Serial Interface) 是指数据一位一位地顺序传送,其特点是 通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。
串口的意义
现如今,智能家电,智能手机等一系列智能出现,方便了人们的活动。尤其是串口和蓝牙连接,可与实现无线数据之间的信息交流。这样就可以实现一台设备控制另一台设备。方便人们的生活。
串口的实现
在实现无线数据之间的信息交流时,首先应该先实现串口功能。
资料和软件
- 软件:Stm32CubeMx ,keilMDK
- 芯片:stm32f103RE
- 资料:STM32F10x参考手册
STM32中USART介绍
STM32芯片具有多个 USART 外设用于串口通讯,它是 Universal Synchronous
Asynchronous Receiver and Transmitter的缩写,即通用同步异步收发器可以灵活地与外部设
备进行全双工数据交换。有别于 USART,它还有具有 UART 外设(Universal Asynchronous
Receiver and Transmitter),它是在 USART基础上裁剪掉了同步通信功能,只有异步通信。
简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基
本都是 UART。
USART初始化
在学习Stm32CubeMx,HAL库版本时,首先应该了解Stm32寄存器版本。简单来说,HAL库其实就是对Stm32寄存器的封装。
下方链接是对串口知识的讲解:
在寄存器版本中实现串口功能,有以及几个步骤:
- RX和TX引脚GPIO时钟和USART时钟;
- 初始化GPIO口,并将GPIO复用到USART上;
- 配置USART参数;
- 配置中断并使能USART中断;
- 使能USART;
- 在USART中断服务函数实现数据接收和发送。
在寄存器版本中,需要将这些步骤逐一的写代码,然而在Stm32CubeMx中,只需要进行图形界面配置就可以完成1~5的步骤。下面的视频就是Stm32CubeMx对串口的配置,在配置过程中时钟都是默认配置。
keilMDK中代码的介绍
打开生成的代码可以看到,在main.c文件中可以找到串口初始化的代码:
static void MX_USART1_UART_Init(void) { /* USER CODE BEGIN USART1_Init 0 */ /* USER CODE END USART1_Init 0 */ /* USER CODE BEGIN USART1_Init 1 */ /* USER CODE END USART1_Init 1 */ huart1.Instance = USART1; //USART1 huart1.Init.BaudRate = 9600; //波特率9600 huart1.Init.WordLength = UART_WORDLENGTH_8B; //字长为8位格式 huart1.Init.StopBits = UART_STOPBITS_1; //一个停止位 huart1.Init.Parity = UART_PARITY_NONE; //无奇偶校验位 huart1.Init.Mode = UART_MODE_TX_RX; //接收和发送模式 huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控 huart1.Init.OverSampling = UART_OVERSAMPLING_16; //可配置的16倍过采样或8倍过采样 if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN USART1_Init 2 */ /* USER CODE END USART1_Init 2 */ }
从void MX_USART1_UART_Init(void)函数中可以看出,使用USART1串口的异步通信,串口波特率为9600,字长为8bit,1个停止位,无奇偶校验位,无硬件流控。
在 stm32f1xx_hal_msp.c 中,生成了串口 MSP 函数 HAL_UART_MspInit,这里可以看到是对时钟,GPIO,NVIC的配置,内容如下:
void HAL_UART_MspInit(UART_HandleTypeDef* huart) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(huart->Instance==USART1) { /* USER CODE BEGIN USART1_MspInit 0 */ /* USER CODE END USART1_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟 /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_9; //选择Px.9引脚 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; //复用推挽模式 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; //高速 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); //初始化PA9 GPIO_InitStruct.Pin = GPIO_PIN_10; //选择Px.10引脚 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //输入模式 GPIO_InitStruct.Pull = GPIO_NOPULL; //无上下拉 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); //初始化PA10 /* USART1 interrupt Init */ HAL_NVIC_SetPriority(USART1_IRQn, 1, 1); //优先级1,子优先级1 HAL_NVIC_EnableIRQ(USART1_IRQn); //使能USART1中断通道 /* USER CODE BEGIN USART1_MspInit 1 */ /* USER CODE END USART1_MspInit 1 */ } }
在stm32f1xx_it.c文件中可以找到,USART1的中断函数void USART1_IRQHandler(void),内容如下:
void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); //调用 HAL 库中断处理公用函数 /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ }
在寄存器版本中,void USART1_IRQHandler(void)是对串口中断处理的。将所处理的事件写入这个函数中。然而在这里,可以看到 HAL_UART_IRQHandler(&huart1);的这个函数。在这里无法看到函数的意义,可以鼠标左击,然后按F12键进入这个函数,下面是HAL_UART_IRQHandler(&huart1)这个函数的一些省略。
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) { ...... /* 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; } } /* If some errors occur */ if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))...... ....... /* UART in mode Transmitter*/ if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET)) { UART_Transmit_IT(huart); return; } ...... }
从这里可以看到两种情况,一种是判断是否错误的占用,另一种判断中断是接收还是发送。本文使用的是接收中断,这时我们可以进入UART_Receive_IT(huart)这个函数里查看函数内容。
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) //判断字长为9位 { 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 //字长为8位 { if (huart->Init.Parity == UART_PARITY_NONE) //判断奇偶校验位(无奇偶校验位) { //接收到的数据放入缓存指针pRxBuffPtr中,每次接收一个字符 *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)//判断计数器RxXferCount是否为0,每接收一个字符,RxXferCount减1 { /* 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; //恢复huart #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) //判断是否进入UART回调函数 /*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; } }
这里可以看到,当数据接收完成,使用函数HAL_UART_RxCpltCallback(huart)。当进入这个函数时可以看到__weak,表示如果自己定义了同名的函数就不用他,如果你没定义就使用这个弱函数。
到了这里才将这个工程文件理解。接下来就来写代码实现串口的接收与发送。
实现串口接收与发送
在实现串口接收与发送,需要使用两个函数:
- HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); 串口中断模式发送
- HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);串口中断模式接收
进入函数HAL_UART_Receive_IT();代码如下:
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
huart->pRxBuffPtr = pData; //将所要接收的字符放入缓存指针pRxBuffPtr中
huart->RxXferSize = Size; //将Size赋值给RxXferSize
huart->RxXferCount = Size; //将Size赋值给RxXferCount,用来数据计数
huart->ErrorCode = HAL_UART_ERROR_NONE; //检查是否为空
huart->RxState = HAL_UART_STATE_BUSY_RX; //忙于接收
/* Process Unlocked */
__HAL_UNLOCK(huart); 打开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;
}
}
这里可以看出该函数会开启接收中断并且设置接收缓冲以及接收缓冲接收最大数据量。函数HAL_UART_Transmit_IT();与HAL_UART_Receive_IT()相似;因此不一一介绍了。
接下来将这些代码加入main.c文件中(图3-1~3)进行编译:
uint8_t aRxBuffer;
HAL_UART_Receive_IT(&huart1,&aRxBuffer,1);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
HAL_UART_Transmit_IT(&huart1,&aRxBuffer,1);//开启接收中断,缓存区,接收的字符量为1
}
实现KeilMDK串口仿真
KeilMDK的仿真类似于本博客中的51单片机发送与接收;但是要注意以下两点:
- 在debug框中将两个数据改掉,如图4-1所示:
- 在Command框下写成如图4-2所示的指令:
完成之后就完成了软件仿真(图4-3)。