STM32串口接收不定长数据(接收中断+超时判断)

发布者:RadiantDreams最新更新时间:2024-12-17 来源: jianshu关键字:STM32  串口接收  不定长数据  接收中断  超时判断 手机看文章 扫描二维码
随时随地手机看文章

玩转 STM32 单片机,肯定离不开串口。串口使用一个称为串行通信协议的协议来管理数据传输,该协议在数据传输期间控制数据流,包括数据位数、波特率、校验位和停止位等。由于串口简单易用,在各种产品交互中都有广泛应用。

但在使用串口通讯的时候,我们并不知道对方会发送多少个数据,也不知道数据什么时候发送完,简单来讲就是:如何确保收到一帧完整的数据?

串口发送的数据有长有短,如果没有接收完整,肯定会影响后续业务的处理。为了接收不定长数据,常见的处理方法有:

1. 固定格式

比如双方约定,一帧的数据以 AA BB 开头,以 BB AA 结尾,这样在从机接收数据的时候,一旦收到 AA BB 字符,就知道对方要发来一个数据包了,然后就把后面发来的数据保存起来,直到接收到 BB AA 为止。

这种方法简单高效,但缺点就是需要每个字符都进行判断,浪费 CPU 资源,增加功耗。

2. 接收中断+超时判断

串口接收到一个数据时,就会触发接收中断。但如何判断数据已经发送完了呢?

通常来讲,两帧数据之间,会有个时间间隔。因此,我们可以使用一个计时器,如果在一个固定的时间点里没接收到新的字符,则认为一帧数据接收完成了。

3. 空闲中断

串口在空闲时,也就是说串口在一段时间里没有接收到新数据,则会触发空闲中断。细心的同学应该发现了,空闲中断实际上跟上面的超时判断是一样样的,只不过空闲中断是硬件自带,但超时判断需要我们自己实现。

所以,一旦接收到空闲中断,可以认为接收到一帧完整的数据。

但是,空闲中断并不是所有的 MCU 都具备,一般高端一点的 MCU 才有,低端一些的 MCU 并没有空闲中断。

1. 源码下载及前置阅读

本文首发 良许嵌入式网 :https://www.lxlinux.net/e/ ,欢迎关注!

本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):

https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-rxne-time-out.html

如果你是个零基础的小白,连 STM32 都没见过,我也给你准备了一个保姆级教程,手把手教你搭建好 STM32 开发环境,并教你如何下载程序,简直业界良心!

https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginner.html

如果你连代码都不知道怎么烧录到 STM32 的,可以参考下文,提供了 5 种代码烧录方式:

https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html

如果你想自己搭一个属于自己的工程模板,可以参考下面这篇文章:

https://www.lxlinux.net/e/stm32/create-stm32-hal-project-template.html

在本文中,我们详细来介绍如何使用接收中断+超时判断完成不定长数据的接收,对于空闲中断的接收,请查看下文:

https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-idle-dma.html

2. 什么是接收中断?

前文已经提到,当接收到一字节数据时,会触发接收中断,对应串口状态寄存器第 5 位被置 1 ,如下图示。

当我们将 DR 寄存器的值读取之后,该位又被自动清零。

3. 硬件准备

  • STM32 核心板

本文使用正点原子 M48Z 核心板,小巧好用,某宝 20 元出头。

  • USB 转 TTL

这种设备主要作用是用来调试或下载程序。价格也很便宜,普遍 5~8 元。

  • ST-Link

ST-Link 是一种用于 STM32 微控制器的调试和编程工具,它可以通过 SWD 或 JTAG 接口与开发板进行通信。一般也很便宜,七八元左右。

4. 编程实战

在本实验中,我们将串口 1 作为 log 输出端口,串口 2 作为本次实验的接收端口。

因此我们需要提前创建 uart2 模块,包含 uart2.c 及 uart2.h 两个文件,并加载进工程模板。

4.1 串口初始化

串口的初始化大家应该不陌生,主要步骤为:

  1. 定义串口句柄 uart2_handle ,并调用 HAL_UART_Init 进行初始化;

  2. 初始化串口底层函数,调用 HAL_UART_MspInit 函数。

第一步在 uart2.c 文件里进行:

UART_HandleTypeDef uart2_handle;


void uart2_init(uint32_t baudrate)

{

    uart2_handle.Instance          = UART2_INTERFACE;              /* UART2 */

    uart2_handle.Init.BaudRate     = baudrate;                     /* 波特率 */

    uart2_handle.Init.WordLength   = UART_WORDLENGTH_8B;           /* 数据位 */

    uart2_handle.Init.StopBits     = UART_STOPBITS_1;              /* 停止位 */

    uart2_handle.Init.Parity       = UART_PARITY_NONE;             /* 校验位 */

    uart2_handle.Init.Mode         = UART_MODE_TX_RX;              /* 收发模式 */

    uart2_handle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;          /* 无硬件流控 */

    uart2_handle.Init.OverSampling = UART_OVERSAMPLING_16;         /* 过采样 */

    HAL_UART_Init(&uart2_handle);                                  /* 使能UART2 */

}

第二步在 usart.c 文件里进行,其实也可以在 uart2.c 文件里做,但我懒~

在最下面一行代码,我们使用 __HAL_UART_ENABLE_IT() 使能接收中断。

void HAL_UART_MspInit(UART_HandleTypeDef *huart)

{

    GPIO_InitTypeDef gpio_init_struct;


    if (huart->Instance == USART_UX)                            /* 如果是串口1,进行串口1 MSP初始化 */

    {

        ....

        // 节略串口1相关代码

        ....

    }

    else if (huart->Instance == UART2_INTERFACE)                /* 如果是UART2 */

    {

        UART2_TX_GPIO_CLK_ENABLE();                             /* 使能UART2 TX引脚时钟 */

        UART2_RX_GPIO_CLK_ENABLE();                             /* 使能UART2 RX引脚时钟 */

        UART2_CLK_ENABLE();                                     /* 使能UART2时钟 */


        gpio_init_struct.Pin    = UART2_TX_GPIO_PIN;            /* UART2 TX引脚 */

        gpio_init_struct.Mode   = GPIO_MODE_AF_PP;              /* 复用推挽输出 */

        gpio_init_struct.Pull   = GPIO_NOPULL;                  /* 无上下拉 */

        gpio_init_struct.Speed  = GPIO_SPEED_FREQ_HIGH;         /* 高速 */

        HAL_GPIO_Init(UART2_TX_GPIO_PORT, &gpio_init_struct);   /* 初始化UART2 TX引脚 */


        gpio_init_struct.Pin    = UART2_RX_GPIO_PIN;            /* UART2 RX引脚 */

        gpio_init_struct.Mode   = GPIO_MODE_INPUT;              /* 输入 */

        gpio_init_struct.Pull   = GPIO_NOPULL;                  /* 无上下拉 */

        gpio_init_struct.Speed  = GPIO_SPEED_FREQ_HIGH;         /* 高速 */

        HAL_GPIO_Init(UART2_RX_GPIO_PORT, &gpio_init_struct);   /* 初始化UART2 RX引脚 */


        HAL_NVIC_SetPriority(UART2_IRQn, 0, 0);                 /* 抢占优先级0,子优先级0 */

        HAL_NVIC_EnableIRQ(UART2_IRQn);                         /* 使能UART2中断通道 */


        __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);              /* 使能UART2接收中断 */

    }

}


4.2 判断接收中断

在串口 2 接收中断里,我们先使用 __HAL_UART_GET_FLAG() 函数判断 RXNE 这一位有没有被置 1 ,如果被置 1 ,则代表接收到字符,调用 HAL_UART_Receive() 函数接收字符,并保存于临时变量 receive_data 中。

之后,再调用 HAL_UART_Transmit() 函数将接收到的字符打印出来。

void UART2_IRQHandler(void)

{

  uint8_t receive_data = 0;   

  if(__HAL_UART_GET_FLAG(&uart2_handle,UART_FLAG_RXNE) != RESET)

  {

    HAL_UART_Receive(&uart2_handle, &receive_data, 1, 1000);        //串口2接收1位数据

    HAL_UART_Transmit(&uart2_handle, &receive_data, 1, 1000);       //将接收的数据打印出来

  }

}


现在我们通过接收中断就可以实现了自发自收,编译后烧进板子,效果如下:

但现在我们只实现了字符的接收,并不知道一帧的数据什么时候接收完。

在下面的操作里,我们就通过超时的方法,进一步判断数据是否完成传输。

4.3 数据接收完成判断

如何判断一帧的数据接收完成了?

在本文中,我们使用超时的方法进行判断,这种方法虽然会耗费 CPU 资源,但因为比较简单,所以使用也很广泛。在下一篇文章里,我们将使用空闲中断+DMA 的方法,更高效进行帧数据接收完成判断。

超时判断的思路如下:

  1. 将接收到的字符保存在接收缓冲区里,并定义一个变量 uart2_cnt 计算总共收到了多少个字符;

  2. 假如一帧的数据接收完成了,那么 uart2_cnt 变量的值应该维持不变。

第一个步骤比较好实现,还是在串口 2 接收中断里,做一些小小的改动:

uint16_t uart2_cnt = 0, uart2_cntPre = 0;


void UART2_IRQHandler(void)

{

    uint8_t receive_data = 0;   

    if(__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_RXNE) != RESET){    //获取接收RXNE标志位是否被置位

        if(uart2_cnt >= sizeof(uart2_rx_buf))                           //如果接收的字符数大于接收缓冲区大小,

            uart2_cnt = 0;                                              //则将接收计数器清零

        HAL_UART_Receive(&uart2_handle, &receive_data, 1, 1000);        //接收一个字符

        uart2_rx_buf[uart2_cnt++] = receive_data;                       //将接收到的字符保存在接收缓冲区

    }

}


关键是第二步,我们如何判断 uart2_cnt 什么时候维持不变(也就是一帧的数据接收完成了)?也很简单,我们就定时去查看一下这个变量的值,看看是否跟上一次一样,如果一样的话就说明数据接收完成了。

因此我们需要再借助一个新的变量 uart2_cntPre ,记录上一次接收到的数据的长度(上面的代码已经定义好了)。

uint8_t uart2_wait_receive(void)

{

    if(uart2_cnt == 0)                                      //如果接收计数为0,则说明没有处于接收数据中,所以直接跳出,结束函数

        return UART_ERROR;


    if(uart2_cnt == uart2_cntPre) {                         //如果上一次的值和这次相同,则说明接收完毕

        uart2_cnt = 0;                                      //清0接收计数

        return UART_EOK;                                    //返回接收完成标志

    }


    uart2_cntPre = uart2_cnt;                               //置为相同

    return UART_ERROR;                                      //返回接收未完成标志

}


然后我们在 main 函数里的 while 死循环定期(例如10ms)调用 uart2_wait_receive 函数,如果返回值为 UART_EOK 则代表帧数据接收完成,我们就可以将数据打印出来。

while(1)

{

    if(uart2_wait_receive() == UART_EOK) {      //判断串口2是否数据接收完成

        printf('recv: %srn', uart2_rx_buf);   //打印收到的数据

        uart2_rx_clear();                       //清空接收缓冲区

    }


    delay_ms(10);                               //每隔10毫秒判断一次

}


当然,接收到的数据使用完成之后,我们就应该清空接收缓冲区,并将计数器置 0 ,方便下一次接收,所以我们调用了 uart2_rx_clear() 函数,其代码实现为:

[1] [2]
关键字:STM32  串口接收  不定长数据  接收中断  超时判断 引用地址:STM32串口接收不定长数据(接收中断+超时判断)

上一篇:《嵌入式-STM32开发指南》第二部分 基础篇 - 第3章 按键(HAL库)
下一篇:《嵌入式-STM32开发指南》第二部分 基础篇 - 第7章DMA(HAL库)

推荐阅读最新更新时间:2026-03-25 12:27

STM32 一直进入串口接收中断
解决方法一: 串口初始化配置时,需要打开ORE 溢出中断,否则串口中断没有及时读取数据会触发溢出中断(打开接收中断默认开启溢出中断,但是为了读取溢出标志位还需要明确执行以下打开溢出中断),如果没有清溢出中断就会一直进串口中断。 USART_ITConfig(USART2, USART_IT_ORE, ENABLE);//USART_IT_ORE参数在这个函数中是不合法,参数检测过不去,关闭参数检测这样写确实有效 串口接收中断函数要增加如下代码: if (USART_GetITStatus(USART2, USART_IT_ORE) == SET) {   USART_ClearITPendingBit(USART2,USART
[单片机]
STM32 串口接收流程-串口接收中断
串口接收 串口接收流程 编程USARTx_CR1的M位来定义字长。 编程USARTx_CR2的STOP位来定义停止位位数。 编程USARTx_BRR寄存器确定波特率。 使能USARTx_CR1的UE位使能USARTx。 如果进行多缓冲通信,配置USARTx_CR3的DMA使能(DMAT)。 使能USARTx_CR1的RE位为1使能接收器。 如果要使能接收中断(接收到数据后产生中断),使能USARTx_CR1的RXNEIE位为1。 当串口接收到数据时 USARTx_SR(ISR)的RXNE位置1。表明移位寄存器内容已经传输到RDR(DR)寄存器。已经接收到数据并且等待读取。 如果开启了接收数据中断(USART
[单片机]
STM32串口USART1中断接收中断发送
  先贴出中断函数:    view plain copy   void USART1_IRQHandler(void){   IF (USART_GetiTStatus(USART1, USART_IT_RXNE) != RESET) {   USART_ClearITPendingBit(USART1, USART_IT_RXNE);   USART1_Buffer =USART_ReceiveData(USART1); //USART1_Buffesh是一个自己定义的接收数组   if(i 3){   SendFlag = 1;   }   }   if(USART_GetITStatus(USART1, USART_I
[单片机]
STM32单片机串口空闲中断接收定长数据
在使用单片机的串口通信功能时,常用的接收数据方法是通过固定的字节数来判断一帧数是否发送完成,或者是通过固定的结束标志位来表示一帧数据发送完成。但是有时候会遇到发送的数据长度不固定,也没有固定的结束标志,对于这样的数据通常的做法是每隔一段时间查看一下接收数据的长度是否发生了变化,如果指定的一段时间内接收数据长度没有发生变化,就认为是一帧数据发送完成。在STM32单片机中串口提供了一个更好用的功能,就是空闲中断功能。也就是说当一帧数据发送结束后,就会产生一个空闲中断。这样就可以利用这个空闲中断来判断一帧数据接收是否完成。 关于串口空闲检测可以在STM32参考手册上找到相关介绍 通过这个图可以看出来,当第一组数据Data1、Da
[单片机]
<font color='red'>STM32</font>单片机<font color='red'>串口</font>空闲<font color='red'>中断</font><font color='red'>接收</font>不<font color='red'>定长</font><font color='red'>数据</font>
STM32单片机串口空闲中断+DMA接收定长数据
在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲中断接收不定长数据,这种方式有一个问题就是串口每接收到一个字节就会进入一次中断,如果发送的数据比较频繁,那么串口中断就会不停打断主程序运行,影响系统运行。那么能不能在串口接收数据过程中不要每接收一个数据中断一次,只有在一帧数据接收结束完成后只中断一次? 用串口的空闲中断加上DMA功能,就可以实现每帧数据接收完成后只中断一次,而在数据接收过程中,由DMA存储串口接收到的每个字节。 关于串口的空闲检测和DMA在STM32参考手册中有详细介绍。 下面看如何初始化串口空闲中断和 DMA。 void uart2_init( u16 baud )
[单片机]
<font color='red'>STM32</font>单片机<font color='red'>串口</font>空闲<font color='red'>中断</font>+DMA<font color='red'>接收</font>不<font color='red'>定长</font><font color='red'>数据</font>
STM32—无需中断来实现使用DMA接收串口数据
本节目标: 通过DMA,无需中断,接收不定时长的串口数据 描述: 当在串口多数据传输下,CPU会产生多次中断来接收串口数据,这样会大大地降低CPU效率,同时又需要CPU去做其它更重要的事情,我们应该如何来优化? 比如四轴飞行器,当在不停地获取姿态控制方向时,又要去接收串口数据. 答:使用DMA,无需CPU中断便能实现接收串口数据 1.DMA介绍 DMA,全称为: Direct Memory Access,即直接存储器访问, DMA 传输方式无需 CPU 直接控制传输,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。 2在main()中调用串口配置函数,初始化串口后,然后使能UA
[单片机]
<font color='red'>STM32</font>—无需<font color='red'>中断</font>来实现使用DMA<font color='red'>接收</font><font color='red'>串口</font><font color='red'>数据</font>
STM32使用CubeMAX配置的串口中断接收方法
STM32使用cubeMAX可以快速建立工程模板,但是默认使用的是Hal库构成的工程,对于习惯使用了ST标准库的同学来说,灵活调用HAL库可能会比较生疏,我也是这么觉得的,但是还是要逐步去接触学习它,毕竟这个hal库的封装还是相当好的,有好多先进的思想和用法。 在学习过程中,我遇到了一个问题,之前也遇到过,但是没时间去研究,就是串口在CUBUMAX上配置好后,如何实现串口中断接收,接下来就来记录一下我学习到的知识: 1.定位串口中断发生的地方 HAL库的中断处理还是和标准库一样的,在stm32xxxx_it.c中定义我们定位到如下函数: HAL_UART_IRQHandler(&huart1); 再往下定位,我们找
[单片机]
stm32 实现串口中断接收浮点型、整型数据
之前已经实现了在stm32中移植printf函数和scanf函数,相信很多网友也已经熟练掌握这个技能了。最近在项目中遇到了问题,需要在串口助手中向下位机stm32写整型或者浮点型数据。这个时候只能使用串口中断接收函数。 void USART1_IRQHandler(void) { uint8_t ch; while(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { ch=USART_ReceiveData(USART1); } } 但是很快发现程序这样写过于简陋,只能接收单个字符,不符合我的要求,然后参考网上例
[单片机]
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved