关闭单个通道的中断
问题的本质:NVIC 是"粗粒度"的
STM32F4 有 16 条 EXTI 线(EXTI0~EXTI15),但 NVIC 只把它们合并成 7 个中断号:
EXTI0 → EXTI0_IRQn (1根线独享) EXTI1 → EXTI1_IRQn (1根线独享) EXTI2 → EXTI2_IRQn (1根线独享) EXTI3 → EXTI3_IRQn (1根线独享) EXTI4 → EXTI4_IRQn (1根线独享) EXTI5~9 → EXTI9_5_IRQn (5根线共享) EXTI10~15 → EXTI15_10_IRQn (6根线共享)
你原来的Exit_Disable()用的是HAL_NVIC_DisableIRQ(EXTI15_10_IRQn)——这一关就把 bit10~bit15 全部 6 根线都瘫痪了。这就是"关一个通道影响 12 个通道"的原因。
STM32 硬件其实有一个"逐线开关"
在 EXTI 硬件到 NVIC 之间,有一个寄存器叫IMR(Interrupt Mask Register,中断掩码寄存器):
EXTI->IMR 是一个 32 位寄存器,每位对应一根 EXTI 线: bit15 bit14 bit13 bit12 bit11 bit10 bit9 bit8 ... bit0 ┌───┬───┬───┬───┬───┬───┬───┬───┐ │ 1 │ 0 │ 1 │ 0 │ 0 │ 0 │ 0 │ 0 │ ... └───┴───┴───┴───┴───┴───┴───┴───┘ ↑ ↑ ↑ │ │ └── bit13=1: EXTI13 放行 │ └────── bit14=0: EXTI14 拦截!(不进 NVIC) └────────── bit15=1: EXTI15 放行每根线有自己独立的 1 bit 开关。关掉 bit14 只影响 EXTI14 这一根线,EXTI15 照样能通过。
这就是绕过 NVIC 共享问题的核心武器。
代码里三层是怎么配合的
上电时:MX_GPIO_Init()(第 157 行)
c
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // ← 改成普通输入把所有 16 个 INT 引脚配成INPUT。此时:
- 引脚是高阻输入,能读电平,但不连 EXTI 边沿检测电路
- EXTI 硬件根本不知道引脚上有上升沿
- IMR 全部为 0(默认),NVIC 只设了 Priority 但没 Enable
结果:上电后 16 路全部"死"的,不管传感器怎么发 INT 都不会触发中断。
开启通道 N:Exit_Init(N)(第 243~264 行)
三步操作,层层打开:
// 第一步:把 GPIO 从 INPUT 改成 IT_RISING // → 引脚接入 EXTI 边沿检测电路,能检测上升沿了 GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; HAL_GPIO_Init(MIR3DA_MAP_INT[num].Port, &GPIO_InitStruct); // HAL_GPIO_Init 内部会自动把 EXTI->IMR 对应 bit 置 1 // 第二步:清除之前可能积攒的 pending 位 // → 假如之前有上升沿产生了但被 IMR 拦着,现在开了会立刻触发——不行,先清掉 __HAL_GPIO_EXTI_CLEAR_IT(pin); // 第三步:使能 NVIC // → 把 CPU 的响应开关也打开(如果已经开着,重复调用无害) HAL_NVIC_EnableIRQ(MIR3DA_MAP_INT[num].IRQn);三层全开,只有通道 N 能产生中断。其他 15 路的 GPIO 还是 INPUT,EXTI 边沿检测电路根本不工作。
关闭通道 N:Exit_Disable(N)(第 272~288 行)
三步操作,从三层全部切断:
// 第一步:关 IMR 对应 bit // → 即使引脚上还有上升沿,EXTI 硬件也不会往 NVIC 发请求了 EXTI->IMR &= ~pin; // 第二步:清 pending 位 // → 如果之前已经有一个 pending 挂着没处理,清掉它,防止下次开 NVIC 时突然爆发 EXTI->PR = pin; // 第三步:GPIO 改回 INPUT // → 从源头切断——连边沿检测电路都不接了,彻底静默 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(MIR3DA_MAP_INT[num].Port, &GPIO_InitStruct);
具体例子:采通道5 时通道7 在发 INT
通道5 = PD15 → EXTI15,通道7 = PD14 → EXTI14,两个共用EXTI15_10_IRQn。
通道5 INT 上升沿: PD15 → EXTI 边沿检测(GPIO=IT_RISING ✅) → EXTI15 PR 置位 → IMR bit15=1 → 放行 → EXTI15_10_IRQn → NVIC(已 Enable) → CPU 进 EXTI15_10_IRQHandler → HAL_GPIO_EXTI_IRQHandler(INT15_Pin) 检查 PR → 有 → HAL_GPIO_EXTI_Callback → IntFlag=1 ✅ 通道7 INT 上升沿: PD14 → EXTI 边沿检测(GPIO=INPUT ❌ 不接!) → 边沿检测电路不工作 → PR 不置位 → 不进 IMR、不进 NVIC、不进 CPU ❌通道7 的信号在第一层(GPIO 模式)就被挡住了,连 EXTI 硬件都不知道它有上升沿。
退一步说,即使 GPIO 是 IT_RISING(比如Exit_Init之前被其他代码配过),第二层 IMR bit14=0 也能拦住——pending 位不会传到 NVIC。
一句话
NVIC 是"总线开关"(一关关一片),EXTI->IMR 是"分线开关"(一关关一根),GPIO 模式是"总闸"(从源头断开)。三层叠加,关通道时三层全断,开通道时三层全通,所以即使 NVIC 被多个通道共享,也能做到只开关一个通道。
<svg viewBox="0 0 680 560" width="100%" role="img" xmlns="http://www.w3.org/2000/svg"> <title>STM32 EXTI 中断三层控制机制</title> <desc>展示 GPIO 模式、EXTI->IMR 逐线掩码、NVIC 共享 IRQ 三层如何协同实现单通道中断开关</desc> <defs> <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"> <path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </marker> <marker id="arrowStop" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"> <path d="M2 1L8 5L2 9" fill="none" stroke="#E24B4A" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </marker> </defs> <!-- Title --> <text x="340" y="26" text-anchor="middle" font-size="15" font-weight="500" fill="#26215C">STM32 EXTI 中断三层控制:为什么能做到只开关一个通道</text> <!-- Layer 1: GPIO Mode --> <rect x="40" y="48" width="600" height="86" rx="12" fill="#EEEDFE" stroke="#534AB7" stroke-width="0.5"/> <text x="56" y="70" font-size="13" font-weight="500" fill="#3C3489">第一层:GPIO 模式寄存器(MODER/PUPDR)</text> <rect x="56" y="82" width="270" height="42" rx="8" fill="#CECBF6" stroke="#7F77DD" stroke-width="0.5"/> <text x="68" y="100" font-size="12" fill="#26215C">GPIO_MODE_INPUT</text> <text x="68" y="115" font-size="11" fill="#534AB7">引脚不连 EXTI 边沿检测电路</text> <rect x="354" y="82" width="270" height="42" rx="8" fill="#CECBF6" stroke="#7F77DD" stroke-width="0.5"/> <text x="366" y="100" font-size="12" fill="#26215C">GPIO_MODE_IT_RISING</text> <text x="366" y="115" font-size="11" fill="#534AB7">引脚接入 EXTI 上升沿检测</text> <!-- Layer 2: EXTI->IMR --> <rect x="40" y="148" width="600" height="86" rx="12" fill="#E1F5EE" stroke="#0F6E56" stroke-width="0.5"/> <text x="56" y="170" font-size="13" font-weight="500" fill="#04342C">第二层:EXTI->IMR 逐线掩码(每根 EXTI 线独立 1 bit)</text> <rect x="56" y="182" width="270" height="42" rx="8" fill="#9FE1CB" stroke="#1D9E75" stroke-width="0.5"/> <text x="68" y="200" font-size="12" fill="#04342C">EXTI->IMR |= pin</text> <text x="68" y="215" font-size="11" fill="#0F6E56">该线中断请求 → 放行</text> <rect x="354" y="182" width="270" height="42" rx="8" fill="#9FE1CB" stroke="#1D9E75" stroke-width="0.5"/> <text x="366" y="200" font-size="12" fill="#04342C">EXTI->IMR &= ~pin</text> <text x="366" y="215" font-size="11" fill="#0F6E56">该线中断请求 → 拦截(其他线不受影响)</text> <!-- Layer 3: NVIC --> <rect x="40" y="248" width="600" height="86" rx="12" fill="#FAECE7" stroke="#993C1D" stroke-width="0.5"/> <text x="56" y="270" font-size="13" font-weight="500" fill="#4A1B0C">第三层:NVIC IRQ 使能(多根 EXTI 线共享一个 IRQ 号)</text> <rect x="56" y="282" width="270" height="42" rx="8" fill="#F5C4B3" stroke="#D85A30" stroke-width="0.5"/> <text x="68" y="300" font-size="12" fill="#4A1B0C">HAL_NVIC_EnableIRQ(IRQn)</text> <text x="68" y="315" font-size="11" fill="#993C1D">CPU 响应该 IRQ(影响同组所有线)</text> <rect x="354" y="282" width="270" height="42" rx="8" fill="#F5C4B3" stroke="#D85A30" stroke-width="0.5"/> <text x="366" y="300" font-size="12" fill="#4A1B0C">HAL_NVIC_DisableIRQ(IRQn)</text> <text x="366" y="315" font-size="11" fill="#993C1D">CPU 不响应该 IRQ(同组全部瘫痪)</text> <!-- Arrows between layers --> <path d="M340 134L340 148" fill="none" stroke="#534AB7" stroke-width="1" marker-end="url(#arrow)"/> <path d="M340 234L340 248" fill="none" stroke="#0F6E56" stroke-width="1" marker-end="url(#arrow)"/> <!-- Example section --> <rect x="40" y="350" width="600" height="190" rx="12" fill="#F1EFE8" stroke="#888780" stroke-width="0.5"/> <text x="56" y="372" font-size="13" font-weight="500" fill="#2C2C2A">举例:采通道5(PD15 → EXTI15)时,通道7(PD14 → EXTI14)也在产生 INT</text> <!-- Ch5 active --> <rect x="56" y="384" width="262" height="56" rx="8" fill="#E1F5EE" stroke="#1D9E75" stroke-width="0.5"/> <text x="68" y="402" font-size="12" font-weight="500" fill="#04342C">通道5 (PD15)</text> <text x="68" y="420" font-size="11" fill="#0F6E56">IMR bit15 = 1 (开) | NVIC = on</text> <text x="68" y="434" font-size="11" fill="#0F6E56">INT 上升沿 → CPU 响应 → IntFlag=1 ✅</text> <!-- Ch7 blocked at IMR --> <rect x="358" y="384" width="262" height="56" rx="8" fill="#FAECE7" stroke="#D85A30" stroke-width="0.5"/> <text x="370" y="402" font-size="12" font-weight="500" fill="#4A1B0C">通道7 (PD14)</text> <text x="370" y="420" font-size="11" fill="#993C1D">IMR bit14 = 0 (关) | NVIC = on</text> <text x="370" y="434" font-size="11" fill="#993C1D">INT 上升沿 → IMR 拦截 → 不进 CPU ❌</text> <!-- Key point --> <rect x="56" y="452" width="564" height="76" rx="8" fill="#E6F1FB" stroke="#378ADD" stroke-width="0.5"/> <text x="68" y="472" font-size="12" font-weight="500" fill="#042C53">关键:NVIC(第三层)虽然开着,但 IMR(第二层)把通道7 拦截了</text> <text x="68" y="490" font-size="11" fill="#0C447C">EXTI15_10_IRQn 同时覆盖 bit10~bit15,但每根线有独立的 IMR 位</text> <text x="68" y="506" font-size="11" fill="#0C447C">关 bit14 不影响 bit15 —— 这就是"逐线掩码"实现单通道控制的核心</text> <text x="68" y="522" font-size="11" fill="#0C447C">Exit_Disable 还额外把 GPIO 改回 INPUT,从源头切断边沿检测</text> </svg>