1. 从一次调试经历说起:为什么我的中断不响应?
大家好,我是老李,一个在嵌入式领域摸爬滚打了十多年的老码农。今天想和大家聊聊一个在GD32F303开发中,尤其是使用串口DMA时,几乎每个人都会踩进去的“坑”——中断配置的顺序问题。这事儿说起来不大,但调试起来能让你怀疑人生。我记得有一次,我负责的一个工业控制器项目,通信模块死活收不到完整的数据包。代码逻辑我检查了无数遍,串口初始化、DMA配置、中断服务函数,看起来都严丝合缝,和官方例程几乎一模一样。但诡异的是,数据发送正常,接收DMA的中断函数就像睡着了一样,一次都没进去过。
那几天,我对着逻辑分析仪抓的波形,还有GD32的参考手册,反复比对。波形显示数据确实从对方设备发过来了,也进了USART的接收数据寄存器,DMA的传输计数器也在递减,可就是没有中断产生。我一度怀疑是芯片的DMA控制器有硬件缺陷,甚至动了换型号的念头。后来,在一个深夜,我几乎是一行一行地“人肉”单步调试库函数,才终于揪出了这个“元凶”:几个关键函数调用的顺序摆错了。不是功能函数用错了,仅仅是先调用A还是先调用B的问题。调换之后,一切豁然开朗,中断响应得那叫一个欢快。
所以,这篇文章,我就想把这个“血泪教训”掰开了、揉碎了讲清楚。咱们不光是知道“应该怎么写”,更要弄明白“为什么必须这么写”。当你理解了GD32的DMA控制器内部那些寄存器是怎么被库函数操作的,你就能真正避开这个坑,甚至能举一反三,解决其他外设类似的配置问题。无论你是刚接触GD32的新手,还是正在被类似问题困扰的朋友,希望我的这些经验能帮你节省几个不眠之夜。
2. 核心问题解剖:中断使能与通道复位的“死锁”
我们先直接亮出问题的核心结论,这也是很多朋友模仿官方例程却失败的根本原因:在GD32的DMA库函数中,开启中断(dma_interrupt_enable)和使能通道(dma_channel_enable)的操作,绝对不能放在通道复位(dma_deinit)或初始化(dma_init)之前。 你必须先完成通道的初始配置,最后再去打开它的中断和使能位。
这听起来有点反直觉,对吧?我们写程序的一般逻辑是:先使能一个功能(比如中断),然后再去配置它、使用它。或者觉得把中断相关的配置放在一起,代码更整洁。但在GD32的DMA这里,这个“好习惯”会直接导致中断失效。为什么?这得从库函数对底层寄存器的操作顺序说起。
2.1 关键函数背后的寄存器操作
让我们把涉及到的几个关键库函数“扒开”看看,理解它们到底对寄存器做了什么。
首先是 dma_deinit(DMA0, DMA_CH3)。 这个函数名字叫“去初始化”,你可以把它理解为“硬复位”某个DMA通道。它干的最重要的一件事,就是把该通道的控制寄存器 CHCTL 直接写为 0x00。CHCTL 寄存器是每个DMA通道的“大脑”,里面有几个至关重要的位:
- Bit 0 (CHEN):通道使能位。写1开启DMA传输,写0关闭。
- Bit 1 (FTFIE):传输完成中断使能位。写1允许在数据全部传完时产生中断。
- Bit 2 (HTFIE):半传输完成中断使能位。写1允许在传完一半数据时产生中断。
- Bit 3 (ERRIE):传输错误中断使能位。
当你调用 dma_deinit 时,它一股脑把这个 CHCTL 寄存器清零了。这意味着,无论之前这个通道是开着还是关着,中断是允许还是禁止,在这一步之后,全部归零,通道被强制置于“休眠”状态。
接着看 dma_interrupt_enable(DMA0, DMA_CH3, DMA_INT_FTF)。 这个函数的作用很单纯,就是找到 CHCTL 寄存器,然后把对应的中断使能位(比如FTFIE)置为1。它内部的操作通常是类似 CHCTL |= (1 << 1) 这样的“读-改-写”操作。
最后是 dma_channel_enable(DMA0, DMA_CH3)。 这个函数更简单,就是把 CHCTL 寄存器的通道使能位 CHEN (Bit 0) 置1。
2.2 错误的顺序如何导致“死锁”
现在我们来模拟一下错误的配置顺序,这也是我最初犯错的写法:
// 错误的顺序示例
/* 1. 先想着把中断打开 */
dma_interrupt_enable(DMA0, DMA_CH3, DMA_INT_FTF);

200

被折叠的 条评论
为什么被折叠?



