【STM32】 DMA中断机制、NVIC 联动与配置实战前章


DMA中断机制、NVIC 联动与配置实战

1. DMA 中断

1.1 回答上一章的遗留问题

上一章结尾问:

"CPU 怎么知道 DMA 把数据搬迁完毕了?DMA 又怎么告诉 CPU,让 CPU 介入做后续数据处理?"

直接回答:

  • DMA(核心是通道)控制器,本质也是一个 MCU 片上外设,和串口、SPI、I2C 一样;
  • 既然其他外设能向 CPU 发送中断,DMA 也能;
  • 当 DMA 搬迁完毕数据,会通过中断的方式通知 CPU,进而让 CPU 进行中断处理;在中断处理函数内部,就可以进一步处理数据了(打印、保存、计算、启动下一批传输......)。

扩展理解:

为什么用中断而不是 CPU 轮询?****------中断是"事件驱动":搬完了才通知,CPU 平时不用操心;轮询则是 CPU 反复去查"搬完没",白白消耗 CPU。

这正好延续了 DMA 的设计哲学:把 CPU 从等待和搬运中彻底解放出来。

1.2 中断使能位:TCIE、HTIE、TEIE(在 DMA_CCRx 中)

既然数据搬迁工作以通道为主体,那么每一个通道一定会有对应的配置信息来支持产生中断------这个寄存器就是之前反复提到的 DMA_CCRx(偏移地址 0x08 + 20 × (通道编号 - 1),复位值 0x0000 0000)。

其中与中断有关的三个位(课件中红色标注区域):

位 名称 英文全称 触发时机 典型用途
位 3 TEIE Transfer error interrupt enable 传输过程中发生总线错误或配置冲突 异常捕获和处理, 避免程序卡死
位 2 HTIE Half transfer interrupt enable 数据传输量达到预设总额的一半 半批数据先处理, 与 DMA 搬运并行
位 1 TCIE Transfer complete interrupt enable 该通道所有数据传输完成 通知 CPU:"我已搬完,你可以处理了"(最常用)

逐条解读:

  • TCIE(传输完成中断**):很好理解,就是当该通道的所有数据传输完成时触发------通知 CPU"我已经搬迁完毕数据了,你可以处理了";**
  • HTIE(半传输中断)****:当数据传输量达到预设总额的一半时触发中断;
  • TEIE(传输错误中断)****:当传输过程中发生总线错误或配置冲突时触发,主要是为了进行异常捕获和处理,避免程序卡死。

重要结论:

该寄存器每一个通道都有,所以每一个通道都可以独立配置、独立向 CPU 发送中断。(再次印证:DMA 中断以通道为载体。)

1.3 为什么会存在半传输中断?(了解)

原因:实现数据传输与数据处理的并行,消除等待时间。

  • 在只有"传输完成中断(TC)"的情况下,CPU 必须等整块数据搬运完才能开始工作;
  • 但如果有了 HT 半传输中断,就可以先传输一半数据,然后让 CPU 介入数据处理,同时 DMA 在继续搬运后半部分数据------这样数据处理和数据搬迁在逻辑上就并行进行了。

两种工作方式对比:

方式 时序
只有 TC (串行) DMA 数据搬运完 ---TC→ CPU 处理数据 ---→ DMA 数据搬运完 ---TC→ CPU 处理数据 ......(CPU 大量时间在"干等")
有 HT (并行) DMA 搬一半 ---HT→ CPU 处理前半段 ‖ DMA 继续搬后半段 ---TC→ 搬一半 ---HT→ ......(CPU 与 DMA 接力赛)

1.4 双缓冲(Ping-Pong Buffering):HT 最经典的应用

场景设定:假设你有一个 1024 字节的缓冲区:

  • 前 512 字节(前半段):当 DMA 填充完这部分时,触发 HT 中断;
  • 后 512 字节(后半段):此时 DMA 不会停止,它继续向后半段搬运数据;
  • 此时:CPU 在中断处理函数里开始处理前半段的数据,而 DMA 正在后台搬运后半段的数据。两者互不干扰,像"接力赛"一样循环往复。

为什么叫"Ping-Pong"?------CPU 和 DMA 就像打乒乓球:DMA 把球(前半段数据)打给 CPU(触发 HT),CPU 接球处理时 DMA 已经把下一个球(后半段数据)准备好了,如此往复,任何时刻都不空转。

✨核心场景:音频处理。

如果"读取完音频 → 播放 → 再读取 → 再播放",中间会产生等待,出现音频卡顿;采用 HT 中断方式,可以让播放音频和拷贝音频同时进行,有效解决卡顿问题。

这个方案后续在相关项目中会体现,此处不是目前重点。

1.5 DMA 通道触发中断的完整路径(与 NVIC 联动)

先看一张图理解整体架构(课件"最后一张图"):

cpp 复制代码
Cortex-M3 内核  ←→  NVIC(嵌套向量中断控制器)
                      ├─ 6 个中断优先级寄存器
                      └─ 所有中断源/外部中断源的开关控制
                          ↑ 中断结果反馈
DMA 控制器
  ├─ DMA_CCR / DMA_CNDTR / DMA_CPAR / DMA_CMAR(寄存器组)
  ├─ 通道中断(软件使能 / 硬件使能)
  └─ 仲裁器(软件优先级 / 硬件优先级)、控制逻辑、外设通信接口
  • CPU 修改寄存器 = 设置 DMA 通道配置(这是程序员做的事);
  • 通道产生中断后,中断结果反馈给 NVIC;
  • NVIC 根据中断向量表找到对应的中断处理函数,CPU 跳转执行。

1.6 中断向量编号(stm32f10x.h 的 IRQn_Type 枚举)

Cortex-M3 内核异常(负数编号):NonMaskableInt_IRQn = -14、HardFault_IRQn = -13、MemoryManagement_IRQn = -12、BusFault_IRQn = -11、UsageFault_IRQn = -10、SVCall_IRQn = -5、DebugMonitor_IRQn = -4、PendSV_IRQn = -2、SysTick_IRQn = -1。

STM32 外设中断(正数编号),其中 DMA 相关:

中断名 编号 说明
DMA1_Channel1_IRQn 11 DMA1 通道 1 全局中断
DMA1_Channel2_IRQn 12 DMA1 通道 2 全局中断
DMA1_Channel3_IRQn 13 DMA1 通道 3 全局中断
DMA1_Channel4_IRQn 14 DMA1 通道 4 全局中断
DMA1_Channel5_IRQn 15 DMA1 通道 5 全局中断
DMA1_Channel6_IRQn 16 DMA1 通道 6 全局中断
DMA1_Channel7_IRQn 17 DMA1 通道 7 全局中断
DMA2_Channel1_IRQn 56 DMA2 通道 1 全局中断
DMA2_Channel2_IRQn 57 DMA2 通道 2 全局中断
DMA2_Channel3_IRQn 58 DMA2 通道 3 全局中断
DMA2_Channel4_5_IRQn 59 DMA2 通道 4 和通道 5 全局中断(共用)

观察规律:

DMA1 每个通道独占一条中断线(11~17),所以在 DMA1 的中断处理函数里,理论上只用关心"自己这个通道"的事件;而 DMA2 通道 4、5 共用一个中断号,需要在处理函数里再区分是哪个通道。

✨核心结论:

DMA 中断的配置和产生,是以通道 Channel 为载体的------这点在后续代码中会有非常强烈的体现。

1.7 最后的问题:CPU 怎么区分是"哪种中断"?

现在 DMA 特定通道已经能配置并完成数据搬迁,也能通过中断通知 CPU 某个通道产生了中断。

但还剩最后一组问题:

CPU 确实能收到某一个通道发来的中断,但是,CPU 怎么区分是"传输完毕(TC)""传输一半(HT)"还是"传送出错(TE)"呢?

朴素的思考:

你一个通道也就一根中断线连接到 CPU 内的 NVIC,最多传递一个高低电平或者电脉冲,告诉 CPU"有中断了"。

太多种类的信息传递不了------如果要传递,这不就成了通信,还得再定个协议什么的。

那么,怎么办呢?

引入 DMA 控制器内部倒数第二个寄存器:DMA_ISR(中断状态寄存器),

以及最后一个寄存器:DMA_IFCR(中断标志清除寄存器)。


2. DMA_ISR:DMA 中断状态寄存器

  • 偏移地址:0x00;复位值:0x0000 0000;只读(各位属性为 r)。

位布局(每通道 4 个标志,共 7 通道):

通道 传输错误标志 半传输标志 传输完成标志 全局中断标志
通道 7 TEIF7(位27) HTIF7(位26) TCIF7(位25) GIF7(位24)
通道 6 TEIF6(位23) HTIF6(位22) TCIF6(位21) GIF6(位20)
通道 5 TEIF5(位19) HTIF5(位18) TCIF5(位17) GIF5(位16)
通道 4 TEIF4(位15) HTIF4(位14) TCIF4(位13) GIF4(位12)
通道 3 TEIF3(位11) HTIF3(位10) TCIF3(位9) GIF3(位8)
通道 2 TEIF2(位7) HTIF2(位6) TCIF2(位5) GIF2(位4)
通道 1 TEIF1(位3) HTIF1(位2) TCIF1(位1) GIF1(位0)

4 个标志位的含义:

标志 x 的含义 整体含义
TEIFx 通道号 是否发生了 DMA 数据搬迁错误(TE)
HTIFx 通道号 是否发生了 DMA 数据半传输完成(HT)
TCIFx 通道号 是否发生了 DMA 数据全部传输完成(TC)
GIFx 通道号 是否产生了上述任意事件(TE、HT 或 TC)
  • 这些位由硬件设置;
  • 清除方式:在 DMA_IFCR 寄存器的相应位写入 '1'。

2.1 最重要的机制:硬件自动置位

一旦特定通道产生中断,除了给 CPU 发送中断通知之外,DMA 硬件还会自动(自动,自动,自动)设置 DMA_ISR 寄存器中特定的位。

举例:通道 5 发生了 TC,那么对应位的状态为:

复制代码
TEIF5 = 0(没有传输错误)
HTIF5 = 0(没有半传输事件)
TCIF5 = 1(传输完成!)
GIF5  = 1(有事件发生)

逻辑闭环:

CPU 一旦收到中断 → 回过头检测 DMA_ISR 寄存器 → 即可明确"是哪一个通道、产生了什么中断" → 再进行软件处理。至此,DMA 理解的逻辑闭环完成了。

顺带一提:

这些中断标志位是只读的,不能手动直接清 0**。**

如果处理完了,要清除对应的中断信息,就需要设置最后一个寄存器 DMA_IFCR,来达到清除 DMA_ISR 的目的。

2.2 DMA_IFCR:DMA 中断标志清除寄存器

  • 偏移地址:0x04;复位值:0x0000 0000。

位布局(与 DMA_ISR 一一对应,写 1 清除):

清除位 对应标志 作用
CTEIFx (位27,23,19,15,11,7,3) TEIFx 1 = 清除 DMA_ISR 中的 TEIF 标志
CHTIFx (位26,22,18,14,10,6,2) HTIFx 1 = 清除 DMA_ISR 中的 HTIF 标志
CTCIFx (位25,21,17,13,9,5,1) TCIFx 1 = 清除 DMA_ISR 中的 TCIF 标志
CGIFx (位24,20,16,12,8,4,0) GIFx (含全部) 1 = 清除 DMA_ISR 中的 GIF、TEIF、HTIF 和 TCIF 标志
  • 这些位由软件设置和清除;0 = 不起作用。

例子(理解清除逻辑):

你把你们村里的小朋友打了,小朋友要收拾你,但不直接找你(因为打不过),ta 会去找你妈,让你妈收拾你------就这个逻辑。

解读:

DMA 不直接"打断" CPU 的每一步(就像小朋友不直接找你打架),而是通过 ISR 标志位"告状"给中断系统(找你妈),CPU 在中断里"收拾"(处理)完之后,再通过 IFCR 把"状纸"(标志位)撕掉------处理完必须清标志,否则下次中断无法正确区分新事件。

2.3 关于 HAL 库

关于这些寄存器的设置和清除工作,后续有 HAL 库自动为我们完成。

我们后续阅读源码的时候,反向查看文档再来细说。

(例如 HAL 库的 HAL_DMA_IRQHandler 会自动读取 ISR、判断 TC/HT/TE、调用对应回调函数并清除标志。)

2.4 一张图看懂完整链路

核心链路:

bash 复制代码
通道产生事件(TC/HT/TE)
  → DMA 硬件自动置位 DMA_ISR 对应标志位
  → 通过中断线通知 NVIC
  → CPU 响应,进入对应通道的 NVIC 中断处理函数
  → 读 DMA_ISR:区分是哪个通道、哪种事件
  → 软件处理数据
  → 写 DMA_IFCR 对应位清除标志
  → 返回主程序

3. 配置案例

3.1 案例 1:配置 DMA(串口 DMA 接收)

使用 USART1 的 DMA 模式进行数据接收的配置示例:

端 参数 配置值 为什么这样配
外设端 USART1 数据宽度 8bit(1byte) 串口数据有效长度就是 8 位
地址自增? 不自增 只有一个数据寄存器
寄存器地址 0x40013804 就是 USART1 数据寄存器地址
传输过程 数据数量 32 一次配置搬运 32 个数据项
存储器端 SRAM 数据宽度 8bit(1byte) 与收发端宽度匹配
地址自增? 自增 需要连续保存串口数据寄存器的数据
存储器地址 0x200000e0 接收缓冲区起始地址

详细配置解释:

  1. 寄存器地址(0x40013804)其实就是 USART1 的数据寄存器地址;
  2. 因为数据有效长度就是 8 位,所以收、发端数据宽度都配置为 8 位;
  3. 因为只有一个数据寄存器,所以外设端寄存器地址需要配置为不自增模式;
  4. 因为需要连续保存串口数据寄存器的数据,所以存储器(SRAM)端的地址需要自增。

总结规律:

外设端"死守一个寄存器"(不自增),内存端"排队存数据"(自增)------这就是串口接收最典型的"单增模式"配置。

3.2 案例 2:按图说话

需求:

  1. 传输方向为外设到存储器;
  2. 外设只有一个 8 位的寄存器保存数据,该寄存器地址为 0x40013804,记作 REG(0x40013804);
  3. 内存缓冲区长度为 256 字节,起始地址为 0x200000e0,记作 BUF(0x200000e0);
  4. 数据宽度为 8 位,外设地址不自增(只有一个),一次 DMA 传输读取 256 字节,内存缓冲区地址自增(256 个地址)。

DMA 工作过程示意图中的地址关系:

  • 外设端:寄存器地址固定 0x40013804;
  • 内存端:缓冲区从 0x200000e0 到 0x200001e0(0x200000e0 + 256 = 0x200001e0)。

完整 12 步过程:

步骤 事件 动作
1 CPU 启动 CPU 启动 DMA 数据传输
2 外设就绪 外设寄存器数据非空,外设向 DMA 控制器发出 DMA 请求
3 首字节搬运 DMA 控制器向外设应答, 启动数据传输:REG(0x40013804) → BUF(0x200000e0)
4 事件清除 数据传输完成后,寄存器数据非空事件自动被清除
5 外设就绪 外设寄存器数据非空(又来数据了),外设向 DMA 控制器发出 DMA 请求
6 第 2 字节搬运 DMA 应答,启动数据传输:REG(0x40013804) → BUF(0x200000e0+1)
7 事件清除 数据传输完成后,寄存器数据非空事件自动被清除
8 ...... 如此往复(第 3、4、...、254 字节同理,内存地址每次 +1)
9 外设就绪 外设寄存器数据非空(又来数据了),外设向 DMA 控制器发出 DMA 请求
10 最后 1 字节搬运 DMA 应答, 启动数据传输:REG(0x40013804) → BUF(0x200000e0+255)
11 事件清除 数据传输完成后,寄存器数据非空事件自动被清除
12 传输完成 256 字节数据传输完成,产生数据传输完成(TC)中断

推演要点:

  • 外设地址永远不变(0x40013804)------外设只有一个数据寄存器,PINC = 0;
  • 内存地址每搬 1 字节 +1(0x200000e0 → 0x200000e0+1 → ... → +255)------MINC = 1,按数据宽度 8 位自增;
  • 每搬完 1 字节,外设"数据非空"事件自动清除,等于告诉外设"我这口井的水被你打走了,可以继续蓄水";
  • 搬满 NDT = 256 项后,CNDTR 归零 → 产生 TC 中断 → 通知 CPU 处理这 256 字节。

4. 总结:使用 DMA 的流程

三个阶段、一条主线:

阶段 参与者 配置/动作 产出
① 程序员设置 程序员(软件) 方向、优先级、地址自增、循环模式、数据宽度 DMA 通道建立
② CPU 启动 CPU 源/目的地址、传输数据长度 启动 DMA 传输
③ DMA 自动 DMA + 中断 发送:发送下一批数据; 接收:处理接收到的数据,启动新的 DMA 接收流程 传输完毕回调处理

闭环解读:

  • 程序员把"怎么搬"配好(通道建立);
  • CPU 告诉 DMA"从哪搬到哪、搬多少"(启动传输);
  • 之后 DMA 全自动搬运,搬完通过中断回调通知 CPU;
  • 发送场景:CPU 在回调里准备/发送下一批数据;
  • 接收场景:CPU 在回调里处理接收到的数据,并启动新的 DMA 接收流程(重新配 CNDTR、重新使能,循环往复)。

一句话总结:

配通道 → 启动传输 → DMA 自动搬 → 中断回调收尾,这就是使用 DMA 的完整流程。


面试题

一、中断机制类

1. DMA 搬完数据后怎么通知 CPU?凭什么 DMA 能发中断?

DMA 通过中断方式通知 CPU。

因为 DMA(核心是通道)控制器本质也是一个 MCU 片上外设,和串口、SPI、I2C 一样,都有能力向 CPU 发送中断。

CPU 收到中断后进入中断处理函数,处理已搬完的数据。

⚡ 追问:为什么用中断而不是 CPU 轮询?------事件驱动,搬完才通知,CPU 平时不被打扰;轮询要反复查状态,浪费 CPU。


2. DMA 的中断使能位在哪?有哪几个?

在 DMA_CCRx(通道配置寄存器)中,共 3 个:

TCIE(位1,传输完成中断)、HTIE(位2,半传输中断)、TEIE(位3,传输错误中断)。

每个通道都有自己的一套,可独立配置。

⚡ 追问:TEIE 有什么用?------传输发生总线错误或配置冲突时触发,用于异常捕获,避免程序卡死。


3. TC、HT、TE 三种中断各自什么时候触发?

TC:该通道所有数据传输完成时;

HT:数据传输量达到预设总额一半时;

TE:传输过程中发生总线错误或配置冲突时。


4. 为什么要设计半传输中断 HT?

为了实现数据传输与数据处理的并行,消除 CPU 等待时间。

只有 TC 时 CPU 要等整块搬完才能处理;

有 HT 时,DMA 搬一半就通知 CPU 先处理前半段,同时 DMA 继续搬后半段,逻辑上并行。

⚡ 追问:HT 的经典应用是什么?------双缓冲(Ping-Pong Buffering),典型用于音频播放,避免"读一段播一段"的卡顿。


二、双缓冲类

5. 什么是双缓冲(Ping-Pong)?怎么工作?

以 1024 字节缓冲区为例:DMA 填满前 512 字节触发 HT 中断,CPU 在中断里处理前半段,同时 DMA 不停止、继续向后半段搬运;CPU 处理完前半段时,DMA 也快搬完后半段......两者像接力赛一样循环往复,互不干扰。

⚡ 追问:为什么能解决音频卡顿?------播放(CPU 处理)和拷贝(DMA 搬运)同时进行,不再"读完了才能播",消除了等待间隙。


三、区分与清除类

6. CPU 收到 DMA 中断后,怎么知道是哪个通道的哪种事件?

靠 DMA_ISR(中断状态寄存器,偏移 0x00,只读)。

每个通道有 4 个标志位:

TCIFx(传输完成)、HTIFx(半传输)、TEIFx(传输错误)、GIFx(任意事件)。

CPU 读 ISR 即可区分通道和事件类型。

⚡ 追问:这些标志位是软件置位还是硬件置位?------硬件自动置位。

一旦通道产生中断,DMA 硬件自动设置 ISR 对应位。

例如通道 5 发生 TC:TEIF5=0、HTIF5=0、TCIF5=1、GIF5=1。


7. DMA_ISR 的标志位能直接清零吗?怎么清?

不能,标志位是只读的。

必须通过 DMA_IFCR(中断标志清除寄存器,偏移 0x04)

对应位写 1 清除:

CTCIFx 清 TCIF、CHTIFx 清 HTIF、CTEIFx 清 TEIF、CGIFx 清 GIF 及全部。

⚡ 追问:处理完不清标志会怎样?------旧事件标志残留,下一次中断到来时无法正确区分新事件,且可能导致中断处理逻辑错乱;所以中断处理函数末尾"清标志"是必须步骤。


8. 一个通道只有一根中断线,怎么传递"完成/半传输/错误"多种信息?

一根中断线只能传一个电脉冲/高低电平,告诉 CPU"有中断";

具体是哪种事件由 DMA_ISR 的标志位区分------中断线负责"通知",ISR 负责"分类",IFCR 负责"销案"。


四、NVIC 与向量表类

9. DMA 的中断号和 NVIC 是什么关系?DMA1 各通道中断号是多少?

通道产生中断 → 反馈给 NVIC(嵌套向量中断控制器,管理中断开关与优先级)→ NVIC 按中断向量表触发对应中断处理函数。

⚡ 追问:DMA 中断以什么为载体?------以通道 Channel 为载体,通道是中断配置与产生的最小单位。


10. 完整描述一次串口 DMA 接收(256 字节)的过程。

① CPU 启动 DMA;

② 外设寄存器数据非空,向 DMA 发请求;

③ DMA 应答,搬 REG(0x40013804) → BUF(0x200000e0);

④ 数据非空事件自动清除;

⑤ 再来数据再请求、再搬(内存地址 +1,外设地址不变);...... 重复 256 次,第 256 字节搬到 BUF(0x200000e0+255);

⑫ 256 字节全部完成,产生 TC 中断通知 CPU。

⚡ 追问:为什么外设端不自增、内存端自增?------外设只有一个数据寄存器,必须固定地址;内存要连续存放多字节,所以自增排队。


五、配置与流程类

11. 串口 DMA 接收要配置哪些关键参数?

外设端:数据宽度(8 位)、不自增、外设地址(USART1 数据寄存器 0x40013804);

存储器端:数据宽度(8 位)、自增、内存地址(缓冲区);

传输过程:数据数量(NDT)、方向(DIR=0 外设→内存)、优先级(PL)、EN 使能;

外设侧还要开 USART_CR3 的 DMAR。

⚡ 追问:搬完后怎么进入下一轮接收?------在 TC 中断回调里处理数据,然后重新配置 CNDTR、重新使能通道,启动新的 DMA 接收流程(循环)。


12. 使用 DMA 的整体流程分哪几步?

三阶段:

① 程序员设置(方向、优先级、地址自增、循环模式、数据宽度)→ 建立 DMA 通道;

② CPU 启动(源/目的地址、传输数据长度)→ 启动 DMA 传输;

③ DMA 自动(发送下一批 / 接收处理并启动新接收)→ 传输完毕回调处理。


相关推荐
我叫洋洋2 小时前
涂鸦 T5+ hashcat 自动化破解 WPA2:从单缺 M2 到全自动值守的踩坑实录 破解wifi 密码
运维·单片机·自动化
Jason_zhao_MR7 小时前
米尔电子参展预告_中国工博会2026-外发版
嵌入式硬件·机器人·工业控制
Zw-awa8 小时前
电机家族:有的电机听“目标”,有的电机听“力气”
stm32
FPGA小徐12 小时前
STM32五路气敏传感器阵列电路详解:从HHC1000分压到AD8606跟随器和ADC采样
stm32·单片机·嵌入式硬件
Horn Still Sounds14 小时前
IMX6ULL裸机|I2C通信协议 + ADC模数转换学习笔记
arm开发·笔记·单片机·嵌入式硬件
智感子14 小时前
无人值守称重:地磅系统架构与流程
嵌入式硬件
新晨单片机设计16 小时前
S011C-基于STM32智能灯光控制系统(光强、声音、人体)【Proteus仿真+Keil程序+原理图】
stm32·嵌入式硬件·proteus·智能灯光控制系统
无敌贵点大王16 小时前
RTThread学习记录13——关于要使用一个外设,RTThread与cubemx到底要怎么配合?
c语言·stm32·学习·rtthread
女神下凡18 小时前
芯参谋(26):DDR4_软件设计规范
arm开发·单片机·嵌入式硬件