UART串口接收过程与配置
参考资料
《STM32Fx中文参考手册》第26章:通用同步异步收发器章节
开发板配套教程《STM32Fx开发指南》 串口实验章节
笔记基于正点原子官方视频
视频连接https://www.bilibili.com/video/BV1Wx411d7wT?p=71&spm_id_from=333.1007.top_right_bar_window_history.content.click
如有侵权,联系删除
一、串口接收过程配置流程概述
1.串口接收流程(配置步骤)
① 编程USARTx_CR1的M位来定义字长。
② 编程USARTx_CR2的STOP位来定义停止位位数。
③ 编程USARTx_BRR寄存器确定波特率。
④ 使能USARTx_CR1的UE位使能USARTx。
⑤ 如果进行多缓冲通信,配置USARTx_CR3的DMA使能(DMAT)。具体请参考后面DMA实验。
⑥ 使能USARTx_CR1的RE位为1使能接收器。
⑦ 如果要使能接收中断(接收到数据后产生中断),使能USARTx_CR1的RXNEIE位为1。
2.当串口接收到数据时
① USARTx_SR(ISR)的RXNE位置1。表明移位寄存器内容已经传输到RDR(DR)寄存器。已经接收到数据并且等待读取。
② 如果开启了接收数据中断(USARTx_CR1寄存器的RXNEIE位为1),则会产生中断。(程序上会执行中断服务函数)
③ 如果开启了其他中断(帧错误等),相应标志位会置1。
④ 读取USARTx_RDR(DR)寄存器的值,该操作会自动将RXNE位清零,等待下次接收后置位。
3.串口接收流程(HAL库)
1)配置过程
- 接收配置步骤①~⑥和发送流程一样,调用HAL_UART_Init函数
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
- 步骤⑦开启接收中断:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef*huart, uint8_t *pData, uint16_t Size);
2)接收数据过程
- 步骤①获取状态标志位通过标识符实现:
__HAL_UART_GET_FLAG //判断状态标志位
__HAL_UART_GET_IT_SOURCE //判断中断标志位
**注意:**这里USART表示同步进行,UART表示异步进行
- 步骤②~③中断服务函数:
void USARTx_IRQHandler(void) ;//(x=1~3,6)
void UARTx_IRQHandler(void) ;//(x=4,5,7,8)
在启动文件startup_stm32fxxx.s中查找。
- 步骤④读取接收数据:
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
4.串口接收中断程序配置过程(HAL库)
① 初始化串口相关参数,使能串口:HAL_UART_Init();
② 串口相关IO口配置,复用配置:在HAL_UART_MspInit中调用HAL_GPIO_Init函数。
③ 串口接收中断优先级配置和使能:
HAL_NVIC_EnableIRQ(); //使能中断通道
HAL_NVIC_SetPriority(); //设置抢占优先级和响应优先级
④ 使能串口接收中断:HAL_UART_Receive_IT();
⑤ 编写中断服务函数:USARTx_IRQHandler
-
经过上面6个步骤,我们就可以写完整的串口接收实验。我们就可以在中断服务函数中编写中断处理过程。
HAL库提供了详细的中断处理函数
HAL_UART_IRQHandler
我们在中断服务函数中会调用此函数处理中断。
-
串口接收中断流程
5.串口中断服务函数执行流程
① 串口中断服务函数中调用HAL库串口中断通用处理函数:
HAL_UART_IRQHandler();
该函数会对中断来源进行分析,调用相应函数。
② 对于不同的中断类型,我们只需要编写最终的中断处理函数:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
二、串口接收实验
实验目标:
通过电脑的串口小程序向单片机串口1发送数据,在单片机接收数据后同时在发送相同的数据给电脑。
步骤
① 初始化串口相关参数,使能串口:HAL_UART_Init();
② 串口相关IO口配置,复用配置:在HAL_UART_MspInit中调用HAL_GPIO_Init函数。
③ 串口接收中断优先级配置和使能:
HAL_NVIC_EnableIRQ(); //使能中断通道
HAL_NVIC_SetPriority(); //设置抢占优先级和响应优先级
④ 使能串口接收中断:HAL_UART_Receive_IT();
⑤ 编写中断服务函数:USARTx_IRQHandler
- 上一讲串口发送实验代码如下,我们在此基础上进行更改:
#include "sys.h"
#include "delay.h"
#include "usart.h"
UART_HandleTypeDef usart1_handler; //定义结构体变量
void uart1_init() //初始化uart1
{
usart1_handler.Instance=USART1;
usart1_handler.Init.BaudRate=115200; //定义波特率
usart1_handler.Init.WordLength=UART_WORDLENGTH_8B; //定义字长位8位
usart1_handler.Init.StopBits=UART_STOPBITS_1; //定义停止位
usart1_handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //定义硬件流
usart1_handler.Init.Mode=UART_MODE_TX_RX; //定义模式
usart1_handler.Init.Parity=UART_HWCONTROL_NONE; //定义奇偶校验位
HAL_UART_Init(&usart1_handler); //初始换刚刚定义的结构体变量
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef GPIO_Initure; //初始化GPIO结构体
if(huart->Instance ==USART1) //如果是串口1,进行串口1 MSP初始化
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟
GPIO_Initure.Pin=GPIO_PIN_9; //PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速
GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为USART1
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA9
GPIO_Initure.Pin=GPIO_PIN_10; //PA10
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA10
}
}
int main(void)
{
u8 data[]="-Apollo by wire- "; //定义一个数组,用于存放要发送的数据
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz
delay_init(180); //初始化延时函数
uart1_init(); //调用前面写的初始化函数
while(1)
{
HAL_UART_Transmit(&usart1_handler,data,sizeof(data),1000);
delay_ms(300);
}
}
1.串口接收中断优先级配置和使能
首先要进行串口接收中断优先级配置,这里要针对串口一
来进行操作
需要使用HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)函数
位置:工程文件-HALLIB-stm32f4xx_hal_cortex.c里面
函数HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)可以看到有三个入口参数:
1)IRQn_Type IRQn (使能哪个项目)
查看定义有
里面列举了所有IRQn可以使用的变量名,我们这里使用USART1_IRQn
2)uint32_t PreemptPriority (抢占优先级)和uint32_t SubPriority(响应优先级)
这里可以看到其定义的都是32为数字,按照前面所学,数字越大其级别越小,故这里我们把级别设小一点,其中抢占优先级为3;响应优先级为3
所以HAL_NVIC_SetPriority函数设置代码和位置如下
HAL_NVIC_SetPriority(USART1_IRQn,3,3)
2.使能中断通道
使能中断通道问哦们要调用HAL_NVIC_EnableIRQ函数
函数体HAL_NVIC_EnableIRQ(IRQn_Type IRQn),只有一个变量
位置:工程文件-HALLIB-stm32f4xx_hal_cortex.c里面
函数体中只有一个变量IRQn_Type IRQn,意思是使能哪个中断通道,这里我们使用USART1_IRQn
代码和位置如下:
HAL_NVIC_EnableIRQ(USART1_IRQn);
3.使能串口接收中断:HAL_UART_Receive_IT();
这里我们要使用HAL_UART_Receive_IT();函数
位置:工程文件-HALLIB-stm32f4xx_hal_uart.c里面
函数体:HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
有三个入口参数:和上一小节串口发送函数很像
UART_HandleTypeDef *huart(串口句柄)
uint8_t *pData(接收的数据)
uint16_t Size(接收的数据大小)
- 串口句柄设置为&usart1_handler(同上一小节串口发送的句柄一样)
- 接收的数据,我们在前面新定义一个数组用来存放接收来的数据:u8 rdata 所以第二个入口参数为刚刚定义的数组名rdata
- 接收的数据大小,我们选择自适应rdata的数据大小,设置为sizeof(rdata)
函数体最终设置代码和位置为
u8 rdata[1];
int main(void)
{
HAL_UART_Receive_IT(&usart1_handler,rdata,sizeof(rdata ));
}
4. 编写中断服务函数:USARTx_IRQHandler
在main.c里面添加中断服务函数:USARTx_IRQHandler,并在里面调用官方HAL库的中断服务函数HAL_UART_IRQHandler(&usart1_handler);
位置:工程文件-HALLIB-stm32f4xx_hal_uart.c里面
在HAL_UART_IRQHandler函数内部,会对中断的类型进行判断如果执行的是接收中断,它会调用UART_Receive_IT函数
查看UART_Receive_IT函数定义如下
在这个函数内部,MCU每接收到一个数据都会执行这个函数,然后把接收到的数据保存到变量pRxBuffPtr中
在接收完数据后,需要继续使能接收中断,所以加入使能接收中断函数HAL_UART_Receive_IT(&usart1_handler,rdata,sizeof(rdata ));
最终中断服务函数代码和位置如下
void USART1_IRQHandler(void) //中断服务函数
{
HAL_UART_IRQHandler(&usart1_handler); //调用官方HAL库的中断服务函数
HAL_UART_Receive_IT(&usart1_handler,rdata,sizeof(rdata )); //使能接收中断
}
5.编写接收中断完成函数:HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
在main.c里面添加接收中断完成函数:HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
我们在函数体中编写上图框起来的部分的代码
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //接收中断完成函数
{
u8 receive;
if(huart->Instance ==USART1) //如果是串口1,
{
receive=*((huart->pRxBuffPtr)-1); //把接收到的数据赋值给receive
//或使用receive=rdata[0];
}
}
6.改写发送程序
这里我们要发送接收到的数据,即rdata[1]数组中的数据也是receive的地址中的数据
在中断完成函数里面编写串口发送函数HAL_UART_Transmit(&usart1_handler,&receive,1,1000);
代码和位置如下:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //接收中断完成函数
{
u8 receive;
if(huart->Instance ==USART1) //如果是串口1,
{
receive=*((huart->pRxBuffPtr)-1); //把接收到的数据赋值给receive
//或使用receive=rdata[0];
HAL_UART_Transmit(&usart1_handler,&receive,1,1000); //使能发送中断
}
}
7.把上一小节的发送函数注释掉
如图
因为库函数里面对串口接收和发送已经定义好了,在位置:工程文件-SYSTEM-usart.c文件里面,这里我们想要使用自己编写的代码,需要把usart.c文件删除,
不删掉的话会报错说重复定义
8.main.c全部代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
u8 rdata[1]; //定义一个数组,用于存放要接收的数据
UART_HandleTypeDef usart1_handler; //定义结构体变量
void uart1_init() //初始化uart1
{
usart1_handler.Instance=USART1;
usart1_handler.Init.BaudRate=115200; //定义波特率
usart1_handler.Init.WordLength=UART_WORDLENGTH_8B; //定义字长位8位
usart1_handler.Init.StopBits=UART_STOPBITS_1; //定义停止位
usart1_handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //定义硬件流
usart1_handler.Init.Mode=UART_MODE_TX_RX; //定义模式
usart1_handler.Init.Parity=UART_HWCONTROL_NONE; //定义奇偶校验位
HAL_UART_Init(&usart1_handler); //初始化刚刚定义的结构体变量,调用官方HAL库的初始化函数
}
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef GPIO_Initure; //初始化GPIO结构体
if(huart->Instance ==USART1) //如果是串口1,进行串口1 MSP初始化
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟
GPIO_Initure.Pin=GPIO_PIN_9; //PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速
GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为USART1
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA9
GPIO_Initure.Pin=GPIO_PIN_10; //PA10
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA10
HAL_NVIC_SetPriority(USART1_IRQn,3,3); //串口1.抢占优先级3,响应优先级3
HAL_NVIC_EnableIRQ(USART1_IRQn); //使能中断通道1
}
}
void USART1_IRQHandler(void) //中断服务函数
{
HAL_UART_IRQHandler(&usart1_handler); //调用官方HAL库的中断服务函数
HAL_UART_Receive_IT(&usart1_handler,rdata,sizeof(rdata )); //使能接收中断
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //接收中断完成函数
{
u8 receive;
if(huart->Instance ==USART1) //如果是串口1,
{
receive=*((huart->pRxBuffPtr)-1); //把接收到的数据赋值给receive
//或使用receive=rdata[0];
HAL_UART_Transmit(&usart1_handler,&receive,1,1000); //使能发送中断
}
}
int main(void)
{
//u8 data[]="-Apollo by wire- "; //定义一个数组,用于存放要发送的数据
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz
delay_init(180); //初始化延时函数
uart1_init(); //调用前面写的初始化函数
HAL_UART_Receive_IT(&usart1_handler,rdata,sizeof(rdata )); //使能接收中断
while(1)
{
//HAL_UART_Transmit(&usart1_handler,data,sizeof(data),1000); //使能发送中断
//delay_ms(300);
}
}
9.下载程序到开发板
下载程序到开发板,打开串口调试助手,点击发送数据a,观察现象
在前面编写的数组rdata[1]中,只定义了一个数据位,那么接下来我们发送更多数据试试,写发送数据apollo,得到结果如下
MCU也返回了apollo,其实他是一个一个返回的,apollo六个字母,系统返回了六次