【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 自动(发送下一批 / 接收处理并启动新接收)→ 传输完毕回调处理。


相关推荐
HQB202616 小时前
EG1198|屹晶微电子 120V 内置 MOS 异步降压 DC‑DC(高性价比)
嵌入式硬件·硬件工程
果壳science17 小时前
张祥前实验如何获取正电荷
笔记·科技·嵌入式硬件·硬件工程·制造
南山电子nscn17 小时前
中科芯CETC汽车电池管理系统BMS进口芯片国产型号替代指南
单片机·汽车·bms
沉淀的.晴天18 小时前
can总线
stm32
桀人18 小时前
认识AT24C02
stm32·单片机·嵌入式硬件
国科安芯19 小时前
星间激光通信终端CANFD控制网络的时延确定性与辐射可靠性
网络·嵌入式硬件·信息与通信·canfd·低轨卫星·商业航天·抗辐射
一直C20 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
传感器与混合集成电路20 小时前
150MHz主频、8路DAC与6路PWM:JLH221360伺服控制SIP解析
嵌入式硬件
comyccccc20 小时前
STM32PID控制板设计全解析学习笔记
单片机·嵌入式硬件
碧海银沙音频科技研究院21 小时前
实现炬芯ATS308X的usb cdc 串口IAP 固件升级功能实现
人工智能·嵌入式硬件·深度学习·算法