W55MH32使用TFTP协议读取文件内容
更新于 2026年9月15日
本篇总结
在 W55MH32 上实现 TFTP 客户端,从 TFTP 服务器读取文本文件内容。
硬件平台:本例程基于 W55MH32L-EVB 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB
本示例演示如何在 W55MH32 上实现 TFTP(简单文件传输协议)功能。通过实战例程,为大家讲解如何使用 TFTP 客户端模式向服务器获取文本文件。该例程用到的其他网络协议,例如 DHCP,请参考相关章节。
前置工具准备
TFTP 协议简介
TFTP(Trivial File Transfer Protocol)协议是一种轻量级的文件传输协议,它通常用于需要快速、简单的文件交换场景,尤其是在网络设备启动和配置过程中。与 FTP 不同,TFTP 设计得非常简单,仅提供基本的文件读写功能,并且使用 UDP 作为传输层协议(端口 69),因而不具备 TCP 的复杂性和重传机制。
| 操作码 | 名称 | 说明 |
|---|---|---|
| 1 | RRQ(读请求) | 用于请求读取服务器上的文件 |
| 2 | WRQ(写请求) | 用于请求向服务器上写入文件 |
| 3 | DATA(数据) | 用于传输文件数据 |
| 4 | ACK(回应) | 用于确认接收到的数据块 |
| 5 | ERROR(错误) | 用于报告传输过程中发生的错误 |
| 6 | OACK(选项回应) | 扩展操作码,回应选项协商 |
W55MH32 的优势
W55MH32 SDK 内置了 TFTP 客户端库,报文组包/解包、超时重传、状态管理全部由库函数处理。你只需要:注册超时定时器 → 调用 TFTP_init() → 发送读请求 TFTP_read_request() → 主循环调用 TFTP_run() 驱动状态机。
硬件连接与拓扑
请注意:
PC 和 W55MH32 需处于同一网段。Tftpd32 的服务器地址必须与程序中 TFTP_SERVER_IP 一致,且 TFTP 目录下必须有程序中读取的文件。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬───────────────────┬──────────┘
│ │
┌────┴──────┐ ┌────┴──────────────┐
│ W55MH32 │ │ PC │
│(TFTP Client)│ │ (Tftpd32 Server) │
│ UDP :69 │ │ 192.168.1.20 │
└───────────┘ └───────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
工程结构
工程位于 SDK 内 1.SDK\ModuleDemo\WZTOE\TFTP\,核心文件如下:
TFTP/
├── Project/
│ └── Project.uvprojx ← Keil 工程文件
└── USER/
├── main.c ← 硬件初始化 + 网络配置 + 主循环
├── wiz_interface/
│ ├── wiz_interface.c ← PHY 检测 / 网络信息打印 / DHCP
│ └── wiz_interface.h
├── tftp/
│ ├── tftp.c ← TFTP 客户端实现(初始化/请求/运行/解析)
│ ├── tftp.h
│ └── tftp_util.c/h ← TFTP 辅助函数
├── ioLibrary/
│ └── Internet/TFTP/ ← TFTP 协议库
├── tim/
│ └── bsp_tim.c/h ← TIM3 定时器(DHCP 1s + TFTP 超时)
└── uart/
└── bsp_uart.c/h ← 调试串口 + printf 重定向
本例程的核心:通过 TFTP_init 初始化、TFTP_read_request 发送读请求、TFTP_run 循环处理收发与超时重传,从服务器读取文本文件内容。
步骤1:注册TFTP超时函数
文件:main.c — TFTP 协议的超时重传需要定时器支持。我们在 TIM3 的 1ms 中断中累加实现,每 1 秒调用一次 tftp_timeout_handler()。
/**
* @brief 1ms timer IRQ Handler
* @param none
* @return none
*/
void TIM3_IRQHandler(void)
{
static uint32_t tim3_1ms_count = 0;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
tim3_1ms_count++;
if (tim3_1ms_count >= 1000)
{
DHCP_time_handler();
tftp_timeout_handler();
tim3_1ms_count = 0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}步骤2:在主函数中调用TFTP读取函数
文件:tftp.c — do_tftp_client() 函数是 TFTP 客户端的入口,首先调用 TFTP_init 初始化,然后发送读请求,最后循环调用 TFTP_run 处理传输。
#define TFTP_SERVER_IP "192.168.1.20"
#define TFTP_SERVER_FILE_NAME "tftp_test_file.txt"
void do_tftp_client(uint8_t sn, uint8_t *buff)
{
uint32_t tftp_server_ip = inet_addr((uint8_t*)TFTP_SERVER_IP);//TFTP服务器地址
uint8_t tftp_read_file_name[] = TFTP_SERVER_FILE_NAME; //读取文件名
TFTP_init(sn, buff);
while (1)
{
if (tftp_read_flag == 0)
{
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, TFTP_SERVER_FILE_NAME);
tftp_read_flag = 1;
}
else
{
tftp_state = TFTP_run();
if (tftp_state == TFTP_SUCCESS)
{
printf("tftp read success, file name: %s\r\n", tftp_read_file_name);
while (1)
{
}
}
else if (tftp_state == TFTP_FAIL)
{
printf("tftp read fail, file name: %s\r\n", tftp_read_file_name);
while (1)
{
}
}
}
}
}进入 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_read_flag 为 0 时,表示尚未发送读取请求。此时,打印 TFTP 服务器的 IP 地址和要读取的文件名,然后调用 TFTP_read_request() 函数向服务器发送读取请求。发送请求后,将 tftp_read_flag 设置为 1,表示已发送请求。
void TFTP_read_request(uint32_t server_ip, uint8_t *filename)
{
set_server_ip(server_ip);
#ifdef __TFTP_DEBUG__
DBG_PRINT(INFO_DBG, "[%s] Set Tftp Server : %x\r\n", __func__, server_ip);
#endif
g_progress_state = TFTP_PROGRESS;
send_tftp_rrq(filename, (uint8_t *)TRANS_BINARY, &default_tftp_opt, 1);
}步骤3:运行TFTP协议并处理结果
文件:tftp.c — 当 tftp_read_flag 为 1 时调用 TFTP_run() 函数处理 TFTP 协议操作,依据其返回的 tftp_state 判断结果:若为 TFTP_SUCCESS 则打印成功信息并进入无限循环,若为 TFTP_FAIL 则打印失败信息并进入无限循环。
int TFTP_run(void)
{
int 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)
{
#ifdef __TFTP_DEBUG__
DBG_PRINT(ERROR_DBG, "[%s] recv_udp_packet error\r\n", __func__);
#endif
return g_progress_state;
}
recv_tftp_packet(g_tftp_rcv_buf, len, from_ip, from_port);
return g_progress_state;
}在处理接收到的 TFTP 数据包时,首先调用 recv_tftp_packet() 函数。
static void recv_tftp_packet(uint8_t *packet, uint32_t packet_len, uint32_t from_ip, uint16_t from_port)
{
uint16_t opcode;
/* Verify Server IP */
if (from_ip != get_server_ip())
{
#ifdef __TFTP_DEBUG__
DBG_PRINT(ERROR_DBG, "[%s] Server IP faults\r\n", __func__);
DBG_PRINT(ERROR_DBG, "from IP : %08x, Server IP : %08x\r\n", from_ip, get_server_ip());
#endif
return;
}
opcode = ntohs(*((uint16_t *)packet));
/* Set Server Port */
if ((get_tftp_state() == STATE_WRQ) || (get_tftp_state() == STATE_RRQ))
{
set_server_port(from_port);
#ifdef __TFTP_DEBUG__
DBG_PRINT(INFO_DBG, "[%s] Set Server Port : %d\r\n", __func__, from_port);
#endif
}
switch (opcode)
{
case TFTP_RRQ: /* When Server */
recv_tftp_rrq(packet, packet_len);
break;
case TFTP_WRQ: /* When Server */
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:
// Unknown Mesage
break;
}
}进入该函数后,第一步验证接收到的数据包的源 IP 地址,只有当它与服务器 IP 地址一致时才继续处理,若不一致则直接返回。接着,从数据包中获取操作码(opcode)。根据获取到的操作码,调用相应的处理函数:如果是 TFTP 读请求(RRQ),则调用 recv_tftp_rrq() 函数;若是写请求(WRQ),则调用 recv_tftp_wrq() 函数;对于接收到的数据数据包,调用 recv_tftp_data() 函数;确认数据包则由 recv_tftp_ack() 函数处理;OACK 数据包由 recv_tftp_oack() 函数处理;若遇到错误数据包,调用 recv_tftp_error() 函数来解析错误代码和错误信息。最后,返回 g_progress_state,以此表示当前 TFTP 操作的状态。
超时重传机制
TFTP 基于 UDP 不保证可靠传输,需在应用层实现超时重传。当 g_resend_flag 置位且计时到达 g_timeout 时,根据当前状态重发对应报文(RRQ 或 ACK)。重试次数累计达到 5 次仍未成功,则调用 init_tftp() 复位并置 TFTP_FAIL。
运行验证
烧录例程运行后,首先进行了 PHY 链路检测,然后是 DHCP 获取网络地址结果,最后打印服务器 IP 和文本名称,读取文本内容。
W55MH32 TFTP Client 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
====================================================================================================
tftp server ip: 192.168.1.20, file name: tftp_test_file.txt
send request
tftp read success, file name: tftp_test_file.txt
请注意
测试实例需要 PC 端和 W55MH32 处于同一网段。首先打开 Tftpd32,选择 TFTP 目录和服务器地址。服务器地址必须与程序对应,并且 TFTP 目录下必须有程序中读取的文件。


常见问题
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: TFTP读取失败如何排查
首先确认PC与W55MH32处于同一网段且服务器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
- 结果判断:依据tftp_state为TFTP_SUCCESS或TFTP_FAIL决定后续处理
下一篇文章将聚焦 SNMP 协议,解析其核心原理及在网络管理中的应用,同时讲解如何在相关设备上实现 SNMP 功能,敬请期待!