这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 【STM32NUCLEO-C542RC】:07调试串口的空闲中断回传功能

共1条 1/1 1 跳转至页

【STM32NUCLEO-C542RC】:07调试串口的空闲中断回传功能

高工
2026-10-19 14:52:46     打赏
一、串口2(USART2)串口中断的基本内容

1.1 串口2在 NUCLEO-C542RC 板上的硬件位置

NUCLEO-C542RC 开发板基于 STM32C542RCT6 芯片,该芯片采用 Arm Cortex-M33 内核,最高主频 144 MHz,内置 256 KB Flash 和 64 KB RAM。板载 ST-LINK V3EC 调试器自带虚拟串口功能,这个虚拟串口在硬件上通过 USART2(即串口2)与单片机相连,其中 PA2 复用为 USART2_TX 发送脚,PA3 复用为 USART2_RX 接收脚。开发板用 USB 线连接电脑后,电脑端会枚举出一个虚拟串口,通常按照 115200 波特率、8 位数据位、无校验、1 位停止位(即 115200 8N1)来使用。串口2 既可以用作调试打印口输出运行信息,也可以作为与上位机交互的数据口,本实验里它承担的就是接收上位机数据并原样回传的功能。

1.2 USART2 的主要中断类型

串口2 的中断属于外设中断,由 NVIC 统一管理,使能后当相应的状态标志位置位时即可进入对应的中断处理。与本次功能相关的几个中断标志位说明如下。

RXNE(接收数据寄存器非空):每当 USART2 接收到一个字节并写入接收数据寄存器时置位,CPU 需要在该标志置位后及时把数据读走,否则下一个字节到来时可能发生溢出。这是最常用的“每字节中断”标志。

TXE(发送数据寄存器空):发送数据寄存器中的数据已被移入移位寄存器、可以写入下一个待发送字节时置位,常用于“逐字节发送”的中断方式。

TC(发送完成):整帧数据包括停止位都已经发送完毕时置位,表示这一帧数据真正从引脚上发出去了。

IDLE(线路空闲检测):RX 接收线上检测到空闲状态时置位。所谓空闲,是指在一帧数据之后,接收线保持高电平的时间超过一个字符的时间。IDLE 中断不会在每收到一个字节时触发,而是在“这一帧数据接收结束、线路进入空闲”的时刻触发一次,因此它是判断不定长数据帧接收完成的关键中断,本实验正是依靠它来获知“上位机发来的一整帧数据已经收完”。

ORE(溢出错误):接收数据寄存器中的数据还没有被读走,新的数据又到达时置位,此时新数据会丢失。除此之外,串口还有 FE 帧错误、NE 噪声错误、PE 校验错误等错误标志,调试时如果发现接收异常,需要一并检查这些错误位。

1.3 中断标志位的触发与清除方式

不同的标志位有不同的清除方式。RXNE 标志在 CPU 读取数据寄存器后自动清除,不需要软件额外操作。TC 标志在向发送数据寄存器写入新数据时自动清除,使用 HAL 库的发送接口时由库内部处理。IDLE 标志的清除顺序比较特殊,必须“先读状态寄存器,再读数据寄存器”,两步顺序不能颠倒,否则标志位无法正确清除,空闲中断会反复触发。在 HAL 库工程中,可以直接调用 __HAL_UART_CLEAR_IDLEFLAG(&huart2) 完成空闲标志的清除,由库内部保证正确的读写顺序。

1.4 为什么采用“中断 + DMA”的组合方式

如果只使用 RXNE 逐字节中断,那么每收到一个字节 CPU 就要进一次中断服务函数,在波特率较高或数据量较大时 CPU 开销非常大,而且中断响应不及时还容易丢数据。引入 DMA 之后,接收路径上的每个字节由 DMA 控制器自动从串口数据寄存器搬运到内存缓冲区,CPU 完全不用参与逐字节处理,只在 DMA 把整帧数据搬完、串口检测到线路空闲触发 IDLE 中断时被通知一次。发送路径同理,CPU 只需把待回传数据的首地址和长度告诉 DMA,由 DMA 把整帧数据搬运到发送寄存器,发送完成后通过中断通知 CPU。这样整条“接收—回传”链路对 CPU 的占用极低,正是串口 DMA 通信的典型用法。

二、串口2 以 DMA 方式接收数据并回传的实现步骤

2.1 实现思路

接收侧:将 USART2_RX 对应的 DMA 通道配置为循环模式(Circular),DMA 持续不断地把串口收到的字节写入接收缓冲区,同时使能串口的空闲中断。当上位机发来一帧数据、线路随后进入空闲时,触发空闲中断(或 HAL 库的接收至空闲事件),在中断回调中根据 DMA 搬运情况计算出本次实际接收到的字节数。

发送侧(回传/回显):在空闲事件回调中,把接收缓冲区里本次收到的数据通过 USART2_TX 对应的 DMA 通道原样发送回上位机,发送完成后再重新启动下一次 DMA 接收,从而实现“收到什么就回传什么”的串口回显功能。

2.2 第一步:STM32CubeMX2 的配置过程

新建工程:打开 STM32CubeMX2,点击新建工程,在芯片选择界面搜索并选中 STM32C542RC(如果工具支持按开发板新建,也可以直接选择 NUCLEO-C542RC 开发板,引脚会被自动预置)。

时钟配置:在 RCC 分类下根据开发板实际情况选择时钟源。NUCLEO-C542RC 板载高速外部晶振,可选择外部时钟源,也可以直接使用内部时钟。调试串口功能对时钟源不敏感,只需要在 Clock Configuration 时钟树页面把系统时钟配置到需要的频率即可,本芯片最高支持 144 MHz,按芯片支持的最高主频配置即可。

串口2 配置:在左侧 Connectivity 分类下选中 USART2。将 Mode 设置为 Asynchronous(异步模式),此时工具会自动把引脚分配到 PA2(USART2_TX)和 PA3(USART2_RX)。在 Parameter Settings 参数设置中,Baud Rate 波特率设置为 115200,Word Length 数据位选择 8 Bits,Parity 校验选择 None,Stop Bits 停止位选择 1,其余参数保持默认。切换到 NVIC Settings 选项卡,勾选使能 USART2 的 Global interrupt(全局中断),这一步必须做,否则空闲中断无法触发。

DMA 配置:在 USART2 的配置页面切换到 DMA Settings 选项卡,点击 Add 添加两个 DMA 请求。第一个请求添加 USART2_RX,Direction 方向选择 Peripheral To Memory(外设到内存),Mode 模式选择 Circular(循环模式),数据宽度、地址自增等参数保持默认,Priority 优先级建议设置为 Medium 或 High。第二个请求添加 USART2_TX,Direction 方向选择 Memory To Peripheral(内存到外设),Mode 模式选择 Normal(普通模式)。DMA 使用的具体通道由 STM32CubeMX2 根据外设请求自动分配,不需要手工指定通道号,生成代码时会自动写入对应配置。

中断确认:在 System Core 分类下的 NVIC 页面确认 USART2 全局中断以及 USART2_RX、USART2_TX 两个 DMA 通道的中断都已勾选使能,优先级保持默认即可。

生成代码:切换到 Project Manager 项目管理页面,选择工具链类型(MDK-ARM、STM32CubeIDE 等均可),填写工程名称与路径后点击 Generate Code 生成工程代码。

2.3 第二步:生成代码后的初始化

工程生成后,main 函数中会按照 CubeMX 生成的顺序依次调用各外设的初始化函数,其中 MX_DMA_Init 完成 DMA 控制器初始化,MX_USART2_UART_Init 完成串口2 的初始化,并在初始化时把两个 DMA 通道与串口句柄关联起来,这两步的顺序由生成代码保证,无需修改。

随后需要在用户代码区定义接收缓冲区,并在 main 函数初始化完成后启动 DMA 接收。推荐直接使用 HAL 库提供的“接收至空闲”接口 HAL_UARTEx_ReceiveToIdle_DMA,它会一次完成“启动 DMA 接收 + 使能空闲中断”的绑定,之后当一帧数据接收完毕线路空闲时,HAL 库会自动进入接收事件回调。示例代码如下。

/* 获取 UART2 的句柄(函数名请以生成代码为准) */
    hal_uart_handle_t *huart2 = mx_usart2_uart_gethandle();

    /* 注册接收事件回调函数(必须在启动接收之前调用) */
    HAL_UART_RegisterRxCpltCallback(huart2, UART2_RxEventCallback);

    /* 启动 DMA 空闲接收 */
    HAL_UART_ReceiveToIdle_DMA(huart2, uart2_rx_buffer, UART2_RX_BUFFER_SIZE);

2.4 第三步:在空闲事件回调中实现接收与回传

使用 HAL_UARTEx_ReceiveToIdle_DMA 启动接收后,每当线路空闲、一帧数据接收完成时,HAL 库会调用 HAL_UARTEx_RxEventCallback 回调函数,其第二个参数 Size 就是本次从上次事件到当前空闲之间实际接收到的字节数。在这个回调中完成三件事:先调用 HAL_UART_DMAStop 停止接收 DMA,防止回传期间又有新数据写入缓冲区造成数据混乱;然后调用 HAL_UART_Transmit_DMA 把接收缓冲区中本次收到的 Size 个字节原样回传给上位机;回传属于异步发送,发送完成时 HAL 库会调用 HAL_UART_TxCpltCallback 回调,在该回调中重新调用 HAL_UARTEx_ReceiveToIdle_DMA 启动下一次接收。示例代码如下。

#define UART2_RX_BUFFER_SIZE  256

/* 接收缓冲区 */
static uint8_t uart2_rx_buffer[UART2_RX_BUFFER_SIZE];
/* 回调类型: hal_uart_rx_cplt_cb_t */
void UART2_RxEventCallback(hal_uart_handle_t *huart,
                            uint32_t size_byte,
                            hal_uart_rx_event_types_t rx_event)
{
    /* 判断是否为空闲中断事件 */
    if (rx_event == HAL_UART_RX_EVENT_IDLE)
    {
        /* 将接收到的数据原路返回 */
        if (size_byte > 0)
        {
            HAL_UART_Transmit(huart, uart2_rx_buffer, size_byte, 1000);
            HAL_UART_ReceiveToIdle_DMA(huart, uart2_rx_buffer, UART2_RX_BUFFER_SIZE);
        }
    }
}

2.5 第四步:编译下载与功能验证

编译工程并下载到开发板后,在电脑端打开串口调试助手,选择 ST-LINK 虚拟串口对应的 COM 口,设置波特率 115200、数据位 8、无校验、停止位 1。任意发送一段数据,例如发送字符串 Hello,串口助手应立即收到回传的同样的内容 Hello。分别测试发送 1 个字节和发送较长的不定长数据,只要都能被正确原样回显,就说明串口2 的 DMA 接收、空闲中断判定和 DMA 回传整条链路工作正常。如果接收到的内容有缺失、错乱或者完全无响应,则按第三部分列出的注意事项逐项排查。

三、调试过程中的注意事项

空闲标志的清除顺序:IDLE 标志必须按照“先读状态寄存器、再读数据寄存器”的顺序清除,顺序颠倒会导致标志清除失败、空闲中断反复触发,回调被连续误调多次。在 HAL 工程中一律使用 __HAL_UART_CLEAR_IDLEFLAG 清除,不要手写读写序列。

接收缓冲区的大小:缓冲区必须足够大,至少大于一帧数据可能的最大长度。循环模式下 DMA 写满缓冲区后会回到缓冲区头部继续写入,如果某一帧数据长度超过了缓冲区容量,旧数据会被覆盖且不会触发空闲中断,表现为接收内容错误或丢失。设计帧长时要给缓冲区留足余量。

回传前先停止接收:在空闲事件回调中要先调用 HAL_UART_DMAStop 停止接收 DMA,再启动发送回传,避免回传期间新数据写入缓冲区造成混乱;回传完成后再重新开启接收。这个“停接收—发数据—再开接收”的顺序是回传功能稳定运行的关键,顺序颠倒容易出现数据互相覆盖。

发送忙碌状态的处理:HAL_UART_Transmit_DMA 是异步接口,上一次发送未完成时再次调用会返回 HAL_BUSY 而不会真正启动新发送。不要在空闲回调中连续多次调用发送接口,也不要在发送进行中直接重新启动接收,统一在发送完成回调 HAL_UART_TxCpltCallback 中重新启动接收,可以彻底避开忙碌冲突。




关键词: STM32 NUCLEO-C542RC     空闲中断    

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]