本篇总结
在 W55MH32 上使用 TOE 中断功能实现 TCP 回环数据测试,通过 PD8 外部中断处理连接、断开、收发等事件。
🔑 KEIL MDK、UART调试、硬件TOE引擎、TCP、外部中断
硬件平台 :本例程基于 W55MH32L-EVB 开发板运行。
采购推荐 :点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB
本示例演示如何在 W55MH32 上使用 TOE 中断功能,并通过实战例程,为大家讲解如何通过中断进行回环数据测试。该例程用到的其他网络协议(例如 DHCP)以及 W55MH32 的初始化过程,请参考 Network Install 章节,这里不再赘述。
前置工具准备
开始本例程前,请确认以下工具已安装:
WIZ UartTool V1.0 :串口调试工具:点击下载
SocketTester :网络调试工具:点击下载
TOE 中断简介
TOE(TCP/IP Offload Engine)中断机制通过 PD8 引脚 在连接建立、数据接收、发送完成等事件发生时主动通知处理器,相比轮询方式具有实时性强、事件驱动、提高系统效率的特点。
网络唤醒: Magic Packet 触发中断唤醒设备
网络配置: 数据接收后按需读取,免持续轮询
错误处理: IP 冲突、网络不可达时立即响应
双类中断源: 通用中断(IP 冲突、Magic Packet 等)与 Socket 中断(发送完成、接收数据、建立/关闭连接等)
使用中断接收数据的基本流程
初始化中断引脚(PD8 配置为外部下降沿触发)→ 开启对应 Socket 中断功能 → 设置 Socket 中断源 → 编写中断处理函数在接收到数据时进行处理。
硬件连接与拓扑
请注意:
测试需要 PC 端和 W55MH32 处于同一网段。
Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
网线连接开发板 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 数据回传验证