全部例程

W6300使用TFTP协议读取文件内容

W6300

更新于 2026年10月10日

本篇总结

在 W6300 上实现 TFTP 客户端,从 TFTP 服务器读取文本文件内容。

🔑 MH2457、QSPI、UART调试、硬件TOE引擎、TFTP、DHCP

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

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W6300-EVB-M4 开发板

本示例演示如何在 W6300 上实现 TFTP(简单文件传输协议)功能。通过实战例程,为大家讲解如何使用 TFTP 客户端模式向服务器获取文本文件。该例程用到的其他网络协议,例如 DHCP,请参考相关章节。

前置工具准备

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

  • Tftpd32:TFTP 服务器软件,用于提供文件下载服务:点击下载
  • WIZ UartTool V1.0:串口调试工具:点击下载

TFTP 协议简介

TFTP(Trivial File Transfer Protocol)协议是一种轻量级的文件传输协议,它通常用于需要快速、简单的文件交换场景,尤其是在网络设备启动和配置过程中。与 FTP 不同,TFTP 设计得非常简单,仅提供基本的文件读写功能,并且使用 UDP 作为传输层协议(端口 69),因而不具备 TCP 的复杂性和重传机制。

操作码名称说明
1RRQ(读请求)用于请求读取服务器上的文件
2WRQ(写请求)用于请求向服务器上写入文件
3DATA(数据)用于传输文件数据
4ACK(回应)用于确认接收到的数据块
5ERROR(错误)用于报告传输过程中发生的错误
6OACK(选项回应)扩展操作码,回应选项协商

W6300 的优势

W6300 SDK 内置了 TFTP 客户端库,报文组包/解包、超时重传、状态管理全部由库函数处理。你只需要:注册超时定时器 → 调用 TFTP_init() → 发送读请求 TFTP_read_request() → 主循环调用 TFTP_run() 驱动状态机。

硬件连接与拓扑

请注意:

PC 和 W6300 需处于同一网段。Tftpd32 的服务器地址必须与程序中 TFTP_SERVER_IP 一致,且 TFTP 目录下必须有程序中读取的文件。

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口
  3. 确保 W6300 与 MH2457 之间的 QSPI 连线正确(CSn、SCK、DATA0~DATA3)
┌──────────────────────────────────┐
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬───────────────────┬──────────┘
│ │
┌────┴──────┐ ┌────┴──────────────┐
│ W6300 │ │ PC │
│(TFTP Client)│ │ (Tftpd32 Server) │
│ UDP :69 │ │ 192.168.1.20 │
└────┬──────┘ └───────────────────┘
│ QSPI ↑
┌────┴──────┐ DEBUG USB
│ MH2457 │──────→ WIZ UartTool
│ MCU │
└───────────┘
网络拓扑:PC 运行 Tftpd32 作为 TFTP 服务器(192.168.1.20),W6300 作为 TFTP 客户端读取文件,MH2457 通过 QSPI 驱动 W6300

工程结构

工程核心文件如下:

Project/
├── main.c                    ← 系统入口:串口、GPIO、QSPI 初始化 + user_run()
├── USER/
│   ├── user_main.c           ← 网络地址定义 + user_run() 主流程
│   ├── tftp_client.c/h       ← TFTP 客户端封装(do_tftp_client / TFTP_SERVER_IP)
│   ├── wiz_platform.c/h      ← 平台适配:debug_usart_init、wizchip_gpio_init、hspi_init
│   └── wiz_interface.c/h     ← W6300 驱动接口:wizchip_initialize、network_init、PHY 检测、软件定时器
└── Internet/
    └── tftp.c                ← TFTP 协议实现(TFTP_init / TFTP_read_request / TFTP_run)

本例程的核心:通过 TFTP_init 初始化、TFTP_read_request 发送读请求、TFTP_run 循环处理收发与超时重传,从服务器读取文本文件内容。

步骤1:注册TFTP超时函数

文件:USER/user_main.c — TFTP 协议的超时重传需要定时器支持。W6300 平台通过软件定时器链表注册回调,每 1 秒调用一次 tftp_timeout_handler()。

wiz_add_timer(tftp_timeout_handler, 1000);
do_tftp_client(SOCKET_ID, ethernet_buf);
wiz_delete_timer(tftp_timeout_handler);

wiz_add_timer() 把回调注册到软件定时器链表,由 1ms 系统定时中断驱动 wiz_timer_handler(),累计到 1000ms 后执行 tftp_timeout_handler()。tftp_timeout_handler() 将 tftp_time_cnt 加 1,为 TFTP 的超时重传提供秒级时基。TFTP 传输完成后,通过 wiz_delete_timer() 注销回调。

步骤2:在主函数中调用TFTP读取函数

文件:USER/tftp_client.c — do_tftp_client() 函数是 TFTP 客户端的入口,首先调用 TFTP_init 初始化,然后发送读请求,最后循环调用 TFTP_run 处理传输。

#define TFTP_RECEIVE_WORD_COUNT ((MAX_MTU_SIZE + sizeof(uint32_t) - 1U) / sizeof(uint32_t))

static uint32_t s_tftp_receive_storage[TFTP_RECEIVE_WORD_COUNT];

void do_tftp_client(uint8_t sn, uint8_t *buff)
{
    uint32_t tftp_server_ip = inet_addr((uint8_t *)TFTP_SERVER_IP);
    int tftp_state;

    /* W5500-compatible signature; keep the proven W6300-aligned receive storage. */
    (void)buff;

    dbg_level = 0;
    TFTP_init(sn, (uint8_t *)s_tftp_receive_storage);

    printf("tftp server ip: %s, file name: %s\r\n", TFTP_SERVER_IP, TFTP_SERVER_FILE_NAME);
    printf("send request\r\n");
    TFTP_read_request(tftp_server_ip, (uint8_t *)TFTP_SERVER_FILE_NAME);

    while (1)
    {
        tftp_state = TFTP_run();

        if (tftp_state == TFTP_SUCCESS)
        {
            printf("tftp read success, file name: %s\r\n", TFTP_SERVER_FILE_NAME);
            printf("\r\n========== File Content Start ==========\r\n");
            printf("%s", (char *)s_tftp_receive_storage + 4);
            printf("\r\n========== File Content End ==========\r\n");
            break;
        }
        else if (tftp_state == TFTP_FAIL)
        {
            printf("tftp read fail, file name: %s\r\n", TFTP_SERVER_FILE_NAME);
            break;
        }

        wiz_user_delay_ms(1U);
    }

    TFTP_exit();
}

进入 do_tftp_client() 函数后开始进行 TFTP 客户端处理,首先会调用 TFTP_init() 函数对 TFTP 客户端进行初始化,步骤如下:

void TFTP_init(uint8_t socket, uint8_t *buf)
{
    init_tftp();

    g_tftp_socket  = open_tftp_socket(socket);
    g_tftp_rcv_buf = buf;
}

static void init_tftp(void)
{
    g_filename[0] = 0;

    set_server_ip(0);
    set_server_port(0);
    set_local_port(0);

    set_tftp_state(STATE_NONE);
    set_block_number(0);

    // timeout flag
    g_resend_flag  = 0;
    tftp_retry_cnt = tftp_time_cnt = 0;
    g_progress_state = TFTP_PROGRESS;
}

初始化完成后,打印 TFTP 服务器的 IP 地址和要读取的文件名,然后调用 TFTP_read_request() 函数向服务器发送读取请求。

void TFTP_read_request(uint32_t server_ip, uint8_t *filename)
{
    set_server_ip(server_ip);

    g_progress_state = TFTP_PROGRESS;
    send_tftp_rrq(filename, (uint8_t *)TRANS_BINARY, &default_tftp_opt, 1);
}

步骤3:运行TFTP协议并处理结果

文件:Internet/tftp.c — 调用 TFTP_run() 函数处理 TFTP 协议操作,依据其返回的 g_progress_state 判断结果:若为 TFTP_SUCCESS 则打印文件内容并退出循环,若为 TFTP_FAIL 则打印失败信息并退出循环。

int TFTP_run(void)
{
    int32_t len;
    uint16_t from_port;
    uint32_t from_ip;

    /* Timeout Process */
    if (g_resend_flag)
    {
        if (tftp_time_cnt >= g_timeout)
        {
            switch (get_tftp_state())
            {
            case STATE_WRQ:
                break;

            case STATE_RRQ:
                send_tftp_rrq(g_filename, (uint8_t *)TRANS_BINARY, &default_tftp_opt, 1);
                break;

            case STATE_OACK:
            case STATE_DATA:
                send_tftp_ack(get_block_number());
                break;

            case STATE_ACK:
                break;

            default:
                break;
            }

            tftp_time_cnt = 0;
            tftp_retry_cnt++;

            if (tftp_retry_cnt >= 5)
            {
                init_tftp();
                g_progress_state = TFTP_FAIL;
            }
        }
    }

    /* Receive Packet Process */
    len = recv_udp_packet(g_tftp_socket, g_tftp_rcv_buf, MAX_MTU_SIZE, &from_ip, &from_port);
    if (len < 0)
    {
        return g_progress_state;
    }

    recv_tftp_packet(g_tftp_rcv_buf, (uint32_t)len, from_ip, from_port);

    return g_progress_state;
}

在处理接收到的 TFTP 数据包时,首先调用 recv_tftp_packet() 函数。进入该函数后,第一步验证接收到的数据包的源 IP 地址,只有当它与服务器 IP 地址一致时才继续处理。接着从数据包中获取操作码(opcode),根据操作码调用相应的处理函数。

static void recv_tftp_packet(uint8_t *packet, uint32_t packet_len, uint32_t from_ip, uint16_t from_port)
{
    uint16_t opcode;

    if ((packet == NULL) || (packet_len < 2U))
    {
        return;
    }

    /* Verify Server IP */
    if (from_ip != get_server_ip())
    {
        return;
    }

    opcode = tftp_read_be16(packet);

    /* Set Server Port */
    if ((get_tftp_state() == STATE_WRQ) || (get_tftp_state() == STATE_RRQ))
    {
        set_server_port(from_port);
    }

    switch (opcode)
    {
    case TFTP_RRQ:
        recv_tftp_rrq(packet, packet_len);
        break;
    case TFTP_WRQ:
        recv_tftp_wrq(packet, packet_len);
        break;
    case TFTP_DATA:
        recv_tftp_data(packet, packet_len);
        break;
    case TFTP_ACK:
        recv_tftp_ack(packet, packet_len);
        break;
    case TFTP_OACK:
        recv_tftp_oack(packet, packet_len);
        break;
    case TFTP_ERROR:
        recv_tftp_error(packet, packet_len);
        break;

    default:
        break;
    }
}

数据包操作码分发逻辑:TFTP_DATA 由 recv_tftp_data() 处理,解析块号并回复 ACK,若本次 DATA 长度小于 512 字节则判定传输完成;TFTP_ERROR 由 recv_tftp_error() 处理,置 g_progress_state = TFTP_FAIL。最后返回 g_progress_state,表示当前 TFTP 操作的状态。

超时重传机制

TFTP 基于 UDP 不保证可靠传输,需在应用层实现超时重传。当 g_resend_flag 置位且计时到达 g_timeout 时,根据当前状态重发对应报文(RRQ 或 ACK)。重试次数累计达到 5 次仍未成功,则调用 init_tftp() 复位并置 TFTP_FAIL。W6300 平台的 tftp_timeout_handler 通过软件定时器链表每秒调用一次。

运行验证

烧录例程运行后,首先进行了 PHY 链路检测,然后是 DHCP 获取网络地址结果,最后打印服务器 IP 和文本名称,读取文本内容。

MH2457 W6300 QSPI example start
W6300 TFTP Client example
PHY link
The current Mbtis speed : 100Mbps
The current Duplex Mode : Full-Duplex
DHCP running
DHCP success!
====================================================================================================
 W6300 network configuration : DHCP

 MAC         : 00:08:DC:12:34:56
 IP          : 192.168.1.8
 Subnet Mask : 255.255.255.0
 Gateway     : 192.168.1.1
 DNS         : 192.168.1.1
====================================================================================================

tftp server ip: 192.168.1.20, file name: tftp_test_file.txt
send request
tftp read success, file name: tftp_test_file.txt

========== File Content Start ==========
HelloWIZNET
========== File Content End ==========

请注意

测试实例需要 PC 端和 W6300 处于同一网段。首先打开 Tftpd32,选择 TFTP 目录和服务器地址。服务器地址必须与程序对应,并且 TFTP 目录下必须有程序中读取的文件。

Tftpd32 配置 — Current Directory: D:\Tftpd32,Server interface: 192.168.1.20
WIZ UartTool 串口输出 — TFTP 读取 tftp_test_file.txt 成功(内容 "HelloWIZNET")

常见问题

Q: TFTP为什么使用UDP而不是TCP

TFTP设计目标是轻量级、简单,UDP无连接开销小、实现容易,适合资源有限的嵌入式设备和启动加载场景。可靠性由TFTP应用层的ACK确认和超时重传机制保证,每个DATA包必须收到对应ACK才发送下一个,因此不需要TCP的复杂状态机。

Q: 如何判断文件传输是否结束

TFTP规定每个DATA报文最多传输512字节数据。当服务器发送的DATA包数据长度小于512字节时,即表示这是文件的最后一个数据块,传输完成。客户端收到该包后回复ACK,传输正常终止。

Q: 服务器端口为什么不是固定的69

TFTP客户端初始向服务器的69端口发送RRQ/WRQ请求。服务器收到请求后,会随机选择一个新的临时端口进行后续数据传输,并通过第一个响应包(OACK或DATA)告知客户端该端口。之后客户端与服务器使用该临时端口通信,避免与其它客户端的请求冲突。

Q: 为什么接收缓存要预留4字节偏移

打印文件内容时使用 (char *)s_tftp_receive_storage + 4,是为了跳过 TFTP DATA 报文头(2 字节操作码 + 2 字节块号)。TFTP_DATA 报文的第 5 字节起才是真正的文件内容,因此读取缓存时要偏移 4 字节。

Q: TFTP读取失败如何排查

首先确认PC与W6300处于同一网段且服务器IP与程序中TFTP_SERVER_IP一致;其次确认tftpd32已启动并选择了正确的TFTP目录,且目录下存在TFTP_SERVER_FILE_NAME指定的文件;最后检查防火墙是否放行TFTP(UDP 69及动态端口)。若仍失败,可通过tftp_state返回的TFTP_FAIL及重试计数判断是否为超时或丢包问题。

总结

  • 协议定位:TFTP是基于UDP、端口69的轻量级文件传输协议,仅提供基本读写功能,适合固件升级与配置文件传输
  • 工作流程:客户端发RRQ请求→服务器切换端口→DATA/ACK交替传输→数据小于512字节结束
  • 核心函数:TFTP_init初始化、TFTP_read_request发送读请求、TFTP_run循环处理收发与超时、recv_tftp_packet按操作码分发
  • 可靠性机制:应用层ACK确认+超时重传,重试5次失败则置TFTP_FAIL;W6300平台通过软件定时器链表提供秒级时基
  • 结果判断:依据tftp_state为TFTP_SUCCESS或TFTP_FAIL决定后续处理

下载本章例程

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

开发环境: Keil MDK5

Andy

W5500 技术支持专家