STM32/STM8技术论坛
直播中

klysa

14年用户 1336经验值
私信 关注
[资料]

STM32物联网之TFTP文件传输

  • 感言:专注物联网应用开发,分享物联网技术经验。
  • 软件平台:IAR6.5
  • TCP/IP协议栈:LWIP1.4.1
  • 硬件平台:STM32F103C8T6有线通信



1、TCP/IP协议栈LWIP1.1、LWIP认识

       LWIP是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈,是Light Weight (轻型)IP协议,有无操作系统的支持都可以运行。LWIP提供三种API,分别是RAW API、LWIP API 、BSD API。其中RAW API把协议栈和应用程序放到一个进程里边,该接口基于函数回调技术来实现的,适合于无操作系统的场合运行,如单片机。本文使用的就是LWIP的RAW API来实现网络层的通信的。

1.2、TFTP在LWIP中的实现

        关于LWIP的移植,就不在本文中多讲,读者可以在网上找到众多资料或在另外的专题中再详细讲解,在这里我们专注其应用。在LWIP中实现一个TFTP服务器非常简单,根据RAW API的编程方法,在初始化的时候创建一个UDP PCB(TFTP使用UDP协议通信),且绑定69端口(TFTP默认通信端口),最后指定该UDP PCB的数据接收回调函数即可。

以上的创建TFTP服务器的方法需要在LWIP初始化,并启动网卡后进行:

[cpp] view plaincopy


  • LwIP_Config();  
  •    printf("ipaddr:%d.%d.%d.%drn", net_ip[0], net_ip[1], net_ip[2], net_ip[3]);  
  •   
  •    tftpd_init();  

在tftpd_init函数中创建TFTP服务器:

[cpp] view plaincopy


  • void tftpd_init(void)  
  • {  
  •   err_t err;  
  •   unsigned port = 69;  
  •   
  •   /* create a new UDP PCB structure  */  
  •   UDPpcb = udp_new();  
  •   if (!UDPpcb)  
  •   {  /* Error creating PCB. Out of Memory  */  
  •     return;  
  •   }  
  •   
  •   /* Bind this PCB to port 69  */  
  •   err = udp_bind(UDPpcb, IP_ADDR_ANY, port);  
  •   if (err != ERR_OK)  
  •   {    /* Unable to bind to port  */  
  •     return;  
  •   }  
  •   
  •   /* TFTP server start  */  
  •   udp_recv(UDPpcb, recv_callback_tftp, NULL);  
  • }  

OK,到这里就完成了TFTP服务器在LWIP中建立起来了,接下来的主要事情就是根据TFTP协议进行协议解释、数据处理。


2、TFTP协议分析2.1、TFTP通信基本流程(摘自网络)


2.2、TFTP报文格式(摘自网络)


2.3、TFTP协议理解

     从以上两张图片,我们了解到什么有用信息呢?

  • 每一次文件传输,首先需要发起一个请求,根据请求帧的操作码判断是读文件还是写文件。
  • 每一帧都有一个操作码用来标识读写。
  • 数据包的长度有一个块编号用来表示数据包的顺序。
  • 数据包中的数据长度为512个字节(在后面的软件我们可以了解到这个长度是可以设定的)。
3、实现TFTP文件传输3.1、文件传输协议实现

有了第2节的协议分析,我们基本了解了TFTP通信的协议,在这里,我们来实现TFTP的服务器端代码。

在监听的回调函数被触发调用时,首先从请求帧中获取操作码:

[cpp] view plaincopy


  • typedef enum {  
  •   TFTP_RRQ = 1,  
  •   TFTP_WRQ = 2,  
  •   TFTP_DATA = 3,  
  •   TFTP_ACK = 4,  
  •   TFTP_ERROR = 5  
  • } tftp_opcode;  
  •   
  • tftp_opcode tftp_decode_op(char *buf)  
  • {  
  •   return (tftp_opcode)(buf[1]);  
  • }  

根据操作码进行相应的处理:

[cpp] view plaincopy


  • tftp_opcode op = tftp_decode_op(pkt_buf->payload);  
  •   
  • switch (op)  
  •   {  
  •   
  •     case TFTP_RRQ:    /* TFTP RRQ (read request) */  
  •       tftp_extract_filename(FileName, pkt_buf->payload);  
  •       tftp_process_read(upcb, addr, port, FileName);  
  •       break;  
  •   
  •     case TFTP_WRQ:    /* TFTP WRQ (write request) */   
  •       tftp_extract_filename(FileName, pkt_buf->payload);  
  •       //在这个加入擦FALSH  
  •        tftp_process_write(upcb, addr, port, FileName);  
  •       break;  
  •   
  •     default:  
  •       /* sEndTransfera generic access violation message */  
  •       tftp_send_error_message(upcb, addr, port, TFTP_ERR_ACCESS_VIOLATION);  
  •       /* TFTP unknown request op */  
  •       /* no need to use tftp_cleanup_wr because no "tftp_connection_args" struct has been malloc'd   */  
  •       udp_remove(upcb);  
  •   
  •       break;  
  •   }  

这里当STM32接收到写操作请求时,通过tftp_extract_filename函数把文件名读出来。接下来通过tftp_process_write函数来完成文件数据的传输:

[cpp] view plaincopy


  • int tftp_process_write(struct udp_pcb *upcb, struct ip_addr *to, int to_port, char *FileName)  
  • {  
  •   ... ...  
  •   udp_recv(upcb, wrq_recv_callback, args);  
  •   tftp_send_ack_packet(upcb, to, to_port, args->block);  
  •   
  •   return 0;  
  • }  

设定数据传输回调函数后,根据TFTP协议,回复一个ACK,之后TFTP客户端开始传输文件数据,从而触发调用wrq_recv_callback[cpp] view plaincopy


  • void wrq_recv_callback(void *_args, struct udp_pcb *upcb, struct pbuf *pkt_buf, struct ip_addr *addr, u16_t port)  
  • {  
  •   tftp_connection_args *args = (tftp_connection_args *)_args;  
  •   int n = 0;  
  •   
  •   if (pkt_buf->len != pkt_buf->tot_len)  
  •   {  
  •     return;  
  •   }  
  •   
  •   /* Does this packet have any valid data to write? */  
  •   if ((pkt_buf->len > TFTP_DATA_PKT_HDR_LEN) &&  
  •       (tftp_extract_block(pkt_buf->payload) == (args->block + 1)))  
  •   {  
  •     /* 在这里处理接收到的数据pkt_buf->payload */  
  •   
  •     /* update our block number to match the block number just received */  
  •     args->block++;  
  •     /* update total bytes  */  
  •     (args->tot_bytes) += (pkt_buf->len - TFTP_DATA_PKT_HDR_LEN);  
  •   
  •     /* This is a valid pkt but it has no data.  This would occur if the file being
  •        written is an exact multiple of 512 bytes.  In this case, the args->block
  •        value must still be updated, but we can skip everything else.    */  
  •   }  
  •   else if (tftp_extract_block(pkt_buf->payload) == (args->block + 1))  
  •   {  
  •     /* update our block number to match the block number just received  */  
  •     args->block++;  
  •   }  
  •   
  •   /* SEndTransferthe appropriate ACK pkt (the block number sent in the ACK pkt echoes
  •    * the block number of the DATA pkt we just received - see RFC1350)
  •    * NOTE!: If the DATA pkt we received did not have the appropriate block
  •    * number, then the args->block (our block number) is never updated and
  •    * we simply sEndTransfera "duplicate ACK" which has the same block number as the
  •    * last ACK pkt we sent.  This lets the host know that we are still waiting
  •    * on block number args->block+1. */  
  •   tftp_send_ack_packet(upcb, addr, port, args->block);  
  •   
  •   /* If the last write returned less than the maximum TFTP data pkt length,
  •    * then we've received the whole file and so we can quit (this is how TFTP
  •    * signals the EndTransferof a transfer!)
  •    */  
  •   if (pkt_buf->len < TFTP_DATA_PKT_LEN_MAX)  
  •   {  
  •     tftp_cleanup_wr(upcb, args);  
  •     pbuf_free(pkt_buf);  
  •   }  
  •   else  
  •   {  
  •     pbuf_free(pkt_buf);  
  •     return;  
  •   }  
  •   
  • }  

OK!至此STM32就完成了整个TFTP协议文件的接收。
3.2、保存文件数据

接收到完整的文件数据之后,我们需要把数据写到STM32的FLASH中,保存起来。

由于STM32内存较小,不可能开辟一个大的内存空间把文件数据保存起来再写到FLASH,

所以需要边接收边写FLASH。

首先在接收到写操作请求后,把存储区域的FLASH擦除:

[cpp] view plaincopy


  • case TFTP_WRQ:    /* TFTP WRQ (write request) */   
  • ... ...  
  •   FlashDestination = HtmlDataAddress;  
  • * Erase the needed pages where the user application will be loaded */  
  •   /* Define the number of page to be erased */  
  •   NbrOfPage = FLASH_PagesMask(HtmlTotalSize);//擦除HTML区域  
  •   
  •   /* Erase the FLASH pages */  
  •   FLASH_Unlock();  
  •   for (EraseCounter = 0; (EraseCounter < NbrOfPage) && (FLASHStatus == FLASH_COMPLETE); EraseCounter++)  
  •    {  
  •      FLASHStatus = FLASH_ErasePage(HtmlSizeAddress + (PageSize * EraseCounter));  
  •    }  
  •   FLASH_Lock();  

在文件数据传输过程中,把<=512BYTE的数据写到FLASH:[cpp] view plaincopy


  • filedata = (uint32_t)pkt_buf->payload + TFTP_DATA_PKT_HDR_LEN;  
  • FLASH_Unlock();  
  • for (n = 0;n < (pkt_buf->len - TFTP_DATA_PKT_HDR_LEN);n += 4)  
  • {  
  • /* Program the data received into STM32F10x Flash */  
  • FLASH_ProgramWord(FlashDestination, *(uint32_t*)filedata);  
  •   
  •   if (*(uint32_t*)FlashDestination != *(uint32_t*)filedata)  
  •    {  
  •      /* End session */  
  • p_send_error_message(upcb, addr, port, FLASH_VERIFICATION_FAILED);  
  •     /* close the connection */  
  •     tftp_cleanup_wr(upcb, args); /* close the connection */  
  •    }  
  •    FlashDestination += 4;  
  •    filedata += 4;  
  • }  
  • FLASH_Lock();  

到这里,就实现了STM32接收TFTP客户端传输的文件数据,并保存到FLASH地址为FlashDestination的区域中。
3.3、演示操作

将通信板连接到与电脑在同一局域的路由器,并正确配置好IP信息。在电脑端打开软件Tftpd32.exe:


点击“上传”按键,就会把文件html.bin文件发送到STM32通信板:



可以在STM32通信板中把文件内容读出来使用,在下一篇博客物联网WEB开发中会使用TFTP传输HTML文件。

4、TFTP的应用

    TFTP主要是实现文件传输,在固件升级、程序调试中极大提高效率,有重要的意义。

在WEB的应用开发中会体会到其强大的作用。


回帖(8)

Johnson

2015-5-17 16:54:03
有用 谢谢分享
举报

陈文胜

2015-5-17 17:45:15
有用 谢谢分享
举报

beian10

2015-5-18 13:30:16
要实现tftp服务器,首先在板上做好一个文件系统才好
举报

刘欢

2015-5-19 21:59:50
涨姿势~~~~~~~~~~~~~~~~
举报

陳正和

2015-7-13 21:35:29
有用 谢谢分享! 谢谢
举报

h1654155205.0694

2016-1-8 20:38:30
有用,谢谢呢,今天才开始看网络的连接
举报

h1654155205.0694

2016-1-8 20:38:46
有用,谢谢呢,今天才开始看网络的连接
举报

h1654155913.0786

2016-5-24 13:34:41
受到警告
提示: 作者被禁止或删除 内容自动屏蔽
举报

更多回帖

发帖
×
20
完善资料,
赚取积分