全部例程

中断示例

W55MH32

更新于 2026年10月17日

本篇总结

在 W55MH32 上使用 TOE 中断功能实现 TCP 回环数据测试,通过 PD8 外部中断处理连接、断开、收发等事件。

🔑 KEIL MDK、UART调试、硬件TOE引擎、TCP、外部中断

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

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

本示例演示如何在 W55MH32 上使用 TOE 中断功能,并通过实战例程,为大家讲解如何通过中断进行回环数据测试。该例程用到的其他网络协议(例如 DHCP)以及 W55MH32 的初始化过程,请参考 Network Install 章节,这里不再赘述。

前置工具准备

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

TOE 中断简介

TOE(TCP/IP Offload Engine)中断机制通过 PD8 引脚在连接建立、数据接收、发送完成等事件发生时主动通知处理器,相比轮询方式具有实时性强、事件驱动、提高系统效率的特点。

  • 网络唤醒:Magic Packet 触发中断唤醒设备
  • 网络配置:数据接收后按需读取,免持续轮询
  • 错误处理:IP 冲突、网络不可达时立即响应
  • 双类中断源:通用中断(IP 冲突、Magic Packet 等)与 Socket 中断(发送完成、接收数据、建立/关闭连接等)

使用中断接收数据的基本流程

初始化中断引脚(PD8 配置为外部下降沿触发)→ 开启对应 Socket 中断功能 → 设置 Socket 中断源 → 编写中断处理函数在接收到数据时进行处理。

硬件连接与拓扑

请注意:

测试需要 PC 端和 W55MH32 处于同一网段。

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口
┌─────────────────────────────────┐
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬──────────────────┬───────────┘
│ │
┌────┴──────┐ ┌────┴──────┐
│ W55MH32 │ │ PC │
│ TCP:8080 │ │SocketTester│
│ IP: DHCP │ │ IP: DHCP │
└───────────┘ └───────────┘
↑ ↑
DEBUG USB ──→ WIZ UartTool
网络拓扑:开发板与 PC 在同一局域网内,通过路由器互通

工程结构

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

Interrupt/
├── Project/
│   └── Project.uvprojx       ← Keil 工程文件
└── USER/
    ├── main.c                ← 初始化 + TCP 回环主循环
    ├── interrupt.c           ← 中断引脚初始化 + 中断处理 + 回环状态机
    ├── wiz_interface.c       ← DHCP / PHY 检测 / 网络初始化
    └── bsp_uart.c            ← 调试串口 + printf 重定向

步骤1:初始化中断引脚

文件:USER/interrupt.c — 配置 PD8 为上拉输入,映射到 EXTI8 外部中断线,设置为下降沿触发;同时配置 NVIC 通道 EXTI9_5_IRQn 的抢占优先级与子优先级。

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

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource8);

    NVIC_InitStructure.NVIC_IRQChannel                   = EXTI9_5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    EXTI_InitStructure.EXTI_Line    = EXTI_Line8;
    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}

在成功触发 TOE 中断后,会调用 wizchip_ISR() 函数,在这里会记录中断标志位并清空中断标志。

步骤2:开启中断功能

文件:USER/main.c — 通过 setSIMR() 开启 Socket0 的全局中断屏蔽,并通过 setSn_IMR() 开启 Socket0 的所有中断功能。

setSIMR(0x01);              // enable socket0 interrupt
setSn_IMR(SOCKET_ID, 0x0f); // enable all interrupt functions of socket 0.

中断屏蔽寄存器说明

setSIMR(0x01) 开启 Socket 0 的全局中断(bit0 = Socket 0);setSn_IMR(SOCKET_ID, 0x0f) 开启 Socket 0 的低 4 位中断(CON/DISCON/RECV/SENDOK)。

步骤3:编写中断处理函数

文件:USER/interrupt.c — 在 TOE 成功触发中断后,进入 EXTI9_5_IRQHandler(),进而调用 wizchip_ISR() 函数,读取 SIR 寄存器判断中断来源,记录中断标志位到 I_STATUS 数组,并清空对应 Socket 的中断标志。

void EXTI9_5_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line8) == SET)
    {
        if (GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_8) == RESET)
        {
            wizchip_ISR();
        }
    }
    EXTI_ClearITPendingBit(EXTI_Line8);
}

/**
  * @brief   Determine the interrupt type and store the value in I STATUS
  * @param   none
  * @return  none
  */
void wizchip_ISR(void)
{
    uint8_t SIR_val = 0;
    uint8_t tmp, sn;
    SIR_val = getSIR();
    if (SIR_val != 0xff)
    {
        setSIMR(0x00);
        for (sn = 0; sn < _WIZCHIP_SOCK_NUM_; sn++)
        {
            tmp = 0;
            if (SIR_val & IR_SOCK(sn))
            {
                tmp           = getSn_IR(sn);
                I_STATUS[sn] |= tmp;
                tmp          &= 0x0f;
                setSn_IR(sn, tmp);
            }
        }
        setSIMR(0xff);
    }
}

先记录再清空

先读取 Sn_IR 寄存器保存到 I_STATUS 数组,再用低 4 位写回清空标志。这样主循环可依据 I_STATUS 判断中断类型并执行相应处理,避免在中断服务程序中处理耗时操作,保证中断响应的实时性。

步骤4:主循环中运行 TCP 回环服务器

文件:USER/interrupt.c | 函数:loopback_tcps_interrupt() — 进入函数后,首先检查 Socket 状态。如果 Socket 处于关闭状态,则启动一个 TCP 服务器。接着,根据记录的中断标志位判断是否触发了中断,并根据中断类型进行相应的处理:连接建立、断开、接收数据、发送完成。

void loopback_tcps_interrupt(uint8_t sn, uint8_t *buf, uint16_t port)
{
    uint16_t len = 0;
    uint8_t  destip[4];
    uint16_t destport;

    if (I_STATUS[sn] == SOCK_CLOSED)
    {
        if (!ch_status[sn])
        {
#ifdef INTERRUPT_DEBUG
            printf("%d:TCP server start\r\n", sn);
#endif
            ch_status[sn] = ready_status;

            if (socket(sn, Sn_MR_TCP, port, 0x00) != sn)
            {
                ch_status[sn] = closed_status;
            }
            else
            {
#ifdef INTERRUPT_DEBUG
                printf("%d:Socket opened\r\n", sn);
#endif
                listen(sn);
#ifdef INTERRUPT_DEBUG
                printf("%d:Listen, TCP server loopback, port [%d]\r\n", sn, port);
#endif
            }
        }
    }
    if (I_STATUS[sn] & Sn_IR_CON)
    {
        getSn_DIPR(sn, destip);
        destport = getSn_DPORT(sn);
#ifdef INTERRUPT_DEBUG
        printf("%d:Connected - %d.%d.%d.%d : %d\r\n", sn, destip[0], destip[1], destip[2], destip[3], destport);
#endif
        ch_status[sn]  = connected_status;
        I_STATUS[sn]  &= ~(Sn_IR_CON);
    }

    if (I_STATUS[sn] & Sn_IR_DISCON)
    {
        printf("%d:Socket disconnected\r\n", sn);
        if ((getSn_RX_RSR(sn)) > 0)
        {
            len = getSn_RX_RSR(sn);

            if (len > ETHERNET_BUF_MAX_SIZE)
            {
                len = ETHERNET_BUF_MAX_SIZE;
            }
            recv(sn, buf, len);
            buf[len] = 0x00;
            printf("%d:recv data:%s\r\n", sn, buf);
            I_STATUS[sn] &= ~(Sn_IR_RECV);
            send(sn, buf, len);
        }
        disconnect(sn);
        ch_status[sn]  = closed_status;
        I_STATUS[sn]  &= ~(Sn_IR_DISCON);
    }

    if (I_STATUS[sn] & Sn_IR_RECV)
    {
        setIMR(0x00);
        I_STATUS[sn] &= ~(Sn_IR_RECV);
        setIMR(0xff);
        if ((getSn_RX_RSR(sn)) > 0)
        {
            len = getSn_RX_RSR(sn);

            if (len > ETHERNET_BUF_MAX_SIZE)
            {
                len = ETHERNET_BUF_MAX_SIZE;
            }
            len      = recv(sn, buf, len);
            buf[len] = 0x00;
            printf("%d:recv data:%s\r\n", sn, buf);
            send(sn, buf, len);
        }
    }

    if (I_STATUS[sn] & Sn_IR_SENDOK)
    {
        I_STATUS[sn] &= ~(Sn_IR_SENDOK);
    }
}

中断事件处理逻辑

函数根据 I_STATUS[sn] 中的中断标志位依次处理:SOCK_CLOSED 启动 TCP 服务器、Sn_IR_CON 处理连接建立、Sn_IR_DISCON 处理断开(先接收残留数据再断开)、Sn_IR_RECV 接收并回传数据、Sn_IR_SENDOK 清除发送完成标志。

运行验证

烧录例程运行后,首先进行 PHY 链路检测,然后打印网络地址信息,接着启动 TCP 中断回环服务器监听 8080 端口。打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1),典型输出如下:

W55MH32 TCP Server Interrupt example
PHY link
The current Mbtis speed : 100Mbps
The current Duplex Mode : Full-Duplex
DHCP running
DHCP success!
====================================================================================================
 W55MH32 network configuration : DHCP

 MAC         : 00:08:DC:00:0E:9A
 IP          : 192.168.1.6
 Subnet Mask : 255.255.255.0
 Gateway     : 192.168.1.1
 DNS         : 192.168.1.1
====================================================================================================

0:TCP server start
0:Socket opened
0:Listen, TCP server loopback, port [8080]

上面这几行是启动后立即打印的,表示 TCP 中断回环服务器已在 8080 端口监听。接下来打开 SocketTester,设为 TCP Client 模式,连接 W55MH32 的 IP 和 8080 端口,发送数据,此时串口会继续打印:

0:Connected - 192.168.1.12 : 64776
0:recv data:Hello W55MH32!

请注意

测试实例需要 PC 端和 W55MH32 处于同一网段。使用 SocketTester 作为 TCP Client 连接 W55MH32 的 8080 端口,发送数据后 W55MH32 通过中断接收并回传。连接建立、收到数据、断开连接的日志只在客户端实际连接交互时才会打印。

WIZ UartTool 串口输出(PHY + DHCP + TCP 中断接收)+ SocketTester TCP 客户端 — 中断回环测试

常见问题

Q: 中断方式与轮询方式有什么区别

  • 轮询方式:处理器需要不断读取寄存器状态,占用大量 CPU 资源。
  • 中断方式:由事件驱动,处理器平时运行主程序,事件发生时通过 PD8 引脚通知处理器立即响应,显著提高系统响应速度与资源利用率。

Q: 为什么使用 PD8 引脚作为中断引脚

W55MH32 的 TOE 中断输出固定由 PD8 引脚输出。当 TOE 内部发生中断事件时,会拉低 PD8 引脚,因此处理器需将 PD8 配置为外部中断输入并设置为下降沿触发(EXTI_Trigger_Falling),对应 EXTI9_5_IRQn 中断通道。

Q: 中断标志位为什么要先记录再清空

在 wizchip_ISR() 中先读取 Sn_IR 寄存器保存到 I_STATUS 数组,再用低 4 位写回清空标志。这样主循环可依据 I_STATUS 判断中断类型并执行相应处理,避免在中断服务程序中处理耗时操作,保证中断响应的实时性。

Q: 断开连接中断触发后为什么要先接收数据

当收到 Sn_IR_DISCON(断开连接)中断时,可能缓冲区中还有未读取的数据。因此先检查 getSn_RX_RSR(sn) 是否大于 0,若有数据则先接收并回传,再调用 disconnect() 关闭连接,确保数据不丢失。

总结

  • 中断机制:TOE 通过 PD8 引脚向处理器输出中断信号,支持实时、事件驱动的处理方式,相比轮询大幅提升系统效率
  • 中断源:通用中断源包括 IP 冲突、目标不可达、PPPoE 关闭、Magic Packet;Socket 中断源包括发送完成、超时、接收数据、关闭连接、建立连接
  • 实现流程:初始化 PD8 外部中断 → 开启 SIMR/Sn_IMR 中断屏蔽 → 编写 EXTI9_5 中断服务程序记录并清空标志 → 主循环依据 I_STATUS 运行 TCP 回环服务器
  • 回环测试:在主循环中根据中断标志处理连接、断开、接收、发送完成事件,实现 TCP 数据回传验证

下载本章例程

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

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

Andy

W5500 技术支持专家