本篇总结
基于 W55MH32 的 IPRAW 模式手动构建 ICMP Echo 报文实现 ping 功能,统计请求、回复与丢包数量。
🔑 KEIL MDK、UART调试、硬件TOE引擎、ICMP、IPRAW
硬件平台:本例程基于 W55MH32L-EVB 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB
本示例演示如何在 W55MH32 芯片上通过 IPRAW 模式实现 ICMP 协议中的 PING 命令,用于网络连通性测试。IPRAW 模式允许直接操作 IP 层数据包,从而支持 ICMP、IGMP 等 IP 层协议。例程通过 do_ping() 控制流程,ping_request() 构造并发送 ICMP 回显请求,ping_reply() 接收并解析回复,完整实现 PING 网络诊断功能。
前置工具准备
开始本例程前,请确认以下工具已安装:
- WIZ UartTool V1.0:串口调试工具:点击下载
IPRAW 模式与 ICMP 协议简介
IPRAW 模式是 W55MH32 TOE 提供的一种网络通信模式,允许用户直接操作 IP 层数据包,支持 ICMP、IGMP 等 IP 层协议的实现。PING 是基于 ICMP 协议的网络连通性测试命令,通过发送"回显请求"(Echo Request)并等待"回显应答"(Echo Reply)来检查目标主机是否可达及网络响应时间,常用于网络连通性测试、故障排查和设备状态监测。
其工作流程为:发送方构建 ICMP 回显请求数据包,经 IP 层封装和数据链路层传输送达目标主机,目标主机返回回显应答,发送方据此计算往返时间与丢包率以评估网络质量。ICMP 报文由类型(Type,8 表示请求/0 表示应答)、代码(Code)、校验和(Checksum)、标识符(Identifier)、序列号(Sequence Number)及数据(Data)等字段构成。
| 特点 | 说明 |
| 简单性 | 基于请求-响应模式,开销小,适合嵌入式设备 |
| 低延迟 | 报文简洁,网络延迟低,适用于实时检测与心跳 |
| 无负载数据 | 仅含简单请求/响应字段,网络负载极小 |
| 容错与重试 | 支持超时与重试机制,保证响应可靠性 |
W55MH32 的优势
W55MH32 的 TOE 引擎原生支持 IPRAW 模式,用户可直接构建 ICMP 报文并通过 IPRAW Socket 发送,无需软件实现完整的 IP 协议栈。IP 头由硬件自动封装,用户只需关注 ICMP 报文内容。
硬件连接与拓扑
请注意:
测试需要 PC 端和 W55MH32 处于同一网段。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口
┌─────────────────────────────────┐
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬──────────────────┬───────────┘
│ │
┌────┴──────┐ ┌────┴──────┐
│ W55MH32 │ │ PC │
│ IPRAW Ping│ │ CMD Ping │
│ IP: DHCP │ │ IP: DHCP │
└───────────┘ └───────────┘
↑ ↑
DEBUG USB ──→ WIZ UartTool
网络拓扑:开发板与 PC 在同一局域网内,通过路由器互通
工程结构
步骤1:主循环调用
文件:USER/main.c。在主循环中调用 do_ping() 函数,传入 Socket 号、目标主机 IP 和 PING 请求次数。
while (1)
{
do_ping(SOCKET_ID, dest_ip, ping_num);
}
do_ping() 函数起到了控制 PING 操作流程的作用,它决定了是否继续进行 PING 操作,以及在操作完成后关闭 Socket 连接。
步骤2:do_ping() 流程控制
步骤3:ping_count() 状态机
文件:USER/ping/ping.c。PING 操作的核心状态机:SOCK_CLOSED 时设置 ICMP 协议并创建 IPRAW Socket,SOCK_IPRAW 时发送 ping 请求并等待回复,超过 1000 次计数(约 5 秒)判定超时,最后统计请求/回复/丢包。
void ping_count(uint8_t s, uint16_t pCount, uint8_t *addr)
{
uint16_t rlen, cnt, i;
cnt = 0;
for (i = 0; i < pCount + 1; i++)
{
if (i != 0)
{
// Output count number
printf("No.%d ", i);
}
switch (getSn_SR(s))
{
case SOCK_CLOSED:
close(s);
// Create Socket
IINCHIP_WRITE(WZTOE_Sn_PROTO(s), IPPROTO_ICMP);
if (socket(s, Sn_MR_IPRAW, 3000, 0) != 0)
{
}
// Check socket register
while (getSn_SR(s) != SOCK_IPRAW);
break;
case SOCK_IPRAW:
ping_request(s, addr);
req++;
while (1)
{
if ((rlen = getSn_RX_RSR(s)) > 0)
{
ping_reply(s, addr, rlen);
rep++;
if (ping_reply_received)
break;
}
if ((cnt > 1000))
{
printf("Request Time out\r\n\r\n");
cnt = 0;
break;
}
else
{
cnt++;
delay_ms(5);
}
}
break;
default:
break;
}
if (req >= pCount)
{
printf("Ping Request = %d, Ping Reply = %d, Lost = %d\r\n", req, rep, req - rep);
}
}
}
状态机关键 API:IINCHIP_WRITE(WZTOE_Sn_PROTO(s), IPPROTO_ICMP) 设置协议为 ICMP,socket(s, Sn_MR_IPRAW, 3000, 0) 创建 IPRAW Socket,getSn_RX_RSR(s) 获取接收缓冲区数据长度。
步骤4:ping_request() 发送请求
文件:USER/ping/ping.c。构造 ICMP 回显请求报文:设置 Type=8(请求)、Code=0、随机 ID 和序列号、填充数据,计算校验和后通过 sendto() 发送。
void ping_request(uint8_t s, uint8_t *addr)
{
uint16_t i;
ping_reply_received = 0;
PingRequest.Type = PING_REQUEST;
PingRequest.Code = CODE_ZERO;
PingRequest.ID = htons(RandomID++);
PingRequest.SeqNum = htons(RandomSeqNum++);
for (i = 0; i < BUF_LEN; i++)
{
PingRequest.Data[i] = (i) % 8;
}
PingRequest.CheckSum = 0;
PingRequest.CheckSum = htons(checksum((uint8_t *)&PingRequest, sizeof(PingRequest)));
if (sendto(s, (uint8_t *)&PingRequest, sizeof(PingRequest), addr, 3000) == 0)
{
printf("Fail to send ping-reply packet\r\n");
}
else
{
printf("Ping:%d.%d.%d.%d\r\n", (addr[0]), (addr[1]), (addr[2]), (addr[3]));
}
}
校验和计算关键:先将 CheckSum 置 0,再对整个 ICMP 报文计算校验和。htons() 用于字节序转换(小端转大端)。
步骤5:ping_reply() 接收回复
文件:USER/ping/ping.c。通过 recvfrom() 接收数据,根据 Type 字段判断是回复(0)还是请求(8),提取并校验信息,校验通过后设置 ping_reply_received 标志位。
void ping_reply(uint8_t s, uint8_t *addr, uint16_t rlen)
{
uint16_t tmp_checksum;
uint16_t len;
uint16_t i;
uint8_t data_buf[136];
uint16_t port = 3000;
PINGMSGR PingReply;
len = recvfrom(s, data_buf, rlen, addr, &port);
if (data_buf[0] == PING_REPLY)
{
PingReply.Type = data_buf[0];
PingReply.Code = data_buf[1];
PingReply.CheckSum = (data_buf[3] << 8) + data_buf[2];
PingReply.ID = (data_buf[5] << 8) + data_buf[4];
PingReply.SeqNum = (data_buf[7] << 8) + data_buf[6];
for (i = 0; i < len - 8; i++)
{
PingReply.Data[i] = data_buf[8 + i];
}
tmp_checksum = ~checksum(data_buf, len);
if (tmp_checksum != 0xffff)
printf("tmp_checksum = %x\r\n", tmp_checksum);
else
{
printf("Reply from %3d.%3d.%3d.%3d ID=%x Byte=%d\r\n\r\n",
(addr[0]), (addr[1]), (addr[2]), (addr[3]),
htons(PingReply.ID), (rlen + 6));
ping_reply_received = 1;
}
}
else if (data_buf[0] == PING_REQUEST)
{
PingReply.Code = data_buf[1];
PingReply.Type = data_buf[2];
PingReply.CheckSum = (data_buf[3] << 8) + data_buf[2];
PingReply.ID = (data_buf[5] << 8) + data_buf[4];
PingReply.SeqNum = (data_buf[7] << 8) + data_buf[6];
for (i = 0; i < len - 8; i++)
{
PingReply.Data[i] = data_buf[8 + i];
}
tmp_checksum = PingReply.CheckSum;
PingReply.CheckSum = 0;
if (tmp_checksum != PingReply.CheckSum)
{
printf(" \n CheckSum is in correct %x shold be %x \n",
(tmp_checksum), htons(PingReply.CheckSum));
}
else
{
}
printf(" Request from %d.%d.%d.%d ID:%x SeqNum:%x :data size %d bytes\r\n",
(addr[0]), (addr[1]), (addr[2]), (addr[3]),
(PingReply.ID), (PingReply.SeqNum), (rlen + 6));
ping_reply_received = 1;
}
else
{
printf(" Unkonwn msg. \n");
}
}
校验和验证关键
接收回复时,通过 ~checksum(data_buf, len) 对整个数据包校验,结果应为 0xffff 表示校验通过。校验通过后设置 ping_reply_received = 1,使 ping_count() 状态机跳出等待循环。
实现要点
IPRAW 模式下使用 socket(s, Sn_MR_IPRAW, 3000, 0) 创建 Socket,并通过 IINCHIP_WRITE(WZTOE_Sn_PROTO(s), IPPROTO_ICMP) 设置协议为 ICMP。校验和计算必须正确,否则目标主机会丢弃报文。
运行验证
烧录例程运行后,首先进行 PHY 链路检测,然后打印设置网络信息,最后对 ping 目标 IP 收到回复。打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1),观察开发板串口输出 PING 请求次数、回复次数及丢包统计信息。
WIZ UartTool 串口输出 — IPRAW Ping 初始化(PHY 链路 + 网络信息 + 首次 Ping 请求)
WIZ UartTool 串口输出 — 4 次 Ping 请求/回复统计(Request=4, Reply=4, Lost=0)
常见问题
Q: IPRAW 模式和 TCP/UDP 模式有什么区别
IPRAW 模式允许用户直接操作 IP 层数据包,可支持 ICMP、IGMP 等网络层协议;而 TCP/UDP 模式工作在传输层,由 TOE 引擎自动处理 TCP/UDP 协议头。PING 基于 ICMP 协议,工作在网络层,因此必须使用 IPRAW 模式才能实现,无法用普通 TCP/UDP Socket 完成。
Q: PING 请求超时(Request Time out)怎么办
首先检查 PC 端与 W55MH32 是否处于同一网段;其次确认目标 IP 地址是否正确;再检查网络物理连接是否正常(网线、PHY 链路状态)。若网络存在防火墙,可能拦截 ICMP 报文导致超时。程序中通过 cnt 计数超过 1000 次后判定为超时,每次延迟 5ms,即约 5 秒无回复则超时。
Q: 校验和计算错误会有什么影响
ICMP 校验和用于校验报文完整性。若校验和计算错误,目标主机会直接丢弃该报文,不会返回回显应答,导致 PING 超时。在 ping_request() 中需先将 CheckSum 置 0 再计算整体校验和;在 ping_reply() 中通过 ~checksum() 对收到的整个数据包进行校验,结果应为 0xffff 表示校验通过。
Q: 为什么需要设置 IPPROTO_ICMP 协议
IPRAW 模式创建 Socket 时,需要通过 IINCHIP_WRITE(WZTOE_Sn_PROTO(s), IPPROTO_ICMP) 指定该 Socket 使用的 IP 层协议类型。ICMP 的协议号为 1,只有正确设置协议号,TOE 引擎才能在发送 IP 数据包时填入正确的协议字段,使目标主机识别为 ICMP 报文并交由 ICMP 协议栈处理。
Q: 如何统计 PING 的丢包率
程序中用 req 记录已发送的 PING 请求次数,用 rep 记录收到的回复次数,丢包数即为 req - rep。当 req 达到设定的请求次数 pCount 时,通过 printf 输出 "Ping Request = %d, Ping Reply = %d, Lost = %d" 统计信息。丢包率 = 丢失数 / 请求总数 x 100%。
总结
本文基于 W55MH32 的 IPRAW 模式手动构建 ICMP 报文实现了 PING 网络诊断功能:
- IPRAW 模式:W55MH32 TOE 提供的网络通信模式,可直接操作 IP 层数据包,支持 ICMP、IGMP 等 IP 层协议
- PING 原理:基于 ICMP 协议发送回显请求并等待回显应答,用于测试网络连通性、检测延迟、排查故障
- 核心函数:do_ping() 控制流程、ping_count() 状态机、ping_request() 发送请求、ping_reply() 解析回复
- 关键 API:socket() 创建 IPRAW Socket、sendto()/recvfrom() 收发数据、IINCHIP_WRITE() 设置协议
- 校验和:必须正确计算 ICMP 校验和,否则报文会被丢弃导致超时