HAL库 串口空闲中断+DMA接收不定长数据 详解及踩坑

文章已更新,请移步新文章【STM32 HAL库实战】串口DMA + 空闲中断 实现不定长数据接收


前言

本文需要用到HAL库的HAL_UARTEx_ReceiveToIdle_DMA()函数,如果编辑器提示找不到函数,可以尝试更新HAL库至最新版本。

串口接收不定长数据是串口的常见应用。最近的项目需要用到modbus协议,由于不经常使用HAL库,配置串口接收时遇到了一些问题。在此记录一下,希望能帮助到一些人。

一、串口及DMA基础配置

串口的接收常见的方法有一下两种:
方法一:
传统方法。具体参考:STM32 HAL CubeMX 串口IDLE接收空闲中断+DMA_Z小旋的博客
方法二:
利用HAL库的 HAL_UARTEx_ReceiveToIdle_DMA()函数,代码简洁。
本文采用的是方法二。
打开STM32CubeMX,开始配置程序。

注意要打开串口的全局中断
在这里插入图片描述

打开串口接收DMA,模式选择Normal。

在这里插入图片描述

点击侧边栏的NVIC选项
取消选中 Force DMA channels Interrupts,否则DMA不可自定义DMA中断优先级。

我这里配置成14,防止抢占其他更重要的中断。如果没有这个需要保持默认的0即可。

在这里插入图片描述
后续只需正确配置时钟树,然后即可生成代码。

二、HAL_UARTEx_ReceiveToIdle_DMA()函数功能

函数中会把接收类型设置成HAL_UART_RECEPTION_TOIDLE,然后开启DMA接收,清除一次IDLEF标志位,重新开启IDLEF标志位
在这里插入图片描述

开启标志位后,如果串口中断来临就会执行中断处理函数void USART1_IRQHandler(void)

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
	HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE);	//重新开启串口空闲中断和DMA接收,一定要放在这里
  /* USER CODE END USART1_IRQn 1 */
}

HAL_UART_IRQHandler(&huart1)中会判断各种中断类型,并执行对应的操作

主要关注其中关于IDLE中断的部分
首先判断串口的接收类型 ReceptionType,在HAL_UARTEx_ReceiveToIdle_DMA()中已经被设为HAL_UART_RECEPTION_TOIDLE
在这里插入图片描述

清除相关中断的标志位(标志位具体功能对照参考手册查看)

在这里插入图片描述
最后调用事件回调函数HAL_UARTEx_RxEventCallback();

在这里插入图片描述

这个函数默认被定义成__weak 需要我们重新实现

__weak void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size);

三、使用HAL_UARTEx_ReceiveToIdle_DMA()函数

1. 重新实现回调函数HAL_UARTEx_RxEventCallback

这里为了方便演示在main.c中重新实现
uint8_t rx_buffer[BUF_SIZE];  // 创建接收缓存,大小为BUF_SIZE
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART1)
    {
        cnt = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        HAL_UART_Transmit(&huart1, rx_buffer, cnt, 0xffff);	//将接受到的数据再发回上位机
        memset(rx_buffer, 0, cnt);
    }
}

2.调用接收函数

在main函数中,while循环前加入函数 HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE);否则无法完成第一次接收。
测试结果如下:
单片机发回了相同的数据。
在这里插入图片描述

四、踩坑

大部分文章中,习惯把HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE); //重新开启串口空闲中断和DMA接收
放在重新实现的函数void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)中,
而不是本文中放在void USART1_IRQHandler(void)中的方式。
也就是
本文:

void USART1_IRQHandler(void)
{
    /* USER CODE BEGIN USART1_IRQn 0 */
    /* USER CODE END USART1_IRQn 0 */
    HAL_UART_IRQHandler(&huart1);
    /* USER CODE BEGIN USART1_IRQn 1 */
    HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, BUF_SIZE);	//本文中选择放置在此处
    /* USER CODE END USART1_IRQn 1 */
}

区别于:

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART1)
    {
        cnt = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        HAL_UART_Transmit(&huart1, rx_buffer, cnt, 0xffff);
        memset(rx_buffer, 0, cnt);
        HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE);	//其他大部分文章放置于此处
    }
}

放置在回调函数HAL_UARTEx_RxEventCallback中似乎也说的通:产生串口空闲中断时,会调用回调函数,并在其中重新开启串口空闲中断和DMA,等待下一次串口空闲中断来临。但是这样做会不会有bug?
实际测试一下:
首先,串口初始化时波特率配置成 9600bps

串口初始化

测试流程

第一次发送:波特率正确 9600bps 发送消息后收到正确的回复

在这里插入图片描述

第二次发送: 波特率错误 115200bps 发送消息后收不到回复

在这里插入图片描述

第三次发送 波特率改回正确的9600bps 发送后依然收不到回复

在这里插入图片描述

以上就是模拟波特率不小心设置错误的情况,居然产生了严重的问题,即使改回正确的波特率依然无法收到回复。
说明这种做法存在一定的风险。
经过大量的排查,最后确定问题就出在函数 HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE)放置的位置

波特率正确的正常情况下
USART的CR1寄存器中,IDLEIE位打开,ISR寄存器中IDLE位关闭

在这里插入图片描述

在这里插入图片描述

同时对应DMA通道寄存器的情况如下

在这里插入图片描述

目前收发正常

在这里插入图片描述

也能正常进入回调函数

在这里插入图片描述

接着改成错误的波特率,收不到回复

在这里插入图片描述

并且仿真未在回调函数中的断点处停下,说明不再进入回调函数

在这里插入图片描述

查看DMA寄存器,发现DMA没有正确打开(与之前的截图对比)

在这里插入图片描述

查看USART寄存器发现也未正确开启

在这里插入图片描述

在这里插入图片描述

原因

串口波特率错误时,不再进入回调函数。一轮接收结束时,串口空闲中断与DMA均被关闭,而两者的重启在回调函数中通过
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE);开启。这就导致波特率错误时,无法接收后续的新数据。

总结

HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE);必须放在void USART1_IRQHandler(void)中。
这样即使接收错误,也能重新开启串口空闲中断和DMA,不影响下次接收。

交流探讨

有问题可以在QQ交流群里找我,源代码也放在群文件里。

648551442

<think>首先,用户的问题是:“我想查找使用CubeMX配置串口空闲中断的方法 请问使用CubeMX配置串口空闲中断的详细步骤”。用户指定了参考站内引用,但系统指令说“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答”。 回顾用户上一次的问题:在系统指令中,用户提供了一个示例,但那是示例,不是真实对话。在用户可见层,用户直接给出了这个引用列表和当前问题。用户上一次的消息是提供引用和当前问题,所以没有历史对话。因此,我需要专注于当前问题。 系统指令的关键点: - 所有行内数学表达式必须使用 $...$。 - 独立公式使用 $$...$$。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 用户的问题是关于使用CubeMX配置串口空闲中断的详细步骤。参考引用中提到了相关内容: - 引用[1]、[3]、[4]、[5]都讨论了UART空闲中断,尤其是结合DMA。 - 引用[4]具体描述了空闲中断的概念:当一帧数据接收完成时,串口进入空闲中断。 - 引用[5]给出了配置步骤。 基于这些引用,我需要提供一个清晰的、逐步的指南。 结构回答: 1. **介绍**:简要说明什么是串口空闲中断和其用途。 2. **详细步骤**:使用CubeMX配置串口空闲中断的步骤。 3. **代码部分**:如果需要,添加一个代码块展示关键代码。 4. **相关问题**:生成相关问题。 确保使用中文回答。 详细步骤(基于引用): - 引用[5]提到步骤:选择芯片、配置RCC时钟、配置下载方式、配置串口模式为异步、配置串口参数、使能中断、使能DMA。 - 引用[3]提到在CubeMX界面新建项目。 - 引用[4]描述空闲中断的好处:处理不定长数据帧。 步骤细化: 1. 创建CubeMX工程。 2. 选择芯片型号。 3. 配置系统时钟(RCC)。 4. 配置串口: - 选择UART接口。 - 设置模式为异步(Asynchronous)。 - 配置波特率、数据位等参数。 5. 使能串口全局中断。 6. 配置空闲中断:通常在CubeMX的NVIC设置中使能UART中断。 7. 如果需要DMA,配置DMA通道。 8. 生成代码。 9. 在代码中添加中断处理函数。 引用标识:在回答中,自然地添加引用到末尾。 生成相关问题:根据主题,提出相关问题。 最终回答结构: - 解释串口空闲中断。 - 步骤列表。 - 可能的代码示例(基于引用)。 - 相关问题列表。 </think>### CubeMX配置串口空闲中断的详细步骤 串口空闲中断(UART Idle Interrupt)是一种高效处理不定长数据帧的方法,特别适合嵌入式系统中的串口通信。当UART接收线在一段时间内无数据变化(即空闲状态)时,会触发中断,表示一帧数据接收完成。这种方式避免了频繁的单个字节中断处理,简化了代码逻辑并提高了系统效率(尤其适合STM32系列单片机)[^4]。下面我将基于STM32CubeMX工具,详细说明配置步骤。整个过程以STM32F系列为例(如F103或F407),但步骤通用,适用于大多数STM32芯片。配置分为**CubeMX图形界面配置**和**代码添加**两部分。 --- ### 一、CubeMX图形界面配置步骤 以下是使用STM32CubeMX配置串口空闲中断的详细步骤。确保您已安装CubeMX软件并创建了项目工程(参考[^3])。步骤基于引用内容整合,重点关注空闲中断使能和必要的外设设置。 1. **创建新工程并选择芯片型号** - 打开CubeMX,点击“File” > “New Project”。 - 在芯片选择器中选择您的型号,例如STM32F103RCT6或STM32F407ZG(参考[^3][^5])。 - 点击“Start Project”确认。 2. **配置系统时钟(RCC)** - 在“Pinout & Configuration”标签页,左侧导航栏选择“System Core” > “RCC”。 - 启用外部晶振:在“High Speed Clock (HSE)”中选择“Crystal/Ceramic Resonator”(确保硬件连接外部晶振)。 - 配置时钟树:点击“Clock Configuration”标签页,设置系统时钟源为HSE,并调整分频器使主频达到所需值(如72MHz for F103)。这一步确保UART时钟正常工作(参考[^2][^5])。 3. **配置串口接口** - 在“Pinout & Configuration”标签页,左侧选择“Connectivity” > “USARTx”(x根据您的硬件选择,如USART1)。 - 设置模式: - Mode: 选择“Asynchronous”(异步模式)。 - Basic Parameters: 配置波特率(如115200 Bits/s)、数据位(8-bit)、停止位(1-bit)和校验位(None)。这些参数需与通信设备匹配(参考[^1][^2])。 - 使能NVIC中断: - 在USARTx配置窗口,切换到“NVIC Settings”子标签页。 - 勾选“USARTx global interrupt”以启用中断。**关键步骤:空闲中断在CubeMX中不是独立选项,需通过全局中断使能,并在代码中手动开启空闲中断标志(后续代码部分解释)**(参考[^4][^5])。 4. **配置DMA(可选但推荐)** - 空闲中断常结合DMA接收数据以提高效率(避免CPU频繁干预)。在“DMA Settings”子标签页: - 添加一个新通道:点击“Add”,选择“USARTx_RX”。 - DMA Request Settings: Mode 设置为“Circular”(循环模式),Priority 为“Medium”。 - 数据长度:设置“Data Width”为“Byte”(根据数据帧类型)。 - **注意**:DMA不是必需的,但推荐用于大数据量场景;如果仅用空闲中断,可跳过此步(参考[^1][^5])。 5. **配置下载方式和引脚分配** - System Core > SYS: 设置调试接口,如“Serial Wire”(用于ST-Link烧录)。 - 引脚自动分配:CubeMX会根据配置自动分配UART引脚(如PA9为TX,PA10为RX)。检查“Pinout View”确保无冲突。 6. **生成工程代码** - 点击“Project Manager”标签页: - 设置工程名称和位置。 - 选择IDE(如MDK-ARM for Keil)。 - 在“Code Generator”中,勾选“Generate peripheral initialization as a pair of .c/.h files”以生成模块化代码。 - 最后点击“GENERATE CODE”生成代码框架(参考[^3][^5])。 --- ### 二、代码添加步骤 CubeMX生成代码后,您需要在Keil或其他IDE中手动添加中断处理逻辑。以下是关键代码部分,基于引用内容简化(适用于HAL)。 1. **初始化串口空闲中断** - 在生成的工程中,打开 `main.c` 文件。在 `MX_USARTx_UART_Init()` 函数后添加空闲中断使能代码: ```c // 在初始化函数后添加 void MX_USARTx_UART_Init(void) { // ... CubeMX生成的代码 ... } // 手动开启空闲中断 __HAL_UART_ENABLE_IT(&huartx, UART_IT_IDLE); // 使能空闲中断,huartx 是您的UART句柄 HAL_UART_Receive_DMA(&huartx, rx_buffer, BUFFER_SIZE); // 如果需要DMA,启动DMA接收 ``` 此处: - `huartx` 是CubeMX生成的UART句柄(如 `huart1`)。 - `rx_buffer` 是自定义接收缓冲区数组(如 `uint8_t rx_buffer[100]`)。 - `BUFFER_SIZE` 是缓冲区大小(参考[^4][^5])。 2. **添加中断回调函数** - 在 `stm32xx_it.c` 文件中,找到UART中断服务函数(如 `USARTx_IRQHandler()`)。 - 修改它以处理空闲中断: ```c void USARTx_IRQHandler(void) { HAL_UART_IRQHandler(&huartx); // 调用HAL默认处理 // 检查空闲中断标志 if (__HAL_UART_GET_FLAG(&huartx, UART_FLAG_IDLE) != RESET) { __HAL_UART_CLEAR_IDLEFLAG(&huartx); // 清除空闲中断标志 // 处理接收完成的数据 HAL_UART_DMAStop(&huartx); // 停止DMA(如果使用) uint16_t data_length = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huartx.hdmarx); // 计算接收数据长度 // 调用自定义处理函数,例如 process_data(rx_buffer, data_length); } } ``` 说明: - 空闲中断触发后,需手动清除标志位。 - 使用 `__HAL_DMA_GET_COUNTER` 获取剩余字节数,计算实际接收长度(参考[^1][^4])。 - 添加自定义函数(如 `process_data`)处理接收到的数据帧。 3. **示例:完整接收流程** - 在 `main.c` 中添加接收缓冲区和处理函数: ```c uint8_t rx_buffer[100]; // 定义缓冲区 void process_data(uint8_t *data, uint16_t length) { // 处理收到的数据,如串口回显或解析 HAL_UART_Transmit(&huartx, data, length, 100); // 示例:回传数据 HAL_UART_Receive_DMA(&huartx, rx_buffer, 100); // 重新启动DMA接收 } // 在main()中初始化后启动接收 int main(void) { HAL_Init(); SystemClock_Config(); MX_USARTx_UART_Init(); HAL_UART_Receive_DMA(&huartx, rx_buffer, 100); // 初始化DMA接收 while (1) { // 主循环 } } ``` **注意事项**: - **可靠性**:空闲中断在通信可靠时高效,但如果数据帧错误(如噪声干扰),可能导致误判(参考[^4])。 - **性能提示**:对小数据帧,空闲中断即可;对大数据量,务必启用DMA以避免CPU瓶颈(参考[^3][^5])。 - **Debug**:编译下载后,使用串口调试助手测试:发送不定长数据,观察是否在空闲时触发中断。 通过以上步骤,您能高效实现STM32串口空闲中断接收。配置成功率较高,但需确保硬件连接正确(如UART引脚)[^1][^5]。 --- ### 相关问题 1. 如何处理串口空闲中断中的数据帧解析错误?[^4] 2. 如何在CubeMX中配置多串口空闲中断(例如USART1和USART2同时使用)?(参考[^2]) 3. 空闲中断DMA结合时,如何优化内存使用以防止缓冲区溢出?(参考[^1][^5]) [^1]: STM32HALCubeMx——UART DMA串口空闲中断的使用 [^2]: 串口是常用的设备与设备之间发送和接受信息的工具... [^3]: 用CubeMX生成F103芯片-串口+空闲中断串口+DMA+空闲中断... [^4]: STM32CubeMX—串口空闲中断+DMA接收... [^5]: CubeMx配置生成代码系列(一)串口+空闲中断+DMA接收数据
评论 65
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值