全部例程

W55MH32中断应用概览

W55MH32

更新于 2026年9月8日

本篇总结

使用外部中断,按键触发中断翻转 LED。

🔑 KEIL MDK、UART调试、NVIC、EXTI

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

本示例演示 W55MH32 的 NVIC(嵌套向量中断控制器)使用方法,以 EXTI6 外部中断为例:按下 PG6 按键触发中断,在中断服务函数中翻转 PD14 LED。
W55MH32 在内核水平上支持 15 个系统异常和 60 个外部中断,除个别异常优先级固定外,其余均可编程。配置中断只需掌握三步:使能外设中断 → 初始化 NVIC → 编写中断服务函数。

前置工具准备

开始本例程前,请确认以下工具已安装:

硬件连接

使用 PG6 接按键(EXTI6 外部中断),PD14 接 LED。USART1 的 TX(PA9) 和 RX(PA10) 通过板载 USB 转串口芯片连接到 DEBUG USB 口,用于观察串口日志。仅需一根 Micro USB 线连接开发板 DEBUG USB 口到 PC。

引脚功能方向模式
PG6EXTI6 按键输入上拉输入 (IPU)
PD14LED输出推挽输出 (Out_PP)
PA9USART1_TX开发板 → PC复用推挽输出 (AF_PP)
PA10USART1_RXPC → 开发板浮空输入 (IN_FLOATING)

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\NVIC\,核心文件如下:

NVIC/
├── USER/
│   ├── main.c            ← 中断优先级分组 + EXTI/NVIC初始化 + LED翻转ISR + 主流程
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数(本例 EXTI9_5_IRQHandler 也可放此)
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件(含中断向量表)
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

步骤1:中断优先级分组

函数:NVIC_PriorityGroupConfig() — 设置抢占优先级和子优先级的位数划分。整个程序只需调用一次,建议放在 main() 开头。

W55MH32 优先级寄存器仅用高 4 位,可分为主(抢占)优先级和子优先级。抢占优先级高的中断可以打断低的正在执行的中断(嵌套);抢占相同时比较子优先级;两者相同时比较硬件中断编号(编号小的优先)。

分组宏主优先级子优先级说明
NVIC_PriorityGroup_00 位0-15无抢占,全部子优先级
NVIC_PriorityGroup_10-10-71 位抢占 + 3 位子
NVIC_PriorityGroup_20-30-32 位抢占 + 2 位子(常用)
NVIC_PriorityGroup_30-70-13 位抢占 + 1 位子
NVIC_PriorityGroup_40-1504 位抢占,无子优先级
/* 中断优先级分组:2位抢占 + 2位子优先级(整个程序只调用一次) */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

全程序只调用一次

优先级分组一旦设定,所有外设的中断优先级都基于此分组解读。重复调用会覆盖之前的设置。建议放在 main() 开头,不要写进各外设的配置函数中。

步骤2:EXTI 与 NVIC 初始化

函数:EXTI_NVIC_Configuration() — 配置 PG6 为 EXTI6 中断源,下降沿触发,并设置 NVIC 优先级。

中断编程三要点中的前两步在此完成:①使能外设中断(EXTI_LineCmd=ENABLE);②初始化 NVIC_InitTypeDef 结构体,配置中断源、抢占优先级、子优先级并使能。

void EXTI_NVIC_Configuration(void)
{
    GPIO_InitTypeDef    GPIO_InitStructure;
    EXTI_InitTypeDef    EXTI_InitStructure;
    NVIC_InitTypeDef    NVIC_InitStructure;

    /* 开启 GPIOG 和 AFIO 时钟(EXTI 需要 AFIO 时钟) */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG | RCC_APB2Periph_AFIO, ENABLE);

    /* PG6: 上拉输入,接按键 */
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOG, &GPIO_InitStructure);

    /* EXTI6: PG6 下降沿触发 */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOG, GPIO_PinSource6);
    EXTI_InitStructure.EXTI_Line    = EXTI_Line6;
    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    /* NVIC: EXTI9_5 中断,抢占优先级1,子优先级0 */
    NVIC_InitStructure.NVIC_IRQChannel                   = EXTI9_5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

NVIC_InitTypeDef 四个成员:NVIC_IRQChannel 指定中断源(参考 w55mh32.h 中的 IRQn_Type 枚举);PreemptionPrioritySubPriority 的取值范围由步骤1的分组决定;Cmd 使能或失能该中断。

如果你想自定义

  • 改用其他 GPIO 触发 → 修改 GPIO_EXTILineConfig() 的端口和引脚源,以及 EXTI_Line 编号
  • 改为上升沿触发 → 将 EXTI_Trigger_Falling 改为 EXTI_Trigger_Rising,或 EXTI_Trigger_Rising_Falling 双沿触发

步骤3:中断服务函数

函数:EXTI9_5_IRQHandler() — 中断编程三要点第三步。函数名必须与启动文件 startup_w55mh32.s 中预定义的一致,否则中断向量表找不到入口。

void EXTI9_5_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line6) != RESET)
    {
        /* 翻转 PD14 LED */
        GPIO_WriteBit(GPIOD, GPIO_Pin_14,
                      (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOD, GPIO_Pin_14)));

        /* 清除中断标志位,否则会不断重入 */
        EXTI_ClearITPendingBit(EXTI_Line6);
    }
}

ISR 内必须先检查中断标志位(EXTI_GetITStatus),处理完后清除标志位(EXTI_ClearITPendingBit)。不清除会导致中断不断重入,程序卡死在 ISR 中。

函数名不可写错

中断服务函数名必须与启动文件中一致(如 EXTI9_5_IRQHandlerUSART1_IRQHandler)。写错不会编译报错,但中断会跳转到启动文件中的空函数并无限循环。

步骤4:主函数逻辑

函数:main() — 先设置优先级分组(全程序一次)→ 初始化 UART/LED → 配置 EXTI+NVIC → 进入主循环等待中断。

int main(void)
{
    GPIO_InitTypeDef    GPIO_InitStructure;
    RCC_ClocksTypeDef   clocks;

    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("\n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, "
           "PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000,
           (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000,
           (float)clocks.PCLK2_Frequency / 1000000,
           (float)clocks.ADCCLK_Frequency / 1000000);
    printf("NVIC Test: EXTI6 (PG6 button) -> LED (PD14) toggle.\n");

    /* 1. 中断优先级分组:整个程序只调用一次 */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    /* 2. PD14: LED 推挽输出 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);
    GPIO_SetBits(GPIOD, GPIO_Pin_14);  /* 初始熄灭 */

    /* 3. 配置 EXTI6 + NVIC */
    EXTI_NVIC_Configuration();

    printf("Press KEY (PG6) to trigger EXTI6 interrupt.\n");

    while (1);  /* 主循环空转,等待中断 */
}

主循环为空,CPU 全部工作交给中断处理。按下按键时触发 EXTI6 中断,ISR 翻转 LED。这种"事件驱动"模式让 CPU 在无事件时可进入低功耗,无需轮询。

如果你想自定义

  • 增加超时去抖 → 在 ISR 中加 delay_ms(20) 软件消抖,或改用定时器中断去抖更可靠
  • 多中断共存 → 为每个外设分别调用 NVIC_Init(),根据步骤1的分组合理分配抢占/子优先级

步骤5:printf 重定向

重写 fputc() 将 printf 输出重定向到 USART1,并自动在 \n 前补 \r 确保串口工具正确换行。

int fputc(int c, FILE *f)
{
    if (c == '\n')
        SER_PutChar('\r');         // 补回车,确保串口工具正确换行
    return (SER_PutChar(c));
}

int SER_PutChar(int ch)
{
    while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
    USART_SendData(USART_TEST, (uint8_t)ch);
    return ch;
}

运行验证

打开 WIZ UartTool V1.0,选择开发板对应 COM 口,波特率 115200 / 8N1,打开串口。按 RESET 启动后,预期串口输出时钟信息和提示语。每按一次 PG6 按键,PD14 LED 翻转一次状态。

SYSCLK: 72.0Mhz, HCLK: 72.0Mhz, PCLK1: 36.0Mhz, PCLK2: 72.0Mhz, ADCCLK: 36.0Mhz
NVIC Test: EXTI6 (PG6 button) -> LED (PD14) toggle.
Press KEY (PG6) to trigger EXTI6 interrupt.

按下按键时 LED 状态翻转,松开按键无变化(下降沿触发)。若 LED 无反应,参考下方常见问题排查。

常见问题

Q: 按键后 LED 不翻转 / 进不了中断

按顺序检查:①NVIC_IRQChannelCmd 是否为 ENABLE;②EXTI_LineCmd 是否为 ENABLE;③GPIO_EXTILineConfig 端口和引脚源是否匹配;④AFIO 时钟是否开启(RCC_APB2Periph_AFIO)。

Q: 中断服务函数名写对了但编译后不执行

检查函数名是否与启动文件 startup_w55mh32.s 中的向量表完全一致(区分大小写,如 EXTI9_5_IRQHandler)。写错不会编译报错,但中断会跳转到空函数并死循环。可在 ISR 首行加 printf 验证是否进入。

Q: 中断不断重入 / 程序卡死在 ISR

ISR 中未清除中断标志位。EXTI 必须调用 EXTI_ClearITPendingBit(EXTI_Line6);其他外设类似(如 USART 调 USART_ClearITPendingBit,Timer 调 TIM_ClearITPendingBit)。

Q: 抢占优先级无效 / 中断不能嵌套

优先级分组设置错误。若设为 NVIC_PriorityGroup_0,则没有抢占优先级(0 位),所有中断不能互相抢占。改用 NVIC_PriorityGroup_2(2 位抢占 + 2 位子)即可支持嵌套。注意 NVIC_PriorityGroupConfig 全程序只能调用一次。

Q: 优先级数值越大优先级越高吗?

不是。优先级数值越小优先级越高,0 是最高优先级。抢占优先级和子优先级的取值范围由分组决定(如 Group_2 下两者均为 0-3)。设置时核心中断(如 SysTick)建议设较小数值(高优先级),普通外设中断设较大数值。

总结

本文基于 W55MH32 的 EXTI6 外部中断演示了 NVIC 中断编程完整流程:

  • 优先级分组:通过 NVIC_PriorityGroupConfig() 设置抢占/子优先级位数划分,全程序只调用一次,建议放 main() 开头
  • 中断三步法:①使能外设中断(EXTI_LineCmd)→ ②初始化 NVIC(NVIC_IRQChannel + 优先级 + Cmd)→ ③编写中断服务函数(函数名与启动文件一致,处理完清标志位)
  • 关键细节:优先级数值越小优先级越高;ISR 中必须清除中断标志位避免重入;AFIO 时钟必须开启才能配置 EXTI
  • 本例以 EXTI6+LED 为演示,其他外设(USART/SPI/Timer/DMA)的中断配置流程完全相同,仅中断源和标志位 API 不同

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家