GD32F4XX串口接收中断和闲时中断配置
Contents
起因
最近在调试GD32F4xx,想使用串口的闲时中断,发现与STM32有些区别:清标志的方法不一样,踩了“闲时中断反复触发/不触发”的坑。在此做个记录,备忘,防止重复踩坑。
需求
- 串口收到数据逐字节进缓冲(RBNE 中断)
- 一帧不定长数据接收结束时通知上层(IDLE 闲时中断断帧)
技术实现原理
闲时中断(IDLE)检测的是什么
串口的 IDLE 检测监视的是 RX 线的电平:收到一帧数据后,线路连续保持高电平超过一个字节时间(起始位+数据+校验+停止位的完整帧长),硬件就置 IDLE 标志。注意它检测的是“从收到数据到空闲”这个边沿,不是“空闲状态”本身——持续空闲不会再触发,必须先再来一帧数据。这个“一帧结束”信号正好用来给不定长报文断帧:协议没有长度字段时,靠它知道“这包收完了”。
根因:IDLE 标志的清除靠“读状态→读数据”的软件序列
本文最核心的坑在这:IDLE 标志不是写 1 清零的,它的清除约定是先读状态寄存器、再读数据寄存器——这套序列从老式 UART 一路继承下来。GD32 标准库里“读数据寄存器”被封装成 usart_data_receive(),于是:
- 只调
usart_interrupt_flag_clear()清标志,清除序列没走完,标志实际没清掉——表现就是闲时中断被触发多次或干脆不触发; - 必须再补一次
usart_data_receive()(哪怕丢弃返回值),把“读 SR→读 DR”走完整,标志才真正落下。
STM32F4 的 HAL 宏 __HAL_UART_CLEAR_IDLEFLAG 展开后同样是先读 SR 再读 DR(见下文贴出的宏定义),同一个硬件机制、两家封装不同——理解了清除序列,换厂牌也不会再踩。
中断处理流程
串口使能中断相关代码:
|
|
串口中断处理函数:
|
|
代码说明
USART_INT_RBNE(读数据区非空)中断里逐字节usart_data_receive取数、写进 ringbuf;USART_INT_IDLE分支做三件事:清标志 → 再调一次usart_data_receive()补完清除序列 → 写事件通知上层“一帧结束”;- 特别要注意的是:与STM32F4不同的是,在进入闲时中断后,需要调用usart_data_receive函数,用于清除接收完成标志位,否则闲时中断会存在被触发多次或不触发的情况。
与 STM32 的对照
在STM32f4xx中,清除闲时中断标志宏定义如下:
|
|
可以看到有读取操作对DR寄存器进行读取操作,相当于GD32标准库中usart_data_receive函数的作用。
踩坑记录与注意事项
- 只清标志不读 DATA = IDLE 异常:本文核心坑。清除序列是“读状态→读数据”两步,GD32 里必须显式补
usart_data_receive(); - IDLE 只在“数据后变空闲”的边沿触发一次:持续空闲不再触发,想再触发得先收到新数据。调试时觉得“怎么只进来一次”多半是这个机制,不是 bug;
- 断帧延迟与波特率挂钩:空闲判定时长约一个字节时间,9600 波特下约 1ms,115200 下几十微秒——对帧间隔敏感的协议要把这个延迟算进去;
- 数据量大就换 DMA:RBNE 逐字节中断在高波特率下 CPU 开销大,标准组合是“DMA 循环收 + IDLE 中断断帧”,在 IDLE 中断里按 DMA 计数差取出一帧;
- 中断里只做搬运和通知:本文在 ISR 里只发事件是对的做法,别在中断里做协议解析等长活。
Author 软件开发大郭
LastMod 2022-07-20