一、实验设计效果
- 配置串口助手波特率为115200,传输数据长度为8 Bit,无奇偶校验位,1个停止位;
- 通过串口助手向MCU发送指定长度的字符串,MCU接收到指定长度的字符串后,回发给串口助手;
二、硬件工作原理和原理图
我们使用的正点原子STM32F103RB NANO开发板上将USART1(引脚为PA9,PA10)接出
USART1并没有在PCB上连接在一起,需要通过跳线帽来连接一下。这里我们把 P5 的 RXD 和 TXD 用跳线帽与 PA9 和 PA10 连接起来。
信号传输:串口 —— 调试器 —— USB ——电脑
三、实验记录
完成 CubeMX初始化配置 → 生成初始化HAL库工程 →在keli中编写串口程序
1. 完成 CubeMX初始化配置
1.1 利用CubeMX完成HAL库工程模板和初始化:
通过选择芯片型号创建CubeMX工程
在弹出的对话框中输入开发板上的芯片型号,STM32F103RB
在右侧筛选栏中选择Tx型,即开发板上芯片所用的LQFP64型封装,双击建立工程
1.2 RCC模块引脚的配置
在弹出的工程配置对话框中的第一个引脚配置选项卡下,先完成RCC时钟模块引脚配置:
选择启用外部的高速和低速时钟源,HSE和LSE,配置为晶振连接;
配置完成后,对应时钟引脚变绿,同时旁边出现其将要配置模式的文字说明;
1.3 串口的配置
- 在侧边栏的通信外设中选择USART1
- 在串口下拉框出选择异步(Asynchronous),成功后可以看到对应的串口引脚RX(接收引脚),TX(发送引脚)变绿。
- 选择下方的Parameter Settings选项卡确认串口参数。设置波特率为115200,传输数据长度为8 Bit,无奇偶校验位,1个停止位,数据方向(Data Direction)为接收和发送(Receive and Transmit)
- 在NVIC Settings选项卡下,开启串口中断,如需调整中断优先级可在右侧修改
1.4 完成时钟配置:
我们前面启用了RCC时钟模块的外部时钟引脚,这里我们需要将外部时钟源配置为实际使用的频率;
查看手册可知:LSE为32.768KHz,HSE为8MHz;
点击上方的时钟配置选项卡,进入时钟配置界面;
前面启用了外部晶振源,这里检查修改HSE\LSE的频率,和实际一致;
在主频选择框中输入72MHz,然后按回车OK;
系统将自动将时钟源、时钟流向、PLL等配置好,以产生相应的主频;
1.5 创建生成工程
2、UART模块HAL初始化代码及API带读
2.1 串口的初始化
1)打开main.c 文件,在硬件外设初始化位置,可以看到串口的初始化函数,MX_USART1_UART_Init();
2)MX_USART1_UART_Init()解析
3)HAL_UART_Init() 解析
函数前面部分位参数检验等次要内容,主要工作是完成串口的物理层和协议层配置,分别调用HAL_UART_MspInit(huart) 和 UART_SetConfig(huart) 完成;
4)HAL_UART_MspInit() 解析
5)总结
2.2 串口中断 和 回调函数
1)同外部中断一样,使能串口中断后,相应串口中断的ISR也出现在stm32f1xx_it.c中
理论部分中提到,STM32串口有多个中断源,所以进入后,首先要判断当前进入中断的触发源是哪个
中断事件 | 事件标志 | 使能控制位 |
发送数据寄存器为空 | TXE | TXEIE |
CTS 标志 | CTS | CTSIE |
发送完成 | TC | TCIE |
准备好读取接收到的数据 | RXNE | RXNEIE |
检测到上溢错误 | ORE |
检测到空闲线路 | IDLE | IDLEIE |
奇偶校验错误 | PE | PEIE |
断路标志 | LBD | LBDIE |
多缓冲通信中的噪声标志、上溢错误和帧错误 | NF/ORE/FE | EIE |
2)HAL_UART_IRQHandler解析
在HAL_UART_IRQHandler中无非是三件事,判断是由什么中断响应的,有错误则处理,响应要调用接收或者发送处理函数
在本例中,我们发送使用轮询方式,而接收使用中断方式。UART每接收到一个字节都会触发串口ISR,并运行至2#处执行UART_Receive_IT(huart)。
3)UART_Receive_IT 解析
UART_Receive_IT 分为两部分:
一、判断UART是否忙碌,及有无完成定长数据的接收(由预设接收长度和实际接收长度确定);如还未完成,则根据是8位还是16位模式,自动将数据搬运至内存的对应位置,并更新数据接收相关状态参数;
/**
* @brief Receives an amount of data in non blocking mode
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval HAL status
*/
static 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;
}
如果接收完定长数据(--huart->RxXferCount == 0U),则除能所有串口接收中断。然后再根据空闲接收模式和一般接收模式进行处理;本例中我们使用的是一般接收模式,则会执行串口接收完成的回调函数HAL_UART_RxCpltCallback(huart);
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;
/* 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 */
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
}
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 */
}
4)HAL_UART_RxCpltCallback 串口接收完成回调函数
和前面的外部中断回调函数 HAL_GPIO_EXTI_Callback 一样,是一个弱定义的占位空函数,使用需要我们自己重写一个同名的函数,来完成串口接收完成后的响应内容;
5)接收完成中断流程图:
这里,我们再把串口接收中断的一般流程进行概括:当接收到一个字符之后,就会触发串口接收中断,在函数UART_Receive_IT中会把数据保存在串口句柄的成员变量pRxBuffPtr缓存中,同时RxXferCount 计数器减 1。如果我们设置RxXferSize=10,那么当接收到 10 个字符之后, RxXferCount 会由10 减到 0( RxXferCount 初始值等于 RxXferSize),这个时候再调用接收完成回调函数 HAL_UART_RxCpltCallback 进行处理。
2.3 串口HAL库相关API简介
在stm32f1xx_hal_uart.c文件中:
1)HAL_StatusTypeDef HAL_UART_Transmit:串口发送函数
/**
* @brief Sends an amount of data in blocking mode.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* the sent data is handled as a set of u16. In this case, Size must indicate the number
* of u16 provided through pData.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @param pData Pointer to data buffer (u8 or u16 data elements).
* @param Size Amount of data elements (u8 or u16) to be sent
* @param Timeout Timeout duration
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
//STM32F1 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了 TDR 和 RDR。
当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。
通过该函数向串口寄存器 USART_DR 写入一个数据。
以阻止模式发送大量数据。
第一个参数:huart 指向包含指定 UART 模块的配置信息的UART_HandleTypeDef结构。
第二个参数:pData 指针指向数据缓冲区(传进的是用户定义的存储发送数据的变量)
第三个参数:Size 要发送的数据元素量(u8 或 u16)
第四个参数:Timeout 超时持续时间
返回 HAL 状态
2)HAL_UART_Receive: 串口接收函数
参数列表和发送函数类似
判断是否忙-->锁住-->标记接收忙-->获取tick计数,判断是否超时
-->赋值RxXferCount有多少数据要接收-->每次从DR内获取一个Byte存在pData指向的空间
uint32_t tickstart = 0U;
if (huart->RxState == HAL_UART_STATE_READY) /* Check that a Rx process is not already ongoing */
{
__HAL_LOCK(huart); /* Process Locked */
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
tickstart = HAL_GetTick();
huart->RxXferSize = Size;
huart->RxXferCount = Size;
while (huart->RxXferCount > 0U)
{
huart->RxXferCount--;
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
return HAL_TIMEOUT;
*pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
}
}
huart->RxState = HAL_UART_STATE_READY;
__HAL_UNLOCK(huart);
3)HAL_UART_Receive_IT
只是配置了一下参数,并没有做任何处理;
配置存储在pData指向位置、空间大小RxXferSize 、接收计数RxXferCount ; 接收状态忙;确保使能接收中断
那么当有数据来的时候,依靠中断函数来处理,并更新句柄中的相应状态。
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)
{
__HAL_LOCK(huart); /* Process Locked */
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
__HAL_UNLOCK(huart); /* Process Unlocked */
/*Error Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_PE);
__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;
}
3、程序编写
3.1 定义发送和接收缓冲区
这里可以对 LENGTH 常数宏进行修改,快速配置接收缓冲区字符长度;
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define LENGTH 14
uint8_t hello[] = "USART1 is ready...\r\n";
uint8_t recv_buf[LENGTH] = {0};
/* USER CODE END 0 */
3.2 修改main函数
在main函数中首先使用 HAL_UART_Transmit 轮询方式 发送hello数组内的提示信息,然后开启串口中断接收定长字符,这里,HAL_UART_Receive_IT(&huart1, (uint8_t*)recv_buf, 13); 准备以中断方式接收13个字符,存入字符型数组recv_buf中。
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
//发送提示信息
HAL_UART_Transmit(&huart1, (uint8_t*)hello, sizeof(hello), 0xffff);
//使能串口中断接收
HAL_UART_Receive_IT(&huart1, (uint8_t*)recv_buf, LENGTH);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
3.3 重新实现接收中断回调函数
在HAL中弱定义了一个中断回调函数HAL_UART_RxCpltCallback, 当调用HAL_UART_Receive_IT启动串口中断接收,在UART接收到足够的字节后就会自动调用该函数,我们需要在用户文件中重新定义该函数,放在哪都可以,这里我放在 main.c 中:
/* USER CODE BEGIN 4 */
/* 中断回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* 判断是哪个串口触发的中断 */
if(huart ->Instance == USART1)
{
//将接收到的数据发送
HAL_UART_Transmit_IT(huart, (uint8_t*)recv_buf, LENGTH);
//重新使能串口接收中断
HAL_UART_Receive_IT(huart, (uint8_t*)recv_buf, LENGTH);
}
}
/* USER CODE END 4 */
4、烧录程序,并用串口助手观察实验现象
4.1 keli中的一些配置
后面直接编译烧录就行了
4.2 串口助手的使用:
- 通过USB连接上电脑,可在设备管理器中查询到单片机连接被分配的端口号,这里是COM4;
- 打开串口调试助手,我这里用的是XCOM V2.2;
根据前面查询到的端口号,选择串口;
根据程序里对串口控制器的配置选择串口协议设置;
XCOM 串口调试助手的其他部分功能可见下图;
4.3 串口定长收发实验的现象
- 烧录完按下复位键运行程序,串口助手接收屏上显示初始化后的发送的欢迎信息:
- 此时串口中断接收定长的14字节已经使能,通过发送框往MCU发送字符将被存入缓冲数组中;
满14个字符将触发接收完成中断,而在接收完成中断中,将会把接收到的缓冲数组中的字符重新发到串口助手上;
- 如勾选了发送新行,则每次发送会自动增加\r\n两个输出格式字符,效果如下
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)