第十四章 W55MH32 TFTP示例

描述

单芯片解决方案,开启全新体验——W55MH32 高性能以太网单片机

W55MH32是WIZnet重磅推出的高性能以太网单片机,它为用户带来前所未有的集成化体验。这颗芯片将强大的组件集于一身,具体来说,一颗W55MH32内置高性能Arm® Cortex-M3核心,其主频最高可达216MHz;配备1024KB FLASH与96KB SRAM,满足存储与数据处理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP协议栈、内置MAC以及PHY,拥有独立的32KB以太网收发缓存,可供8个独立硬件socket使用。如此配置,真正实现了All-in-One解决方案,为开发者提供极大便利。

在封装规格上,W55MH32 提供了两种选择:QFN100和QFN68。

W55MH32L采用QFN100封装版本,尺寸为12x12mm,其资源丰富,专为各种复杂工控场景设计。它拥有66个GPIO、3个ADC、12通道DMA、17个定时器、2个I2C、5个串口、2个SPI接口(其中1个带I2S接口复用)、1个CAN、1个USB2.0以及1个SDIO接口。如此丰富的外设资源,能够轻松应对工业控制中多样化的连接需求,无论是与各类传感器、执行器的通信,还是对复杂工业协议的支持,都能游刃有余,成为复杂工控领域的理想选择。 同系列还有QFN68封装的W55MH32Q版本,该版本体积更小,仅为8x8mm,成本低,适合集成度高的网关模组等场景,软件使用方法一致。更多信息和资料请进入http://www.w5500.com/网站或者私信获取。

此外,本W55MH32支持硬件加密算法单元,WIZnet还推出TOE+SSL应用,涵盖TCP SSL、HTTP SSL以及 MQTT SSL等,为网络通信安全再添保障。

为助力开发者快速上手与深入开发,基于W55MH32L这颗芯片,WIZnet精心打造了配套开发板。开发板集成WIZ-Link芯片,借助一根USB C口数据线,就能轻松实现调试、下载以及串口打印日志等功能。开发板将所有外设全部引出,拓展功能也大幅提升,便于开发者全面评估芯片性能。

若您想获取芯片和开发板的更多详细信息,包括产品特性、技术参数以及价格等,欢迎访问官方网页:http://www.w5500.com/,我们期待与您共同探索W55MH32的无限可能。

以太网

第十四章 W55MH32 TFTP示例

本篇文章,我们将详细介绍如何在W55MH32芯片上面实现TFTP协议。并通过实战例程,为大家讲解如何使用TFTP客户端模式向服务器获取文本文件。

该例程用到的其他网络协议,例如DHCP请参考相关章节。有关W55MH32的初始化过程,也请参考Network Install章节,这里将不再赘述。

1 TFTP协议简介

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

2 TFTP协议特点

简单性:TFTP协议设计简单,它的报头格式简洁,操作命令种类少,这使得实现起来相对容易,对资源的需求也较低。

轻量级:TFTP协议不需要复杂的连接建立和管理过程,开销小,因此适合在一些对性能要求不高、资源有限的环境中使用。

基于UDP:TFTP使用UDP作为传输层协议,利用了UDP的快速传输和无连接特性,从而能够快速地传输数据。不过,这也意味着TFTP本身不提供可靠的传输保证,需要在应用层实现可靠性机制。

端口固定:TFTP使用固定的端口69来监听客户端的请求。数据传输使用的端口是动态分配的,每次传输会在此基础上选择一个临时端口。

数据块大小限制:每个数据报文最多只能传输512字节的数据,如果文件较大,会分多次传输,每次发送一个512字节的数据块。最后一个数据块可能小于512字节,表示文件的结束。

3 TFTP协议应用场景

接下来,我们了解下在W55MH32上,可以使用TFTP协议完成哪些操作及应用呢?

固件升级:对于路由器、交换机等网络设备,TFTP协议常用于将固件传输到这些设备以进行固件更新。TFTP协议能够确保固件文件快速、准确地传输到目标设备。

配置文件传输:TFTP协议也常用于管理网络设备的配置文件。将配置文件传输到网络设备以进行 配置更新,或者从网络设备下载配置文件进行备份或分析。

IOT设备固件升级:TFTP协议因其简单性和高效性,成为IOT设备固件升级的一种常用协议。

4 TFTP协议基本工作流程

请求发送:客户端向服务器发送读请求(RRQ,Read Request)或写请求(WRQ,Write Request)。这些请求包含了要读取或写入的文件名以及传输模式(如二进制或ASCII码)。

建立连接:服务器接收到客户端的请求后,根据请求中的文件名和传输模式,打开相应的文件(对于写请求)或准备发送文件数据(对于读请求),并向客户端发送确认信息,从而建立连接。

数据传输:在写请求的情况下,客户端开始发送文件数据到服务器,服务器接收并写入文件。数据以数据块的形式发送,每个数据块的大小通常为512字节(但可以根据网络状况调整)。

在读请求的情况下,服务器开始发送文件数据到客户端,客户端接收并保存文件。同样,数据也是以数据块的形式发送的。

回应与确认:每当客户端或服务器发送一个数据块后,接收方会发送一个回应包(ACK,Acknowledgment)来确认接收到了该数据块。这个回应包包含了接收到的数据块的编号,以确保数据的顺序和完整性。

继续传输或结束:根据回应包,发送方会继续发送下一个数据块,直到整个文件传输完成。如果传输过程中出现错误,服务器会向客户端发送错误信息包(ERROR),中断传输过程。

关闭连接:文件传输完成后,客户端和服务器会关闭连接。

5 TFTP协议报文解析

常见的操作码:

1:读请求(RRQ),用于请求读取服务器上的文件。

2:写请求(WRQ),用于请求向服务器上写入文件。

3:数据(DATA),用于传输文件数据。

4:回应(ACK),用于确认接收到的数据块。

5:错误信息(ERROR),用于报告传输过程中发生的错误。

常见操作码的报文格式如下:

报文类型 报文格式 操作码 其他关键字段及说明
读请求(RRQ) 总长可变,由2字节操作码、可变长文件名(以1字节0结尾)、可变长传输模式(以1字节 0 结尾)组成 1 文件名:明确要读取的文件名称
传输模式:“netascii” 表示ASCI码模式,“octet”表示二进制模式
写请求(WRQ) 总长可变,由2字节操作码、可变长文件名(以1字节0结尾)、可变长传输模式(以1字节 0 结尾)组成 2 文件名:明确要读取的文件名称
传输模式:“netascii” 表示ASCII码模式,“octet”表示二进制模式
数据(DATA) 由2字节操作码、2字节数据块编号、最多512字节数据组成 3 数据块编号:从1开始,用于标识数据块顺序
数据:实际传输的文件内容
确认(ACK) 由2字节操作码和2字节确认的数据块编号组成 4 数据块编号:与接收到的数据块编号一致,用于确认接收
错误(ERROR) 由2字节操作码、2字节错误码、可变长错误信息(以 1字节0结尾)组成 5 错误码:明确错误类型
错误信息:具体描述错误情况

报文示例:

客户端读请求报文:

 

|报文解析|
Trivial File Transfer Protocol
    Opcode: Read Request (1)             (操作码为01,读请求报文)
    Source File: tftp_test_file.txt      (明确要读取的文件名为tftp_test_file.txt)
    Type: octet                           (传输模式为octet)
    Option: timeout = 5

|报文原文|
00 01 74 66 74 70 5f 74 65 73 74 5f 66 69 6c 65 2e 74 78 74 00 6f 63 74 65 74 00 74 69 6d 65 6f 75 74 00 35 00

 

服务器响应报文:

 

|报文解析|
Trivial File Transfer Protocol
    Opcode: Option Acknowledgement (6)   (操作码为06,扩展操作码)
    [Destination File: tftp_test_file.txt]
    [Read Request in frame 125]
Option: timeout = 5

|报文原文|
00 06 74 69 6d 65 6f 75 74 00 35 00

 

客户端响应报文:

 

|报文解析|
Trivial File Transfer Protocol
    Opcode: Acknowledgement (4)          (操作码为04,回应报文)
    [Destination File: tftp_test_file.txt]
    [Read Request in frame 125]
    Block: 0                              (数据块标号为00 00)
    [Full Block Number: 0]

|报文原文|
00 04 00 00

 

服务器响应报文:

 

|报文解析|
Trivial File Transfer Protocol
    Opcode: Data Packet (3)              (操作码为03,数据报文)
    [Destination File: tftp_test_file.txt]
    [Read Request in frame 125]
    Block: 1                              (数据块标号为00 01)
    [Full Block Number: 1]
Data (36 bytes)
    Data: 736461666173646661736466617364666666666666666641617364666173666166736466    (数据)
[Length: 36]

|报文原文|
00 03 00 01 73 64 61 66 61 73 64 66 61 73 64 66 61 73 64 66 66 66 66 66 66 66 66 41 61 73 64 66 61 73 66 61 66 73 64 66 

 

客户端响应报文:

 

|报文解析|
Trivial File Transfer Protocol
    Opcode: Acknowledgement (4)          (操作码为04,回应报文)
    [Destination File: tftp_test_file.txt]
    [Read Request in frame 125]
    Block: 1                              (数据块标号为00 01)
   [Full Block Number: 1]

|报文原文|
00 04 00 01

 

6 实现过程

接下来,我们在W55MH32上实现TFTP协议读取文件。

注意:测试实例需要PC端和W55MH32处于同一网段。

在主函数中调用do_tftp_client()函数不断检查和处理 TFTP 客户端的状态,并根据读取的结果(成功或失败)进入相应的处理状态。

 

1.  do_tftp_client(SOCKET_ID, ethernet_buf);

 

do_tftp_client()函数如下:

 

 void do_tftp_client(uint8_t sn, uint8_t *buff)
{
   uint32_t tftp_server_ip       = inet_addr(TFTP_SERVER_IP);
   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: %srn", TFTP_SERVER_IP, TFTP_SERVER_FILE_NAME);
           printf("send requestrn");
           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: %srn", tftp_read_file_name);
               while (1)
               {
                   // 成功后进入死循环
               }
           }
           else if (tftp_state == TFTP_FAIL)
           {
               printf("tftp read fail, file name: %srn", tftp_read_file_name);
               while (1)
               {
                   // 失败后进入死循环
               }
           }
       }
   }
}

 

进入do_tftp_client()函数后开始进行TFTP客户端处理,步骤如下:

步骤一:TFTP初始化

调用TFTP_init()函数对TFTP客户端进行初始化,参数sn和buff分别是socket号,socket缓存。

 

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 读请求

当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 : %xrn", __func__, server_ip);
#endif
   g_progress_state = TFTP_PROGRESS;
   send_tftp_rrq(filename, (uint8_t *)TRANS_BINARY, &default_tftp_opt, 1);
}
   

 

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

当 tftp_read_flag 为 1 时调用 TFTP_run()函数处理 TFTP 协议操作,依据其返回的 tftp_state 判断结果:若为 TFTP_SUCCESS 则打印成功信息并进入无限循环,若为 TFTP_FAIL 则打印失败信息并进入无限循环。

TFTP_run()函数如下:

 

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 errorrn", __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()函数。

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 faultsrn", __func__);
       DBG_PRINT(ERROR_DBG, "from IP : %08x, Server IP : %08xrn", 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 : %drn", __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 Message
           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操作的状态。

7 运行结果

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

以太网

8 总结

本文讲解了如何在 W55MH32 芯片上实现 TFTP 协议,通过实战例程详细展示了使用 TFTP 客户端模式从服务器获取文本文件的过程,涵盖 TFTP 初始化、发送读请求、运行协议并处理结果等核心步骤。文章还对 TFTP 协议的简介、特点、应用场景、基本工作流程和报文解析进行了分析,帮助读者理解其在文件传输中的实际应用价值。

下一篇文章将聚焦 SNMP 协议,解析其核心原理及在网络管理中的应用,同时讲解如何在相关设备上实现 SNMP 功能,敬请期待!

WIZnet 是一家无晶圆厂半导体公司,成立于 1998 年。产品包括互联网处理器 iMCU™,它采用 TOE(TCP/IP 卸载引擎)技术,基于独特的专利全硬连线 TCP/IP。iMCU™ 面向各种应用中的嵌入式互联网设备。

WIZnet 在全球拥有 70 多家分销商,在香港、韩国、美国设有办事处,提供技术支持和产品营销。

香港办事处管理的区域包括:澳大利亚、印度、土耳其、亚洲(韩国和日本除外)。


审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分