全部例程

W55MH32 IPRAW模式实现ping功能

W55MH32

更新于 2026年9月21日

本篇总结

基于 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 网络诊断功能。

前置工具准备

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

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 处于同一网段。

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 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 校验和,否则报文会被丢弃导致超时

下载本章例程

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

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

Andy

W5500 技术支持专家