STM32串口UART实战-串口接收超时解析法模板

STM32串口UART实战-串口接收超时解析法模板

STM32串口UART实战-串口接收超时解析法模板

一,超时解析引入1,什么是数据解析

二,全局变量与常量三,中断回调函数HAL_UART_RxCpltCallback四,数据处理-uart_task()

上一节单片机发送了数据,本节将讲解超时解析法让单片机完成数据接收

一,超时解析引入

1,什么是数据解析

电脑的数据源源不断地通过 RX 线进入单片机,但问题是:我怎么知道一帧完整的数据什么时候结束呢? 比如,电脑发送了 “Hello” 这个字符串,单片机是一个字节一个字节接收的(‘H’, ‘e’, ‘l’, ‘l’, ‘o’)。单片机怎么知道收到 ‘o’ 之后就表示 “Hello” 发送完了,而不是后面还有其他字符呢? 这就是数据解析要解决的问题。方法有很多,比如:

固定长度: 双方约定好每次都发送固定长度的数据,比如每次 10 个字节。收满 10 个字节就算一帧。特定结束符: 双方约定好用一个特殊的字符或字符串作为结束标志,比如每次发送都以回车换行符 \r\n 结尾。收到这个标志就算一帧结束。超时解析法: 利用数据传输的间歇时间来判断。如果两个字节之间的时间间隔超过某个阈值(比如 10ms),就认为上一帧数据已经结束了。

其核心思想: 1.设置一个接收缓冲区(就是一个数组),用来存放收到的字节。 2.启动 UART 接收(通常使用中断方式,每收到一个字节就触发一次中断)。 3.在中断服务函数里: 将收到的字节存入缓冲区。 记录当前收到字节的时间(或者说重置一个计时器)。 再次启动下一次接收。 4.在主循环(或一个定时任务)里,不断检查:当前时间距离上次收到字节的时间,是否超过了预设的超时时间? 5.如果超过了超时时间,并且缓冲区里有数据,就说明一帧数据接收完毕!可以对缓冲区里的数据进行处理了。处理完后,清空缓冲区,准备接收下一帧。

也就是说,超时解析是一种处理接收到的数据的方法

二,全局变量与常量

要让串口接收数据,首先得有地方存放数据、记录状态。我们定义以下变量去存放数据,记录存放时间和存放数量

uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE];

“货架” (缓冲区): 一个 uint8_t 类型的数组,用于存放串口接收到的每一个字节(货物)。UART_RX_BUFFER_SIZE (值为 128) 定义了货架的大小,防止货物堆积如山导致溢出。

uint16_t uart_rx_index;

“计数器”: 记录当前货架上放了多少件货(收到了多少字节)。当新货到达,计数器加一;当一批货处理完毕,计数器清零。

uint32_t uart_rx_ticks;

“计时器” (时间戳): 记录最后一件货物放到货架上的准确时间。我们用它来判断货物之间的时间间隔。

extern volatile uint32_t uwTick;

“系统时钟”: 这是由 STM32 HAL 库提供的一个全局变量,像一个精准的秒表,每毫秒自动递增。我们用它来获取当前时间,与 uart_rx_ticks 比较。

#define UART_TIMEOUT_MS 100

“超时规则”: 定义了一个常量,表示我们能容忍的货物到达的最大时间间隔(100毫秒)。如果超过这个时间没新货来,我们就认为这一批货送完了。

extern UART_HandleTypeDef huart1;

“串口控制器”: 这是 HAL 库中代表具体串口硬件(如 USART1)的结构体。我们需要通过它来操作串口,比如启动接收、发送数据等。

#include

“工具库”: 引入标准库头文件,提供诸如 memset (清空货架)、vsnprintf (打包打印信息) 等常用工具。

我们将这些变量定义在函数外部,称为全局变量,这样在中断处理函数和主任务函数中都可以访问和修改它们,共享状态信息。

三,中断回调函数HAL_UART_RxCpltCallback

当串口硬件接收到一个字节的数据时,它会向 CPU 发送一个"中断"信号,CPU 暂停当前工作,转而执行一个特定的函数来处理这个事件。在 HAL 库中,这个函数就是 HAL_UART_RxCpltCallback。 那这个函数里写什么呢:目前串口已经接收到数据,但是还没有存放到缓冲区uart_rx_buffer,我们在这个函数中先判断串口号,再记录数据入库时间,并让数据地址加1,然后用HAL_UART_Receive_IT函数将数据存放到缓冲区

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

// 1. 核对身份:是 USART1 的快递员吗?

if (huart->Instance == USART1)

{

// 2. 更新收货时间:记录下当前时间

uart_rx_ticks = uwTick;

// 3. 货物入库:将收到的字节放入缓冲区(HAL库已自动完成)

// 并增加计数器

// (注意:实际入库由 HAL_UART_Receive_IT 触发,这里只更新计数)

uart_rx_index++;

// 4. 准备下次收货:再次告诉硬件,我还想收一个字节

HAL_UART_Receive_IT(&huart1, &uart_rx_buffer[uart_rx_index], 1);

}

}

中断函数流程:

核对身份 (if 判断): 确认是目标串口 USART1 发来的中断。更新时间戳 (uart_rx_ticks = uwTick;): 立刻记录下当前收到字节的时间。这是超时判断的基础。更新计数 (uart_rx_index++;): 记录已接收字节数的"计数器"加一。HAL 库在调用这个回调之前,已经默默地把接收到的那个字节放到了 uart_rx_buffer 中 uart_rx_index 指向的位置。“预订下一个包裹” (HAL_UART_Receive_IT(…)): 这是精髓所在!我们告诉 UART 硬件:“好的,这个字节我收到了,请继续监听,如果再来一个字节,请再次通知我(触发中断),并把它放到缓冲区的下一个位置 (&uart_rx_buffer[uart_rx_index])”。如果不重新启动接收,UART 就只会接收这一个字节,然后就"关门谢客"了。

还有一个关键的问题,中断函数中我们只是让接收到一个数据就让数据地址加1,但是数据的首地址是什么?

四,数据处理-uart_task()

判断数据是否送完,并进行处理的任务,交给uart_task 函数。这个函数需要在调度器中不断地被调用。

void uart_task(void)

{

// 1. 检查货架:如果计数器为0,说明没货或刚处理完,休息。

if (uart_rx_index == 0)

return;

// 2. 检查手表:当前时间 - 最后收货时间 > 规定的超时时间?

if (uwTick - uart_rx_ticks > UART_TIMEOUT_MS) // 核心判断

{

// --- 3. 超时!开始理货 ---

// "uart_rx_buffer" 里从第0个到第 "uart_rx_index - 1" 个

// 就是我们等到的一整批货(一帧数据)

my_printf(&huart1, "uart data: %s\n", uart_rx_buffer);

// (在这里加入你自己的处理逻辑,比如解析命令控制LED)

// --- 理货结束 ---

// 4. 清理现场:把处理完的货从货架上拿走,计数器归零

memset(uart_rx_buffer, 0, uart_rx_index);

uart_rx_index = 0;

huart1.pRxBuffPtr = uart_rx_buffer;

}

// 如果没超时,啥也不做,等下次再检查

}

代码逻辑:

检查是否有货 (if (uart_rx_index == 0)): 如果缓冲区是空的 (uart_rx_index 是 0),说明没有待处理的数据,直接返回,不浪费时间。判断是否超时 (if (uwTick - uart_rx_ticks > UART_TIMEOUT_MS)): 这是超时法的核心!用当前系统时间 uwTick 减去最后一次收到字节的时间 uart_rx_ticks,得到时间差。如果这个差值大于我们设定的 UART_TIMEOUT_MS(100毫秒),就意味着在100毫秒内没有新的字节到来。回显数据 (超时后的代码块): 一旦超时条件满足,我们就认为 uart_rx_buffer 中从第 0 个元素到第 uart_rx_index - 1 个元素构成了一帧完整的数据。代码中简单地用 my_printf 将数据显示出来。这是你需要根据实际应用替换成自己数据处理逻辑的地方,比如解析 JSON、执行命令等。清空状态 (memset(…) 和 uart_rx_index = 0;): 数据处理完毕后,必须清理现场!使用 memset 将缓冲区内容清零(好习惯),并将 uart_rx_index 置 0,表示货架空了,可以接收下一批新货了。关于 uwTick 回卷: uwTick 是一个 32 位无符号整数,它会一直增加,最终会溢出回零。但是, (uint32_t)currentTime - (uint32_t)lastTime 这种计算方式在 C 语言中能正确处理回卷(只要两次时间差不超过 uint32_t 最大值的一半,对于毫秒计时这几乎不可能发生),所以直接相减是安全的。重要修正:关于注释掉的 huart1.pRxBuffPtr = ...: 原始代码注释中认为在简单中断场景下此行可选,但经过实践验证,在此实现中,这行代码是必需的! 如果注释掉 huart1.pRxBuffPtr = uart_rx_buffer; 这一行,很可能会导致接收指针混乱,引发不可预期的错误。具体原因涉及到 HAL 库内部状态管理,我们将在下节课开头进行详细的 Debug 演示来深入剖析这个问题。请务必保留这行代码!

相关推荐

微博名怎么取?打造独特个人品牌的取名攻略
365服务热线

微博名怎么取?打造独特个人品牌的取名攻略

⏱️ 08-08 ⭐ 8161
QQ亲密关系 | 从工具到情感再到认同
365bet官方网投

QQ亲密关系 | 从工具到情感再到认同

⏱️ 10-01 ⭐ 5895
手机拍美食专用app排行榜TOP10推荐
365服务热线

手机拍美食专用app排行榜TOP10推荐

⏱️ 07-29 ⭐ 3677