【RL-TCPnet网络教程】第37章 RL-TCPnet之FTP客户端

时间:2021-06-13 12:51:24

第37章      RL-TCPnet之FTP客户端

本章节为大家讲解RL-TCPnet的FTP客户端应用,学习本章节前,务必要优先学习第35章的FTP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。

本章教程含STM32F407开发板和STM32F429开发板。

37.1  初学者重要提示

37.2  FTP函数

37.3  FTP配置说明(Net_Config.c)

37.4  FTP调试说明(Net_Debug.c)

37.5  FTP访问方法和板子的操作步骤

37.6  实验例程说明(裸机)

37.7  实验例程说明(RTX)

37.8  总结

37.1  初学者重要提示

  1. 学习本章节前,务必保证已经学习了第35章的基础知识。
  2. 本章配套的例子是将开发板作为FTP客户端,使用开发板上面的SD卡作为客户端的存储介质。所以测试本章节的例子,务必要准备一个SD卡。
  3. 由于配套例子的文件系统是采用的RL-FlashFS,此文件系统的文件名仅支持ASCII字符,不支持中文,特别注意!
  4. 具体电脑端FTP服务器的创建方法和板子的操作步骤在本章的37.5小节有详细说明。做本章节配套的实验,必须要看!

37.2  FTP函数

使用如下18个函数可以实现RL-TCPnet的FTP:

  • ftp_accept_host
  • ftp_check_account
  • ftp_fclose
  • ftp_evt_notify
  • ftp_fdelete
  • ftp_ffind
  • ftp_file_access
  • ftp_fopen
  • ftp_fread
  • ftp_frename Server
  • ftp_fwrite Server
  • ftp_get_user_id
  • ftpc_cbfunc
  • ftpc_connect
  • ftpc_fclose
  • ftpc_fopen
  • ftpc_fread
  • ftpc_fwrite

关于这18个函数的讲解及其使用方法可以看教程第 3 章 3.4 小节里面说的参考资料 rlarm.chm 文件:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

这里我们重点的说以下6个函数,因为本章节配套的例子使用的是这6个函数:

  • ftpc_cbfunc
  • ftpc_connect
  • ftpc_fclose
  • ftpc_fopen
  • ftpc_fread
  • ftpc_fwrite

关于这些函数注意以下三点:

  1. FTP的所有函数都不支持重入,也就是不支持多任务调用。
  2. 以ftp_开头的函数是用于FTP服务器的。
  3. 以ftpc_开头的函数是用于FTP客户端的。

37.2.1    函数ftpc_fopen

函数原型:

void *ftpc_fopen (

    U8* mode);    /* 操作模式 */

函数描述:

函数ftpc_fopen用于打开本地文件(FTP客户端的文件)。此函数在MDK的安装目录中的FTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。

  1. 第1个参数是操作模式,可以是读操作或者写操作,具体支持的形参类型如下:【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端
  2. 返回值,打开文件成功的话,返回指向此文件的指针变量,否则返回NULL。

使用这个函数要注意以下问题:

  1. 此接口函数是用于FTP客户端的。

使用举例:

void *ftpc_fopen (U8 *mode) {

  /* 打开文件,如果返回NULL,表示打开失败 */

  return (fopen (LOCAL_FILE, (char *)mode));

}

37.2.2   函数ftpc_fclose

函数原型:

void *ftpc_fclose (

    FILE* file);  /* 文件句柄地址 */

函数描述:

函数ftpc_fclose用于关闭文件。此函数在MDK的安装目录中的FTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。

  1. 第1个参数是要关闭的文件句柄地址。
  2. 返回值,实际上此函数无需返回任何数值,写成下面使用举例中的形式即可。

使用这个函数要注意以下问题:

  1. 此接口函数是用于FTP客户端的。

使用举例:

void ftpc_fclose (void *file) {

  /* 关闭文件 */

  fclose (file);

}

37.2.3   函数ftpc_fread

函数原型:

U16 ftpc_fread (

    FILE* file,     /* 文件句柄地址 */

    U8*   buf,      /* 数据缓冲地址 */

    U16   len );    /* 要读取的字节数 */

函数描述:

函数ftpc_fread用于从文件中读出len个字节数据。此函数在MDK的安装目录中的FTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。

  1. 第1个参数是要读取数据的文件句柄地址。
  2. 第2个参数是数据缓冲地址,用于存储读取出来的数据。
  3. 第3个参数是要读取出来的数据大小,单位字节。
  4. 返回值,返回从文件中实际读出的字节数。

使用这个函数要注意以下问题:

  1. 设置读取函数时,必须设置指定大小的字节数。如果实际读出的字节数小于len,将停止读取并关闭文件,这种情况一般都是文件已经读取完毕。
  2. 此接口函数是用于FTP客户端的。

使用举例:

U16 ftpc_fread (void *file, U8 *buf, U16 len) {

  /* 读取len字节到buf中,返回值是实际读取的字节数,返回0的话,表示文件已经读取完毕,文件将被关闭 */

  return (fread (buf, , len, file));

}

37.2.4   函数ftpc_fwrite

函数原型:

U16 ftpc_fwrite (

    FILE* file,     /* 文件句柄地址 */

    U8*   buf,      /* 数据缓冲地址 */

    U16   len );    /* 要写入的字节数 */

函数描述:

函数ftpc_fwrite用于往文件中写入len个字节数据。此函数在MDK的安装目录中的FTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。

  1. 第1个参数是要写入数据的文件句柄地址。
  2. 第2个参数是数据缓冲地址,存储要写入的数据。
  3. 第3个参数是要写入的数据大小,单位字节。
  4. 返回值,返回实际写入文件的字节数。

使用这个函数要注意以下问题:

  1. 设置写函数时,必须设置指定大小的字节数。如果实际写入的字节数小于len,FTP客户端将停止写入,终止数据传输并关闭FTP会话,这种情况一般是写操作出错了。
  2. 此接口函数是用于FTP客户端的。

使用举例:

U16 ftpc_fwrite (void *file, U8 *buf, U16 len) {

  /* 将buf中的len字节写入到文件中,如果返回数值(实际写入的字节数)不等于len,数据传输将终止 */

  return (fwrite (buf, , len, file));

}

37.2.5   函数ftpc_cbfunc

函数原型:

U16 ftpc_cbfunc (

    U8   code,      /* 消息类型 */

    U8*  buf,       /* 输出缓冲区地址 */

U16  buflen );  /* 输出缓冲区大小 */

函数描述:

函数ftpc_cbfunc是FTP客户端的回调函数,用于为FTP客户端会话提供额外参数,如登录FTP服务器的用户名和密码、本地和远程文件名等。FTP客户端多次调用此函数才可完成文件操作请求。

  1. 第1个参数是FTP客户端所需的附加信息(用户,密码,文件名等)类型,具体支持的参数类型如下:【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端
  2. 第2个参数是输出缓冲区地址,用于将请求的数据写入到输出缓冲区。
  3. 第3个参数是输出缓冲区大小,单位字节。
  4. 返回值,返回使用的输出缓冲区大小,单位字节。

使用这个函数要注意以下问题:

  1. 每次通信,数据缓冲区的大小会有所不同,因为它是由TCP Socket的MSS最大报文段协商决定的,局域网中一般是1400字节左右。
  2. 写到输出缓冲区的数据,不可以超过第三个参数buflen的大小,否则会造成内存指针链表的损坏,从而很容易造成系统崩溃。

使用举例:

#define FTPC_USERNAME   "armfly"       /* 远程FTP服务器的账号 */

#define FTPC_PASSWORD   "123456"       /* 远程FTP服务器的密码 */

#define FTPC_PATH       ""             /* 要访问的子文件夹,FTP电脑端设置后是没有路径的,填空 */

#define FTPC_FILENAME   "server.pdf"   /* 要访问的文件 */

#define FTPC_NEWNAME    "renamed.pdf"  /* 使用这里定义的文件字重命名宏定义FTPC_FILENAME的文件名 */

#define FTPC_DIRNAME    "New_Folder"    /* 创建名字为New_Folder的文件夹或者删除名字为New_Folder的文件夹 */

#define FTPC_LISTNAME   "*"            /* 浏览宏定义FTPC_PATH路径下所有文件 */

#define LOCAL_FILE      "client.pdf"   /* 开发板SD卡里面要上传的文件,或者从FTP服务器下载文件后,

                                          文件会被设置成此名字 */

U16 ftpc_cbfunc (U8 code, U8 *buf, U16 buflen) {

  /* This function is called by the FTP client to get parameters. It returns*/

  /* the number of bytes written to buffer 'buf'. This function should NEVER*/

  /* write more than 'buflen' bytes to this buffer.                         */

  /* Parameters:                                                            */

  /*   code   - function code with following values:                        */

  /*             0 = Username for FTP authentication                        */

  /*             1 = Password for FTP authentication                        */

  /*             2 = Working directory path for all commands                */

  /*             3 = Filename for PUT, GET, APPEND, DELETE, RENAME command  */

  /*             4 - New filename for RENAME command                        */

  /*             5 - Directory name for MKDIR, RMDIR command                */

  /*             6 - File filter/mask for LIST command (wildcards allowed)  */

  /*             7 = Received directory listing on LIST command             */

  /*   buf    - transmit/receive buffer                                     */

  /*   buflen - length of this buffer                                       */

  /*             on transmit, it specifies size of 'buf'                    */

  /*             on receive, specifies the number of bytes received         */

  U32 i,len = ;

  switch (code) {

    case :

      /* Enter Username for login. */

      len = str_copy (buf, FTPC_USERNAME);

      break;

    case :

      /* Enter Password for login. */

      len = str_copy (buf, FTPC_PASSWORD);

      break;

    case :

      /* Enter a working directory path. */

      len = str_copy (buf, FTPC_PATH);

      break;

    case :

      /* Enter a filename for file operations. */

      len = str_copy (buf, FTPC_FILENAME);

      break;

    case :

      /* Enter a new name for rename command. */

      len = str_copy (buf, FTPC_NEWNAME);

      break;

    case :

      /* Enter a directory name to create or remove. */

      len = str_copy (buf, FTPC_DIRNAME);

      break;

    case :

      /* Enter a file filter for list command. */

      len = str_copy (buf, FTPC_LISTNAME);

      break;

    case :

      /* Process received directory listing in raw format. */

      /* Function return value is don't care.              */

      for (i = ; i < buflen; i++) {

        putchar (buf[i]);

      }

      break;

  }

  return ((U16)len);

}

37.2.6   函数ftpc_connect

函数原型:

BOOL ftpc_connect (

    U8*   ipadr,                   /* FTP服务器的IP地址 */

    U16   port,                    /* FTP服务器的端口号 */

    U8    command,                 /* FTP命令 */

    void (*cbfunc)(U8 event) );    /* 回调函数 */

函数描述:

函数ftpc_connect用于启动RL-TCPnet的FTP客户端登录FTP服务器,可以对FTP服务器进行文件管理,比如上传下载、删除、重命名等操作,FTP服务器的地址和端口号由此函数的前两个形参决定。如果第2个参数的端口号填0,系统将使用FTP服务器的标准端口号21进行连接。

  1. 第1个参数填FTP服务器的IP地址。
  2. 第2个参数填FTP服务器的端口号。
  3. 第3个参数填FTP命令,具体支持的命令如下,这些命令是客户端发给服务器的:【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端
  4. 第4个参数填此函数的回调函数,当FTP会话即将结束时,会调用这个函数。此回调函数只有一个形参,形参类型如下:【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端
  5. 返回值,返回__TRUE表示FTP客户端启动成功(注意,仅仅是客户端启动成功,并不是命令成功执行),返回__FALSE表示启动失败。

使用这个函数要注意以下问题:

  1. 标准FTP的端口号是用的TCP端口21。
  2. 用户是通过此函数启动RL-TCPnet的FTP客户端登录FTP服务器进行文件管理。

使用举例:

/*

*********************************************************************************************************

*                                          变量

*********************************************************************************************************

*/

/*

*********************************************************************************************************

*                              宏定义,远程FTP服务器的IP和端口

*********************************************************************************************************

*/

/* 要访问的远程FTP服务器IP配置 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            4

#define PORT_NUM       21  /* FTP服务器,默认端口号是21,无需改动 */

/*

*********************************************************************************************************

*                                         变量

*********************************************************************************************************

*/

uint8_t ServerIP[] = {IP1, IP2, IP3, IP4};

/*

*********************************************************************************************************

*    函 数 名: ftpc_notify

*    功能说明: 函数ftpc_connect的回调函数。

*    形    参: event  事件类型

*    返 回 值: 无

*********************************************************************************************************

*/

static void ftpc_notify (U8 event)

{

     switch (event)

     {

          /* 文件操作成功 */

         case FTPC_EVT_SUCCESS:

              printf_debug ("Command successful\n");

              break;

         /* 失败,因为FTP服务器响应时间溢出,因此FTP客户端终止操作 */

         case FTPC_EVT_TIMEOUT:

              printf_debug ("Failed, timeout expired\n");

              break;

         /* 失败,FTP客户端登陆FTP服务器失败 */

         case FTPC_EVT_LOGINFAIL:

              printf ("Failed, username/password invalid\n");

              break;

         /* 失败,禁止操作此文件 */

         case FTPC_EVT_NOACCESS:

              printf_debug ("Failed, operation not allowed\n");

              break;

         /* 失败,在FTP服务器上找不到请求的文件 */

         case FTPC_EVT_NOTFOUND:

              printf_debug ("Failed, file or path not found\n");

              break;

         /* 失败,在FTP服务器上找不到工作目录路径 */

         case FTPC_EVT_NOPATH:

              printf ("Failed, working directory not found\n");

              break;

         /* 失败,文件打开或者写入出错,即FTP客户端操作SD卡或者其它存储介质出错 */

         case FTPC_EVT_ERRLOCAL:

              printf_debug ("Failed, local file open/write error\n");

              break;

         /* 失败,未指定的协议错误,或者说在文件操作过程中遇到错误 */

         case FTPC_EVT_ERROR:

              printf_debug ("Failed, unspecified protocol error\n");

              break;

     }

}

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPent测试函数。

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{

     OS_RESULT xResult;

     while ()

     {

         os_evt_wait_or(0x003F, 0xFFFF); 

         xResult = os_evt_get ();

         switch (xResult)

         {

              /* 接收到K2键按下,发送FTPC_CMD_GET命令,表示从FTP服务器下载文件 */

              case KEY2_BIT1:   

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_GET, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

              /* 接收到K3键按下,发送FTPC_CMD_PUT命令,表示向FTP服务器上传文件 */

              case KEY3_BIT2:              

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_PUT, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

              /* 接收到摇杆上键按下,发送FTPC_CMD_LIST命令,表示列出当前目录下所有文件详细信息 */

              case JOY_U_BIT3:             

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_LIST, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

              /* 接收到摇杆左键按下,发送FTPC_CMD_MKDIR命令,表示创建一个文件夹 */

              case JOY_L_BIT4:

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_MKDIR, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

         /* 接收到摇杆右键按下,发送FTPC_CMD_RMDIR命令,表示删除一个文件夹,文件夹为空的时候才可以删除 */

              case JOY_R_BIT5:  

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_RMDIR, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

               /* 其他的键值不处理 */

              default:                    

                   break;

         }

         while (main_TcpNet() == __TRUE);

     }

}

37.3 FTP配置说明(Net_Config.c)

(本章节配套例子的配置与本小节的说明相同)

RL-TCPnet的配置工作是通过配置文件Net_Config.c实现。在MDK工程中打开文件Net_Config.c,可以看到下图所示的工程配置向导:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

RL-TCPnet要配置的选项非常多,我们这里把几个主要的配置选项简单介绍下。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

System Definitions

(1)Local Host Name

局域网域名。

这里起名为armfly,使用局域网域名限制为15个字符。

(2)Memory Pool size

参数范围1536-262144字节。

内存池大小配置,单位字节。另外注意一点,配置向导这里显示的单位是字节,如果看原始定义,MDK会做一个自动的4字节倍数转换,比如我们这里配置的是8192字节,那么原始定义是#define MEM_SIZE  2048,也就是8192/4 = 2048。

(3)Tick Timer interval

可取10,20,25,40,50,100,200,单位ms。

系统滴答时钟间隔,也就是网络协议栈的系统时间基准,默认情况下,取值100ms。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

Ethernet Network Interface

以太网接口配置,勾选了此选项就可以配置了,如果没有使能DHCP的话,将使用这里配置的固定IP

(1)  MAC Address

局域网内可以随意配置,只要不跟局域网内其它设备的MAC地址冲突即可。

(2) IP Address

IP地址。

(3) Subnet mask

子网掩码。

(4) Default Gateway

默认网关。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

Ethernet Network Interface

以太网接口配置,这个配置里面还有如下两项比较重要的配置需要说明。

(1)  NetBIOS Name Service

NetBIOS局域网域名服务,这里打上对勾就使能了。这样我们就可以通过前面配置的Local Host Name局域网域名进行访问,而不需要通过IP地址访问了。

(2)  Dynaminc Host Configuration

即DHCP,这里打上对勾就使能了。使能了DHCP后,RL-TCPnet就可以从外接的路由器上获得动态IP地址。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

UDP Sockets

UDP Sockets配置,打上对勾就使能了此项功能

(1)  Number of UDP Sockets

用于配置可创建的UDP Sockets数量,这里配置了5个。

范围1 – 20。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

TCP Sockets

TCP Sockets配置,打上对勾就使能了此项功能

(1)  Number of TCP Sockets

用于配置可创建的TCP Sockets数量。

(2)  Number of Retries

范围0-20。

用于配置重试次数,TCP数据传输时,如果在设置的重试时间内得不到应答,算一次重试失败,这里就是配置的最大重试次数。

(3) Retry Timeout in seconds

范围1-10,单位秒。

重试时间。如果发送的数据在重试时间内得不到应答,将重新发送数据。

(4) Default Connect Timeout in seconds

范围1-600,单位秒。

用于配置默认的保持连接时间,即我们常说的Keep Alive时间,如果时间到了将断开连接。常用于HTTP Server,Telnet Server等。

(5)  Maximum Segment Size

范围536-1460,单位字节。

MSS定义了TCP数据包能够传输的最大数据分段。

(6) Receive Window Size

范围536-65535,单位字节。

TCP接收窗口大小。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

FTP Client

FTP 配置,打上对勾就使能了此项功能

(1)  Response Timeout in seconds

FTP客户端等待FTP服务器响应时间,如果溢出,客户端将终止操作。

范围1-120,单位秒。

(2) Passive mode

被动模式,客户端发起到服务器的数据连接,详看第35章的35.3.5小节说明。

37.4 FTP调试说明(Net_Debug.c)

(重要说明,RL-TCPnet的调试是通过串口打印出来的)

RL-TCPnet的调试功能是通过配置文件Net_Debug.c实现。在MDK工程中打开文件Net_Debug.c,可以看到下图所示的工程配置向导:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

Print Time Stamp

勾选了此选项的话,打印消息时,前面会附带时间信息。

其它所有的选项

默认情况下,所有的调试选项都关闭了,每个选项有三个调试级别可选择,这里我们以FTP Client Debug为例,点击下拉列表,可以看到里面有Off,Errors only和Full debug三个调试级别可供选择,每个调试选项里面都是这三个级别。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

Off:表示关闭此选项的调试功能。

Errors only:表示仅在此选项出错时,将其错误打印出来。

Full debug:表示此选项的全功能调试。

具体测试,我们这里就不做了,大家可以按照第11章讲解的调试方法进行测试。

37.5 FTP服务器的建立方法和板子的操作步骤

本章节的测试稍麻烦些,需要大家配置工程,并且在电脑端建立一个FTP服务器,而开发板是作为客户端,并且采用SD卡作为存储介质(测试前要准备好一个SD卡插到开发板上面),所以大家测试本章节配套的例子前,务必将这里的操作步骤全部看完才可以做测试!

另外有一点特别注意,我们使用的是RL-FlashFS文件系统,此文件系统的文件名仅支持ASCII字符,不支持中文,对于中文名的文件夹或者文件是无法操作的,因此,电脑端创建FTP服务器的时候,使用的文件夹和文件名也不要有中文。

37.5.1 获取电脑的IP地址

获取电脑IP地址的方法很多,可以在网上邻居获取,也可以通过输入命令ipconfig获取:

  • WIN+R组合键打开“运行”窗口,输入cmd。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

  • 弹出的命令窗口中,输入ipconfig。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

  • 输入ipconfig后,回车。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

获得电脑的IP地址是192.168.1.4。

37.5.2 在程序中配置要访问的FTP服务器IP地址和端口

根据刚获得的IP地址,需要大家配置程序中app_tcpnet_lib.c文件开头的宏定义:

/*

*********************************************************************************************************

*                              宏定义,远程FTP服务器的IP和端口

*********************************************************************************************************

*/

/* 要访问的远程FTP服务器IP配置 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            4

#define PORT_NUM       21  /* FTP服务器,默认端口号是21,无需改动 */

37.5.3 在程序中配置FTP服务器的账号和文件管理参数

FTP服务器的账号和文件管理参数已经在FTPC_uif.c文件开头的宏定义中配置好,大家做测试是无需修改的。建议当前的功能测试完毕后,修改相关参数,做一些新的测试,以此来熟练掌握这些配置所代表的含义:

#define FTPC_USERNAME   "armfly"        /* 远程FTP服务器的账号 */

#define FTPC_PASSWORD   "123456"        /* 远程FTP服务器的密码 */

#define FTPC_PATH       ""              /* 要访问的子文件夹,FTP电脑端设置后是没有路径的,填空 */

#define FTPC_FILENAME   "server.pdf"    /* 要访问的文件 */

#define FTPC_NEWNAME    "renamed.pdf"   /* 使用这里定义的文件字重命名宏定义FTPC_FILENAME的文件名 */

#define FTPC_DIRNAME    "New_Folder"    /* 创建名字为New_Folder的文件夹或者删除名字为New_Folder的文件夹 */

#define FTPC_LISTNAME   "*"             /* 浏览宏定义FTPC_PATH路径下所有文件 */

#define LOCAL_FILE      "client.pdf"    /* 开发板SD卡里面要上传的文件,或者从FTP服务器下载文件后,

                                           文件会被设置成此名字 */

37.5.4 电脑端创建FTP服务器

第1步:下载FTP服务器软件

FTP软件推荐采用FileZilla Server,下载地址:http://bbs.armfly.com/read.php?tid=32586 。建议下载帖子里面二楼的中文版。

第2步:下载后,安装此软件,安装完毕后,打开软件的效果如下

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

使用默认参数,不用做任何修改,点击确定,弹出如下界面:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

第3步:设置一个可供外部访问的FTP服务器账户

点击编辑->用户:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

弹出如下界面:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

点击确定后,设置密码:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

第4步:创建要分享的文件夹

简单的在电脑桌面上创建一个文件夹,起名为good(任何其它地方均可,但建议不要有中文,防止测试不成功)。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

为了方便查看上传和下载文件的效果,找一个稍大些的文件放到此文件夹,这里将我们之前做的FreeRTOS教程放到这个新建的文件夹里面(已经将这个文件放在了本章节配套例子的Doc文件夹),起名为server.pdf,务必且只能设置成此名字,因为我们的程序中是配置成访问此文件。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

仅放这一个文件即可。现在就可以将这个good文件夹路径添加到FTP服务器软件上。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

添加完毕路径后,配置权限,将所有权限都使能:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

至此,就可以进行开发板的功能测试了。

37.5.5 开发板访问FTP服务器操作说明

FTP服务器创建完毕后,开发板访问FTP服务器就比较简单了。

第1步:将本章节配套的例子下载到开发板后,务必将SD卡插上,因为从FTP服务器上传或者下载文件要用到。

第2步:务必优先测试下载功能(第3步还要将下载的这个文件上传),按下开发板的K2按键,会将FTP服务器分享的server.pdf文件下载到SD卡中,下载后的文件起名叫client.pdf,文件是一样的,只是换了个名字做区分。

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

下载成功后,串口调试助手打印出如下信息(波特率115200,数据位8,奇偶校验位无,停止位1):

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

第3步:测试上传功能,为方便起见,需要大家将电脑端分享的server.pdf文件删掉,然后按下开发板上的K3按键,实现将第2步下载的server.pdf文件重新上传到FTP服务器:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

上传成功后,串口调试助手打印出如下信息:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

第4步:测试完毕上传下载功能后,按下摇杆上键,可以将good文件夹中的所有文件及其详情列出来,下面是串口调试助手显示的信息:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

上面打印出来的是FTP服务器端的server.pdf文件详情。

第5步:按下开发板上的摇杆左键,可以给good文件夹中创建一个新的文件夹New_Folder:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端 

第6步:按下开发板上的摇杆右键,可以将新创建的New_Folder文件夹删除:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

至此,开发板实现的几个功能已经都实现了,大家还可以测试下文件重命名、文件删除等功能,不过最主要的还是文件的上传和下载功能。大家测试的时候,别忘了实际打开文件看下,查看文件是否被损坏,以此来保证文件被成功传输了。

37.6 实验例程说明(RTX)

37.6.1 STM32F407开发板实验

配套例子:

V5-1054_RL-TCPnet实验_FTP客户端(RTX)

实验目的:

  1. 学习RL-TCPnet的FTP客户端实现。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
  2. FTP客户端的存储器是采用的SD卡,所以测试本例子前务必准备好一个SD卡并插上。
  3. 文件系统是采用的RL-FlashFS,此文件系统的文件名仅支持ASCII字符,不支持中文,特别注意!
  4. 远程FTP服务器的IP地址和端口号是在app_tcpnet_lib.c文件开头的宏定义设置。
  5. 需要上传下载的文件、文件夹的创建和删除、文件夹浏览等配置是在FTPC_uif.c文件开头的宏定义设置。
  6. 测试本例子,需要在电脑端先建立FTP服务器,具体建立方法和本例子的测试步骤在本实例配套教程里面有详细讲解,必看!
  7. K2按键按下,从FTP服务器下载文件到开发板的SD卡。
  8. K3按键按下,将开发板SD卡里面的文件上传到FTP服务器。
  9. 摇杆上键按下,浏览FTP服务器当前目录下的所有文件详情。
  10. 摇杆左键按下,创建一个文件夹。
  11. 摇杆右键按下,删除一个文件夹。

实验操作:

详见本章节37.5小节。

配置向导文件设置(Net_Config.c):

详见本章节37.3小节。

调试文件设置(Net_Debug.c):

详见本章节37.4小节。

RTX配置:

RTX配置向导详情如下:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

Task Configuration

(1)Number of concurrent running tasks

允许创建6个任务,实际创建了如下5个任务:

AppTaskUserIF任务   :按键消息处理。

AppTaskLED任务     :LED闪烁。

AppTaskMsgPro任务 :按键检测。

AppTaskTCPMain任务:RL-TCPnet测试任务。

AppTaskStart任务  :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

(2)Number of tasks with user-provided stack

创建的5个任务都是采用自定义堆栈方式。

(3)Run in privileged mode

设置任务运行在非特权级模式。

RTX任务调试信息:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

程序设计:

任务栈大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任务栈 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任务栈 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任务栈 */

static uint64_t AppTaskTCPMainStk[4096/8]; /* 任务栈 */

static uint64_t AppTaskStartStk[1024/8];     /* 任务栈 */

将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。

系统栈大小分配:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

RTX初始化:

/*

*********************************************************************************************************

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

     os_sys_init_user (AppTaskStart,              /* 任务函数 */

                       ,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

                       sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */

     while();

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

*********************************************************************************************************

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

*********************************************************************************************************

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     bsp_InitDWT();     /* 初始化DWT */

     bsp_InitUart();    /* 初始化串口 */

     bsp_InitKey();    /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

     bsp_InitLed();    /* 初始LED指示灯端口 */

     MountSD();        /* 挂载SD卡 */

}

RTX任务创建:

/*

*********************************************************************************************************

*    函 数 名: AppTaskCreate

*    功能说明: 创建应用任务

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

                                           sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任务函数 */

                                        ,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

                                        sizeof(AppTaskLEDStk));  /* 任务栈大小,单位字节数 */

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

                                           sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

                                           sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */

}

五个RTX任务的实现:

/*

*********************************************************************************************************

*    函 数 名: AppTaskUserIF

*    功能说明: 按键消息处理     

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)

*********************************************************************************************************

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while()

    {

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下 */

                   case KEY_DOWN_K1:

                       printf("K1键按下\r\n");        

                       break;  

                   /* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */

                   case KEY_DOWN_K2:

                       printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置\r\n");

                       os_evt_set (KEY2_BIT1, HandleTaskTCPMain);

                       break;

                   /* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */

                   case KEY_DOWN_K3:

                       printf("K3键按下,直接发送事件标志给任务AppTaskTCPMain,bit2被设置\r\n");

                       os_evt_set (KEY3_BIT2, HandleTaskTCPMain);

                       break;

                   /* 摇杆上键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit3 */

                   case JOY_DOWN_U:

                       printf("摇杆上键按下,直接发送事件标志给任务AppTaskTCPMain,bit3被设置\r\n");

                       os_evt_set (JOY_U_BIT3, HandleTaskTCPMain);

                       break;                

                   /* 摇杆左键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit4 */

                   case JOY_DOWN_L:

                       printf("摇杆左键按下,直接发送事件标志给任务AppTaskTCPMain,bit4被设置\r\n");

                       os_evt_set (JOY_L_BIT4, HandleTaskTCPMain);

                       break;

                   /* 摇杆右键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit5 */

                   case JOY_DOWN_R:

                       printf("摇杆右键按下,直接发送事件标志给任务AppTaskTCPMain,bit5被设置\r\n");

                       os_evt_set (JOY_R_BIT5, HandleTaskTCPMain);

                       break;

                   /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

         os_dly_wait();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

*********************************************************************************************************

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = ; /* 延迟周期 */

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while()

    {

         bsp_LedToggle();

         /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/

         os_itv_wait();

    }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

*********************************************************************************************************

*/

__task void AppTaskMsgPro(void)

{

    while()

    {

         bsp_KeyScan();

         os_dly_wait();

    }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

*********************************************************************************************************

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

         TCPnetTest();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

*********************************************************************************************************

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

         timer_tick ();

         os_evt_set(0x0001, HandleTaskTCPMain);

    }

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现开发板发给FTP服务器的文件操作命令和网络主函数main_TcpNet的调用。

#include "includes.h"

/*

*********************************************************************************************************

*                                      用于本文件的调试

*********************************************************************************************************

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

*********************************************************************************************************

*                              宏定义,远程FTP服务器的IP和端口

*********************************************************************************************************

*/

/* 要访问的远程FTP服务器IP配置 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            4

#define PORT_NUM       21  /* FTP服务器,默认端口号是21,无需改动 */

/*

*********************************************************************************************************

*                                         变量

*********************************************************************************************************

*/

uint8_t ServerIP[] = {IP1, IP2, IP3, IP4};

/*

*********************************************************************************************************

*    函 数 名: ftpc_notify

*    功能说明: 函数ftpc_connect的回调函数。

*    形    参: event  事件类型

*    返 回 值: 无

*********************************************************************************************************

*/

static void ftpc_notify (U8 event)

{

     switch (event)

     {

         /* 文件操作成功 */

         case FTPC_EVT_SUCCESS:

              printf_debug ("Command successful\n");

              break;

         /* 失败,因为FTP服务器响应时间溢出,因此FTP客户端终止操作 */

         case FTPC_EVT_TIMEOUT:

              printf_debug ("Failed, timeout expired\n");

              break;

         /* 失败,FTP客户端登陆FTP服务器失败 */

         case FTPC_EVT_LOGINFAIL:

              printf ("Failed, username/password invalid\n");

              break;

         /* 失败,禁止操作此文件 */

         case FTPC_EVT_NOACCESS:

              printf_debug ("Failed, operation not allowed\n");

              break;

         /* 失败,在FTP服务器上找不到请求的文件 */

         case FTPC_EVT_NOTFOUND:

              printf_debug ("Failed, file or path not found\n");

              break;

         /* 失败,在FTP服务器上找不到工作目录路径 */

         case FTPC_EVT_NOPATH:

              printf ("Failed, working directory not found\n");

              break;

         /* 失败,文件打开或者写入出错,即FTP客户端操作SD卡或者其它存储介质出错 */

         case FTPC_EVT_ERRLOCAL:

              printf_debug ("Failed, local file open/write error\n");

              break;

         /* 失败,未指定的协议错误,或者说在文件操作过程中遇到错误 */

         case FTPC_EVT_ERROR:

              printf_debug ("Failed, unspecified protocol error\n");

              break;

     }

}

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPent测试函数。

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{

     OS_RESULT xResult;

     while ()

     {

         os_evt_wait_or(0x003F, 0xFFFF); 

         xResult = os_evt_get ();

         switch (xResult)

         {

              /* 接收到K2键按下,发送FTPC_CMD_GET命令,表示从FTP服务器下载文件 */

              case KEY2_BIT1:   

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_GET, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

              /* 接收到K3键按下,发送FTPC_CMD_PUT命令,表示向FTP服务器上传文件 */

              case KEY3_BIT2:              

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_PUT, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

              /* 接收到摇杆上键按下,发送FTPC_CMD_LIST命令,表示列出当前目录下所有文件详细信息 */

              case JOY_U_BIT3:             

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_LIST, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

              /* 接收到摇杆左键按下,发送FTPC_CMD_MKDIR命令,表示创建一个文件夹 */

              case JOY_L_BIT4:

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_MKDIR, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

         /* 接收到摇杆右键按下,发送FTPC_CMD_RMDIR命令,表示删除一个文件夹,文件夹为空的时候才可以删除 */

              case JOY_R_BIT5:  

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_RMDIR, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

               /* 其他的键值不处理 */

              default:                    

                   break;

         }

         while (main_TcpNet() == __TRUE);

     }

}

FTP用户接口文件的实现

KEIL官网有提供FTP的接口文件,名为FTPC_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:

#include <Net_Config.h>

#include <stdio.h>

#define FTPC_USERNAME   "armfly"       /* 远程FTP服务器的账号 */

#define FTPC_PASSWORD   "123456"       /* 远程FTP服务器的密码 */

#define FTPC_PATH       ""             /* 要访问的子文件夹,FTP电脑端设置后是没有路径的,填空 */

#define FTPC_FILENAME   "server.pdf"   /* 要访问的文件 */

#define FTPC_NEWNAME    "renamed.pdf"  /* 使用这里定义的文件字重命名宏定义FTPC_FILENAME的文件名 */

#define FTPC_DIRNAME    "New_Folder"    /* 创建名字为New_Folder的文件夹或者删除名字为New_Folder的文件夹 */

#define FTPC_LISTNAME   "*"            /* 浏览宏定义FTPC_PATH路径下所有文件 */

#define LOCAL_FILE      "client.pdf"   /* 开发板SD卡里面要上传的文件,或者从FTP服务器下载文件后,

                                          文件会被设置成此名字 */

/*----------------------------------------------------------------------------

 *      FTP Client File Access and Data CallBack Functions

 *---------------------------------------------------------------------------*/

/*--------------------------- ftpc_fopen ------------------------------------*/

void *ftpc_fopen (U8 *mode) {

  /* Open local file for reading or writing. If the return value is NULL, */

  /* processing of FTP Client commands PUT, APPEND or GET is cancelled.   */

  return (fopen (LOCAL_FILE, (char *)mode));

}

/*--------------------------- ftpc_fclose -----------------------------------*/

void ftpc_fclose (void *file) {

  /* Close a local file. */

  fclose (file);

}

/*--------------------------- ftpc_fread ------------------------------------*/

U16 ftpc_fread (void *file, U8 *buf, U16 len) {

  /* Read 'len' bytes from file to buffer 'buf'. Return number of bytes   */

  /* copied. The file will be closed, when the return value is 0.         */

  /* For optimal performance the return value should be 'len'             */

  return (fread (buf, , len, file));

}

/*--------------------------- ftpc_fwrite -----------------------------------*/

U16 ftpc_fwrite (void *file, U8 *buf, U16 len) {

  /* Write 'len' bytes from buffer 'buf' to a file. Data transfer will be */

  /* aborted, if the return value is not equal 'len'.                     */

  return (fwrite (buf, , len, file));

}

/*--------------------------- ftpc_cbfunc -----------------------------------*/

U16 ftpc_cbfunc (U8 code, U8 *buf, U16 buflen) {

  /* This function is called by the FTP client to get parameters. It returns*/

  /* the number of bytes written to buffer 'buf'. This function should NEVER*/

  /* write more than 'buflen' bytes to this buffer.                         */

  /* Parameters:                                                            */

  /*   code   - function code with following values:                        */

  /*             0 = Username for FTP authentication                        */

  /*             1 = Password for FTP authentication                        */

  /*             2 = Working directory path for all commands                */

  /*             3 = Filename for PUT, GET, APPEND, DELETE, RENAME command  */

  /*             4 - New filename for RENAME command                        */

  /*             5 - Directory name for MKDIR, RMDIR command                */

  /*             6 - File filter/mask for LIST command (wildcards allowed)  */

  /*             7 = Received directory listing on LIST command             */

  /*   buf    - transmit/receive buffer                                     */

  /*   buflen - length of this buffer                                       */

  /*             on transmit, it specifies size of 'buf'                    */

  /*             on receive, specifies the number of bytes received         */

  U32 i,len = ;

  switch (code) {

    case :

      /* Enter Username for login. */

      len = str_copy (buf, FTPC_USERNAME);

      break;

    case :

      /* Enter Password for login. */

      len = str_copy (buf, FTPC_PASSWORD);

      break;

    case :

      /* Enter a working directory path. */

      len = str_copy (buf, FTPC_PATH);

      break;

    case :

      /* Enter a filename for file operations. */

      len = str_copy (buf, FTPC_FILENAME);

      break;

    case :

      /* Enter a new name for rename command. */

      len = str_copy (buf, FTPC_NEWNAME);

      break;

    case :

      /* Enter a directory name to create or remove. */

      len = str_copy (buf, FTPC_DIRNAME);

      break;

    case :

      /* Enter a file filter for list command. */

      len = str_copy (buf, FTPC_LISTNAME);

      break;

    case :

      /* Process received directory listing in raw format. */

      /* Function return value is don't care.              */

      for (i = ; i < buflen; i++) {

        putchar (buf[i]);

      }

      break;

  }

  return ((U16)len);

}

/*----------------------------------------------------------------------------

 * end of file

 *---------------------------------------------------------------------------*/

37.6.2 STM32F429开发板实验

配套例子:

V6-1054_RL-TCPnet实验_FTP客户端(RTX)

实验目的:

  1. 学习RL-TCPnet的FTP客户端实现。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
  2. FTP客户端的存储器是采用的SD卡,所以测试本例子前务必准备好一个SD卡并插上。
  3. 文件系统是采用的RL-FlashFS,此文件系统的文件名仅支持ASCII字符,不支持中文,特别注意!
  4. 远程FTP服务器的IP地址和端口号是在app_tcpnet_lib.c文件开头的宏定义设置。
  5. 需要上传下载的文件、文件夹的创建和删除、文件夹浏览等配置是在FTPC_uif.c文件开头的宏定义设置。
  6. 测试本例子,需要在电脑端先建立FTP服务器,具体建立方法和本例子的测试步骤在本实例配套教程里面有详细讲解,必看!!
  7. K2按键按下,从FTP服务器下载文件到开发板的SD卡。
  8. K3按键按下,将开发板SD卡里面的文件上传到FTP服务器。
  9. 摇杆上键按下,浏览FTP服务器当前目录下的所有文件详情。
  10. 摇杆左键按下,创建一个文件夹。
  11. 摇杆右键按下,删除一个文件夹。

实验操作:

详见本章节37.5小节。

配置向导文件设置(Net_Config.c):

详见本章节37.3小节。

调试文件设置(Net_Debug.c):

详见本章节37.4小节。

RTX配置:

RTX配置向导详情如下:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

Task Configuration

(1)Number of concurrent running tasks

允许创建6个任务,实际创建了如下5个任务:

AppTaskUserIF任务   :按键消息处理。

AppTaskLED任务     :LED闪烁。

AppTaskMsgPro任务 :按键检测。

AppTaskTCPMain任务:RL-TCPnet测试任务。

AppTaskStart任务  :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

(2)Number of tasks with user-provided stack

创建的5个任务都是采用自定义堆栈方式。

(3)Run in privileged mode

设置任务运行在非特权级模式。

RTX任务调试信息:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

程序设计:

任务栈大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任务栈 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任务栈 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任务栈 */

static uint64_t AppTaskTCPMainStk[4096/8]; /* 任务栈 */

static uint64_t AppTaskStartStk[1024/8];     /* 任务栈 */

将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。

系统栈大小分配:

【RL-TCPnet网络教程】第37章	 RL-TCPnet之FTP客户端

RTX初始化:

/*

*********************************************************************************************************

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

     os_sys_init_user (AppTaskStart,              /* 任务函数 */

                       ,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

                       sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */

     while();

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

*********************************************************************************************************

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

*********************************************************************************************************

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     SystemCoreClockUpdate();    /* 根据PLL配置更新系统时钟频率变量 SystemCoreClock */

     bsp_InitDWT();      /* 初始化DWT */

     bsp_InitUart();     /* 初始化串口 */

     bsp_InitKey();     /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

     bsp_InitExtIO();    /* FMC总线上扩展了32位输出IO, 操作LED等外设必须初始化 */

     bsp_InitLed();      /* 初始LED指示灯端口 */

     MountSD();          /* 挂载SD卡 */

}

RTX任务创建:

/*

*********************************************************************************************************

*    函 数 名: AppTaskCreate

*    功能说明: 创建应用任务

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

                                           sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任务函数 */

                                        ,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

                                        sizeof(AppTaskLEDStk));  /* 任务栈大小,单位字节数 */

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

                                           sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

                                           sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */

}

五个RTX任务的实现:

/*

*********************************************************************************************************

*    函 数 名: AppTaskUserIF

*    功能说明: 按键消息处理     

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)

*********************************************************************************************************

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while()

    {

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下 */

                   case KEY_DOWN_K1:

                       printf("K1键按下\r\n");        

                       break;  

                   /* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */

                   case KEY_DOWN_K2:

                       printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置\r\n");

                       os_evt_set (KEY2_BIT1, HandleTaskTCPMain);

                       break;

                   /* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */

                   case KEY_DOWN_K3:

                       printf("K3键按下,直接发送事件标志给任务AppTaskTCPMain,bit2被设置\r\n");

                       os_evt_set (KEY3_BIT2, HandleTaskTCPMain);

                       break;

                   /* 摇杆上键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit3 */

                   case JOY_DOWN_U:

                       printf("摇杆上键按下,直接发送事件标志给任务AppTaskTCPMain,bit3被设置\r\n");

                       os_evt_set (JOY_U_BIT3, HandleTaskTCPMain);

                       break;                

                   /* 摇杆左键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit4 */

                   case JOY_DOWN_L:

                       printf("摇杆左键按下,直接发送事件标志给任务AppTaskTCPMain,bit4被设置\r\n");

                       os_evt_set (JOY_L_BIT4, HandleTaskTCPMain);

                       break;

                   /* 摇杆右键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit5 */

                   case JOY_DOWN_R:

                       printf("摇杆右键按下,直接发送事件标志给任务AppTaskTCPMain,bit5被设置\r\n");

                       os_evt_set (JOY_R_BIT5, HandleTaskTCPMain);

                       break;

                   /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

         os_dly_wait();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

*********************************************************************************************************

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = ; /* 延迟周期 */

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while()

    {

         bsp_LedToggle();

         /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/

         os_itv_wait();

    }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

*********************************************************************************************************

*/

__task void AppTaskMsgPro(void)

{

    while()

    {

         bsp_KeyScan();

         os_dly_wait();

    }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

*********************************************************************************************************

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

         TCPnetTest();

     }

}

/*

*********************************************************************************************************

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

*********************************************************************************************************

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

         timer_tick ();

         os_evt_set(0x0001, HandleTaskTCPMain);

    }

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现开发板发给FTP服务器的文件操作命令和网络主函数main_TcpNet的调用。

#include "includes.h"

/*

*********************************************************************************************************

*                                      用于本文件的调试

*********************************************************************************************************

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

*********************************************************************************************************

*                             宏定义,远程FTP服务器的IP和端口

*********************************************************************************************************

*/

/* 要访问的远程FTP服务器IP配置 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            4

#define PORT_NUM       21  /* FTP服务器,默认端口号是21,无需改动 */

/*

*********************************************************************************************************

*                                         变量

*********************************************************************************************************

*/

uint8_t ServerIP[] = {IP1, IP2, IP3, IP4};

/*

*********************************************************************************************************

*    函 数 名: ftpc_notify

*    功能说明: 函数ftpc_connect的回调函数。

*    形    参: event  事件类型

*    返 回 值: 无

*********************************************************************************************************

*/

static void ftpc_notify (U8 event)

{

     switch (event)

     {

         /* 文件操作成功 */

         case FTPC_EVT_SUCCESS:

              printf_debug ("Command successful\n");

              break;

         /* 失败,因为FTP服务器响应时间溢出,因此FTP客户端终止操作 */

         case FTPC_EVT_TIMEOUT:

              printf_debug ("Failed, timeout expired\n");

              break;

         /* 失败,FTP客户端登陆FTP服务器失败 */

         case FTPC_EVT_LOGINFAIL:

              printf ("Failed, username/password invalid\n");

              break;

         /* 失败,禁止操作此文件 */

         case FTPC_EVT_NOACCESS:

              printf_debug ("Failed, operation not allowed\n");

              break;

         /* 失败,在FTP服务器上找不到请求的文件 */

         case FTPC_EVT_NOTFOUND:

              printf_debug ("Failed, file or path not found\n");

              break;

         /* 失败,在FTP服务器上找不到工作目录路径 */

         case FTPC_EVT_NOPATH:

              printf ("Failed, working directory not found\n");

              break;

         /* 失败,文件打开或者写入出错,即FTP客户端操作SD卡或者其它存储介质出错 */

         case FTPC_EVT_ERRLOCAL:

              printf_debug ("Failed, local file open/write error\n");

              break;

         /* 失败,未指定的协议错误,或者说在文件操作过程中遇到错误 */

         case FTPC_EVT_ERROR:

              printf_debug ("Failed, unspecified protocol error\n");

              break;

     }

}

/*

*********************************************************************************************************

*    函 数 名: TCPnetTest

*    功能说明: TCPent测试函数。

*    形    参: 无

*    返 回 值: 无

*********************************************************************************************************

*/

void TCPnetTest(void)

{

     OS_RESULT xResult;

     while ()

     {

         os_evt_wait_or(0x003F, 0xFFFF); 

         xResult = os_evt_get ();

         switch (xResult)

         {

              /* 接收到K2键按下,发送FTPC_CMD_GET命令,表示从FTP服务器下载文件 */

              case KEY2_BIT1:   

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_GET, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

              /* 接收到K3键按下,发送FTPC_CMD_PUT命令,表示向FTP服务器上传文件 */

              case KEY3_BIT2:              

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_PUT, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

              /* 接收到摇杆上键按下,发送FTPC_CMD_LIST命令,表示列出当前目录下所有文件详细信息 */

              case JOY_U_BIT3:             

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_LIST, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

              /* 接收到摇杆左键按下,发送FTPC_CMD_MKDIR命令,表示创建一个文件夹 */

              case JOY_L_BIT4:

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_MKDIR, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

         /* 接收到摇杆右键按下,发送FTPC_CMD_RMDIR命令,表示删除一个文件夹,文件夹为空的时候才可以删除 */

              case JOY_R_BIT5:  

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_RMDIR, ftpc_notify) == )

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

               /* 其他的键值不处理 */

              default:                    

                   break;

         }

         while (main_TcpNet() == __TRUE);

     }

}

FTP用户接口文件的实现

KEIL官网有提供FTP的接口文件,名为FTPC_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:

#include <Net_Config.h>

#include <stdio.h>

#define FTPC_USERNAME   "armfly"       /* 远程FTP服务器的账号 */

#define FTPC_PASSWORD   "123456"       /* 远程FTP服务器的密码 */

#define FTPC_PATH       ""             /* 要访问的子文件夹,FTP电脑端设置后是没有路径的,填空 */

#define FTPC_FILENAME   "server.pdf"   /* 要访问的文件 */

#define FTPC_NEWNAME    "renamed.pdf"  /* 使用这里定义的文件字重命名宏定义FTPC_FILENAME的文件名 */

#define FTPC_DIRNAME    "New_Folder"    /* 创建名字为New_Folder的文件夹或者删除名字为New_Folder的文件夹 */

#define FTPC_LISTNAME   "*"            /* 浏览宏定义FTPC_PATH路径下所有文件 */

#define LOCAL_FILE      "client.pdf"   /* 开发板SD卡里面要上传的文件,或者从FTP服务器下载文件后,

                                          文件会被设置成此名字 */

/*----------------------------------------------------------------------------

 *      FTP Client File Access and Data CallBack Functions

 *---------------------------------------------------------------------------*/

/*--------------------------- ftpc_fopen ------------------------------------*/

void *ftpc_fopen (U8 *mode) {

  /* Open local file for reading or writing. If the return value is NULL, */

  /* processing of FTP Client commands PUT, APPEND or GET is cancelled.   */

  return (fopen (LOCAL_FILE, (char *)mode));

}

/*--------------------------- ftpc_fclose -----------------------------------*/

void ftpc_fclose (void *file) {

  /* Close a local file. */

  fclose (file);

}

/*--------------------------- ftpc_fread ------------------------------------*/

U16 ftpc_fread (void *file, U8 *buf, U16 len) {

  /* Read 'len' bytes from file to buffer 'buf'. Return number of bytes   */

  /* copied. The file will be closed, when the return value is 0.         */

  /* For optimal performance the return value should be 'len'             */

  return (fread (buf, , len, file));

}

/*--------------------------- ftpc_fwrite -----------------------------------*/

U16 ftpc_fwrite (void *file, U8 *buf, U16 len) {

  /* Write 'len' bytes from buffer 'buf' to a file. Data transfer will be */

  /* aborted, if the return value is not equal 'len'.                     */

  return (fwrite (buf, , len, file));

}

/*--------------------------- ftpc_cbfunc -----------------------------------*/

U16 ftpc_cbfunc (U8 code, U8 *buf, U16 buflen) {

  /* This function is called by the FTP client to get parameters. It returns*/

  /* the number of bytes written to buffer 'buf'. This function should NEVER*/

  /* write more than 'buflen' bytes to this buffer.                         */

  /* Parameters:                                                            */

  /*   code   - function code with following values:                        */

  /*             0 = Username for FTP authentication                        */

  /*             1 = Password for FTP authentication                        */

  /*             2 = Working directory path for all commands                */

  /*             3 = Filename for PUT, GET, APPEND, DELETE, RENAME command  */

  /*             4 - New filename for RENAME command                        */

  /*             5 - Directory name for MKDIR, RMDIR command                */

  /*             6 - File filter/mask for LIST command (wildcards allowed)  */

  /*             7 = Received directory listing on LIST command             */

  /*   buf    - transmit/receive buffer                                     */

  /*   buflen - length of this buffer                                       */

  /*             on transmit, it specifies size of 'buf'                    */

  /*             on receive, specifies the number of bytes received         */

  U32 i,len = ;

  switch (code) {

    case :

      /* Enter Username for login. */

      len = str_copy (buf, FTPC_USERNAME);

      break;

    case :

      /* Enter Password for login. */

      len = str_copy (buf, FTPC_PASSWORD);

      break;

    case :

      /* Enter a working directory path. */

      len = str_copy (buf, FTPC_PATH);

      break;

    case :

      /* Enter a filename for file operations. */

      len = str_copy (buf, FTPC_FILENAME);

      break;

    case :

      /* Enter a new name for rename command. */

      len = str_copy (buf, FTPC_NEWNAME);

      break;

    case :

      /* Enter a directory name to create or remove. */

      len = str_copy (buf, FTPC_DIRNAME);

      break;

    case :

      /* Enter a file filter for list command. */

      len = str_copy (buf, FTPC_LISTNAME);

      break;

    case :

      /* Process received directory listing in raw format. */

      /* Function return value is don't care.              */

      for (i = ; i < buflen; i++) {

        putchar (buf[i]);

      }

      break;

  }

  return ((U16)len);

}

/*----------------------------------------------------------------------------

 * end of file

 *---------------------------------------------------------------------------*/

37.7 总结

本章节就为大家讲解这么多,其中FTP的测试稍麻烦些,希望大家实际动手操作一遍,并将其熟练掌握。