第36章 RL-TCPnet之FTP服务器
本章节为大家讲解RL-TCPnet的FTP服务器应用,学习本章节前,务必要优先学习第35章的FTP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。
本章教程含STM32F407开发板和STM32F429开发板。
36.1 初学者重要提示
36.2 FTP函数
36.3 FTP配置说明(Net_Config.c)
36.4 FTP调试说明(Net_Debug.c)
36.5 FTP访问方法和板子的操作步骤
36.6 实验例程说明(裸机)
36.7 实验例程说明(RTX)
36.8 总结
36.1 初学者重要提示
- 学习本章节前,务必保证已经学习了第35章的基础知识。
- 本章配套的例子是将开发板作为FTP服务器,使用开发板上面的SD卡作为服务器的存储介质。所以测试本章节的例子,务必要准备一个SD卡。
- 由于配套例子的文件系统是采用的RL-FlashFS,此文件系统的文件名仅支持ASCII字符,不支持中文,特别注意!
- 具体FTP服务器的访问方法在本章的36.5小节有详细说明。
36.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 文件:
这里我们重点的说以下 9个函数,因为本章节配套的例子使用的是这9个函数:
- ftp_fopen
- ftp_fclose
- ftp_fread
- ftp_fwrite
- ftp_fdelete
- ftp_frename
- ftp_ffind
- ftp_accept_host
- ftp_evt_notify
关于这些函数注意以下三点:
- FTP的所有函数都不支持重入,也就是不支持多任务调用。
- 以ftp_开头的函数是用于FTP服务器的。
- 以ftpc_开头的函数是用于FTP客户端的。
36.2.1 函数ftp_fopen
函数原型:
void *ftp_fopen ( U8* fname, /* 文件名地址 */ U8* mode); /* 操作模式 */
函数描述:
函数ftp_fopen用于打开文件。此函数在MDK的安装目录中的FTP_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
使用举例:
void *ftp_fopen (U8 *fname, U8 *mode) { /* 打开文件 */ return (fopen ((const char *)fname, (const char *)mode)); }
36.2.2 函数ftp_fclose
函数原型:
void *ftp_fclose (
FILE* file); /* 文件句柄地址 */
函数描述:
函数ftp_fclose用于关闭文件。此函数在MDK的安装目录中的FTP_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
- 第1个参数是要关闭的文件句柄地址。
- 返回值,实际上此函数无需返回任何数值,写成下面使用举例中的形式即可。
使用举例:
void ftp_fclose (void *file) { /* 关闭文件 */ fclose (file); }
36.2.3 函数ftp_fread
函数原型:
U16 ftp_fread ( FILE* file, /* 文件句柄地址 */ U8* buf, /* 数据缓冲地址 */ U16 len ); /* 要读取的字节数 */
函数描述:
函数ftp_fread用于从文件中读出len个字节数据。此函数在MDK的安装目录中的FTP_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
- 第1个参数是要读取数据的文件句柄地址。
- 第2个参数是数据缓冲地址,用于存储读取出来的数据。
- 第3个参数是要读取出来的数据大小,单位字节。
- 返回值,返回从文件中实际读出的字节数。
使用这个函数要注意以下问题:
- 设置读取函数时,必须设置指定大小的字节数。如果实际读出的字节数小于len,将停止读取并关闭文件,这种情况一般都是文件已经读取完毕。
使用举例:
U16 ftp_fread (void *file, U8 *buf, U16 len) { /* 读取len字节到buf中,当此函数的返回值,即实际读取的字节数小于len的时候,说明文件已经读取完毕, 文件将被关闭*/ return (fread (buf, 1, len, file)); }
36.2.4 函数ftp_fwrite
函数原型:
U16 ftp_fwrite ( FILE* file, /* 文件句柄地址 */ U8* buf, /* 数据缓冲地址 */ U16 len ); /* 要写入的字节数 */
函数描述:
函数ftp_fwrite用于往文件中写入len个字节数据。此函数在MDK的安装目录中的FTP_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
- 第1个参数是要写入数据的文件句柄地址。
- 第2个参数是数据缓冲地址,存储要写入的数据。
- 第3个参数是要写入的数据大小,单位字节。
- 返回值,返回实际写入文件的字节数。
使用举例:
U16 ftp_fwrite (FILE *file, U8 *buf, U16 len) { /* 将buf中的len字节写入到文件中 */ return (fwrite (buf, 1, len, file)); }
36.2.5 函数ftp_fdelete
函数原型:
BOOL ftp_fdelete ( U8* fname ); /* 文件名 */
函数描述:
函数ftp_fdelete用于删除文件或文件夹。此函数在MDK的安装目录中的FTP_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
- 第1个参数要删除的文件或者文件夹名字。
- 返回值,返回__TRUE表示删除成功,返回__FALSE表示删除失败。
使用举例:
BOOL ftp_fdelete (U8 *fname) { /* 删除文件,返回__TRUE表示删除成功 */ if (fdelete((char *)fname) == 0) { return (__TRUE); } /* 返回__FALSE表示删除失败 */ return (__FALSE); }
36.2.6 函数ftp_frename
函数原型:
BOOL ftp_frename ( U8* fname, /* 老文件名 */ U8* newn ); /* 新文件名 */
函数描述:
函数ftp_frename用于文件或者文件夹的重命名。要操作的文件或者文件夹名字fname必须存在,而新命名的名字newn必须是当前目录不存在的。
- 第1个参数是要替换的文件或者文件夹名字,即老的文件名。
- 第2个参数是新命名文件或者文件夹名字,即新的文件名。
- 返回值,返回__TRUE表示重命名成功,返回__FALSE表示重命名失败。
使用举例:
BOOL ftp_frename (U8 *fname, U8 *newn) { /* 文件重命名,返回__TRUE表示重命名成功 */ if (frename((char *)fname, (char *)newn) == 0) { return (__TRUE); } /* 返回__FALSE表示重命名失败 */ return (__FALSE); }
36.2.7 函数ftp_ffind
函数原型:
U16 ftp_ffind ( U8 code, /* 请求类型 */ U8* buf, /* 输出缓冲区 */ U8* mask, /* 检索的字符 */ U16 buflen ); /* 输出缓冲区大小,单位字节 */
函数描述:
函数ftp_ffind用于在文件系统目录中搜索与参数mask匹配的文件。匹配的文件信息会被存储到数据缓冲区buf中。输出数据必须以FTP文件夹列表格式进行存储。
- 第1个参数是请求类型:
- 第2个参数是输出缓冲区地址,用于存储文件信息和数据。
- 第3个参数用于文件检索的字符,类似于我们在电脑端输入关键字符进行文件或者文件夹的检索。
- 第4个参数是输出缓冲区的大小,单位字节。
- 返回值,返回写入到输出缓冲区的字节数。
使用举例:
U16 ftp_ffind (U8 code, U8 *buf, U8 *mask, U16 len) { /* Find file names and other file information. */ static FINFO info; U32 rlen,v; U8 *tp; if (code < 4) { /* First call to ffind, initialize the info. */ info.fileID = 0; } rlen = 0; next: if (ffind ((char *)mask, &info) == 0) { /* File found, print file information. */ if (info.name[0] == '.') { if ((info.name[1] == 0) || (info.name[1] == '.' && info.name[2]) == 0) { /* Ignore the '.' and '..' folders. */ goto next; } } switch (code) { case 0: /* Return file size as decimal number. */ rlen = sprintf ((char *)buf,"%d\r\n", info.size); break; case 1: /* Return last-modified time in format "YYYYMMDDhhmmss". */ rlen = sprintf ((char *)buf,"%04d%02d%02d", info.time.year, info.time.mon, info.time.day); rlen += sprintf ((char *)&buf[rlen],"%02d%02d%02d\r\n", info.time.hr, info.time.min, info.time.sec); break; case 2: case 4: /* List file names only. */ rlen = sprintf ((char *)buf,"%s\r\n", info.name); break; case 3: case 5: /* List directory in extended format. */ rlen = sprintf ((char *)buf,"%02d-%02d-%02d", info.time.mon, info.time.day, info.time.year%100); /* Convert time to "AM/PM" format. */ v = info.time.hr % 12; if (v == 0) v = 12; if (info.time.hr < 12) tp = "AM"; else tp = "PM"; rlen += sprintf ((char *)&buf[rlen]," %02d:%02d%s",v,info.time.min,tp); if (info.attrib & ATTR_DIRECTORY) { rlen += sprintf ((char *)&buf[rlen],"%-21s",""); } else { rlen += sprintf ((char *)&buf[rlen],"%21d", info.size); } rlen += sprintf ((char *)&buf[rlen]," %s\r\n", info.name); break; } } return (rlen); }
36.2.8 函数ftp_accept_host
函数原型:
BOOL ftp_accept_host ( U8* rem_ip, /* 远程设备IP地址 */ U16 rem_port ); /* 远程设备端口号 */
函数描述:
函数ftp_accept_host用于设置是否接受远程连接,用户可以通过此函数选择允许哪些设备可以连接,哪些不可以连接。
- 第1个参数是远程设备的IP地址。
- 第2个参数是远程设备的端口号。
- 返回值,返回__TRUE表示允许此远程连接,返回__FALSE表示不允许此远程连接。
使用这个函数要注意以下问题:
- 此函数是可选的,如果大家在工程中没有写这个函数,RL-TCPnet库会调用默认的函数,允许所有的连接请求,如果在工程中写了此函数,会执行新写的这个函数。
使用举例:
BOOL ftp_accept_host (U8 *rem_ip, U16 rem_port) { if (rem_ip[0] == 192 && rem_ip[1] == 168 && rem_ip[2] == 1 && rem_ip[3] == 1) { /* 接受此连接. */ return (__TRUE); } /* 拒绝此连接 */ return (__FALSE); }
36.2.9 函数ftp_evt_notify
函数原型:
void ftp_evt_notify (U8 evt); /* 事件类型 */
函数描述:
函数ftp_evt_notify用于通知用户应用程序:FTP服务器中的事件。通过这些事件消息可以做很多有用的事情,比如服务器的固件升级,接收到消息FTP_EVT_LOGOUT后,表示服务器已经接收到固件,可以更新固件了。
- 第1个参数是FTP服务器的事件类型。
使用这个函数要注意以下问题:
- 此函数是可选的,一般正常的FTP服务器操作无需此函数。
使用举例:
void ftp_evt_notify (U8 evt) { /* Notify the user application about events in FTP server.*/ switch (evt) { case FTP_EVT_LOGIN: /* User logged in, FTP session is busy. */ break; case FTP_EVT_LOGOUT; /* User logged out, session is idle. */ break; case FTP_EVT_LOGFAIL: /* User login failed (invalid credentials). */ break; case FTP_EVT_DOWNLOAD: /* File download ended. */ break; case FTP_EVT_UPLOAD: /* File upload ended. */ break; case FTP_EVT_DELETE: /* File deleted. */ break; case FTP_EVT_RENAME: /* File or directory renamed. */ break; case FTP_EVT_MKDIR: /* Directory created. */ break; case FTP_EVT_RMDIR: /* Directory removed. */ break; case FTP_EVT_ERRLOCAL: /* Local file operation error. */ break; case FTP_EVT_DENIED: /* Requested file operation denied. */ break; case FTP_EVT_ERROR: /* Generic file operation or protocol error. */ break; }
36.3 FTP配置说明(Net_Config.c)
(本章节配套例子的配置与本小节的说明相同)
RL-TCPnet的配置工作是通过配置文件Net_Config.c实现。在MDK工程中打开文件Net_Config.c,可以看到下图所示的工程配置向导:
RL-TCPnet要配置的选项非常多,我们这里把几个主要的配置选项简单介绍下。
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。
Ethernet Network Interface
以太网接口配置,勾选了此选项就可以配置了,如果没有使能DHCP的话,将使用这里配置的固定IP。
(1) MAC Address
局域网内可以随意配置,只要不跟局域网内其它设备的MAC地址冲突即可。
(2)IP Address
IP地址。
(3)Subnet mask
子网掩码。
(4) Default Gateway
默认网关。
Ethernet Network Interface
以太网接口配置,这个配置里面还有如下两项比较重要的配置需要说明。
(1) NetBIOS Name Service
NetBIOS局域网域名服务,这里打上对勾就使能了。这样我们就可以通过前面配置的Local Host Name局域网域名进行访问,而不需要通过IP地址访问了。
(2)Dynaminc Host Configuration
即DHCP,这里打上对勾就使能了。使能了DHCP后,RL-TCPnet就可以从外接的路由器上获得动态IP地址。
UDP Sockets
UDP Sockets配置,打上对勾就使能了此项功能
(1) Number of UDP Sockets
用于配置可创建的UDP Sockets数量,这里配置了5个。
范围1 – 20。
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接收窗口大小。
FTP Server
FTP 配置,打上对勾就使能了此项功能
(1) Number of FTP Sessions
同时可以连接的会话个数,即可以连接的FTP客户端个数。
范围1-10。
(2)Port Number
FTP服务器的监听端口号。
范围1-65535。
(3) Welcome Message
登录FTP服务器后的欢迎消息,如果用户没有配置此选项,将使用默认的欢迎消息,如果配置了,将使用用户配置的。
(4) Idle Session Timeout in seconds
空闲连接溢出时间,如果连接后,这段时间内无操作,FTP服务器将断开客户端的连接。
配置为数值0,将禁止超时断开连接,即一直保持连接。
范围0-3600,单位秒。
(5) Enable User Authentication
用户认证,如果此选项打上对勾的话,将使能用户认证。
Authentication Username 是用户名。
Authentication Password 是用户密码。
36.4 FTP调试说明(Net_Debug.c)
(重要说明,RL-TCPnet的调试是通过串口打印出来的)
RL-TCPnet的调试功能是通过配置文件Net_Debug.c实现。在MDK工程中打开文件Net_Debug.c,可以看到下图所示的工程配置向导:
Print Time Stamp
勾选了此选项的话,打印消息时,前面会附带时间信息。
其它所有的选项
默认情况下,所有的调试选项都关闭了,每个选项有三个调试级别可选择,这里我们以FTP Server Debug为例,点击下拉列表,可以看到里面有Off,Errors only和Full debug三个调试级别可供选择,每个调试选项里面都是这三个级别。
Off:表示关闭此选项的调试功能。
Errors only:表示仅在此选项出错时,将其错误打印出来。
Full debug:表示此选项的全功能调试。
具体测试,我们这里就不做了,大家可以按照第11章讲解的调试方法进行测试。
36.5 FTP服务器的访问方法和板子的操作步骤
我们这里介绍三种访问FTP服务器的方法,对于本实验实现的FTP服务器,支持的操作如下:
- 支持文件的上传和下载。
- 支持文件和文件夹的删除。
- 支持文件和文件夹的重命名。
- 支持新建文件和文件夹。
另外,本章节配套的例子是用开发板做FTP服务器,SD卡做为服务器的存储介质,所以务必准备好一个SD卡插到开发板上面。
最后,特别注意一点,我们使用的是RL-FlashFS文件系统,此文件系统的文件名仅支持ASCII字符,不支持中文,对于中文名的文件夹或者文件是无法操作的。
36.5.1 使用FTP客户端软件访问
- 第1步:下载FTP客户端软件
FTP软件推荐采用FileZilla Client,下载地址:http://bbs.armfly.com/read.php?tid=32486 。
- 第2步:下载后,安装此软件,安装完毕后,打开软件的效果如下
- 第3步:输入地址,账号和端口号
主机:armfly (备注,因为使能了NetBIOS Name,这里可以直接写armfly或者实际的IP地址)。
用户名:admin
密码:123456
端口:21
如果开发板已经上电,就可以点击快速连接进行访问了。
- 第4步:点击“快速连接”按钮就可以访问了
访问后效果如下:
访问FTP服务器成功后,这个客户端软件操作也比较简单,大家摸索下就熟练了,文件的上传下载、文件和文件夹的创建、删除、重命名等都支持。这里主要说上传和下载文件的方法。
- 第5步:文件的上传方法
点击了上传后,状态栏里面会有上传速度和上传进度:
- 第6步:文件的下载方法
点击了下载后,状态栏里面会有下载速度和下载进度:
36.5.2 “网络”地址栏输入地址访问
这种方法访问也非常方便,大家打开电脑端的“网络”或者“计算机”图标,早期的XP系统是叫“我的电脑”和“网上邻居”。
剩下的操作就很简单了,电脑端怎么操作文件的,这里就怎么操作(特别注意,不支持中文文件名,仅支持ASCII字符的文件名)。大家可以实际测试下文件的复制粘贴、文件和文件夹的创建、删除、重命名等功能。
另外这种访问方式,有一种情况要注意,如果FTP服务器中有某个文件了,重复上传会弹出一个窗口,提示正在计算上传文件所需时间,这个窗口不用管它,直接点击“是”或者“全是”按钮即可。
36.5.3 浏览器访问
浏览器访问FTP服务器也比较简单,这里以谷歌浏览器为例,按照下图所标注的四步即可登录:
登录成功后的界面如下:
不过这种方式仅支持文件的浏览,其它的任何操作均不支持。
36.6 实验例程说明(RTX)
36.6.1 STM32F407开发板实验
配套例子:
V5-1051_RL-TCPnet实验_FTP服务器(RTX)
实验目的:
- 学习RL-TCPnet的FTP服务器实现。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- FTP服务器的存储器是采用的SD卡,所以测试本例子前务必准备好一个SD卡并插上。
- 文件系统是采用的RL-FlashFS,此文件系统的文件名仅支持ASCII字符,不支持中文,特别注意!
- FTP服务器的访问方法在本实例配套教程里面有详细讲解。可以使用FTP客户端软件访问,也可以在“我的电脑”地址栏输入ftp://armfly进行访问。
- FTP服务器的用户名admin,密码123456。
实验操作:
详见本章节36.5小节。
配置向导文件设置(Net_Config.c):
详见本章节36.3小节。
调试文件设置(Net_Debug.c):
详见本章节36.4小节。
RTX配置:
RTX配置向导详情如下:
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任务调试信息:
程序设计:
任务栈大小分配:
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类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main (void) { /* 初始化外设 */ bsp_Init(); /* 创建启动任务 */ os_sys_init_user (AppTaskStart, /* 任务函数 */ 5, /* 任务优先级 */ &AppTaskStartStk, /* 任务栈 */ sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */ while(1); }
硬件外设初始化
硬件外设的初始化是在 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, /* 任务函数 */ 1, /* 任务优先级 */ &AppTaskUserIFStk, /* 任务栈 */ sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */ HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */ 2, /* 任务优先级 */ &AppTaskLEDStk, /* 任务栈 */ sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */ HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */ 3, /* 任务优先级 */ &AppTaskMsgProStk, /* 任务栈 */ sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */ HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain, /* 任务函数 */ 4, /* 任务优先级 */ &AppTaskTCPMainStk, /* 任务栈 */ sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */ }
五个RTX任务的实现:
/* ********************************************************************************************************* * 函 数 名: AppTaskUserIF * 功能说明: 按键消息处理 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反) ********************************************************************************************************* */ __task void AppTaskUserIF(void) { uint8_t ucKeyCode; while(1) { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下 */ case KEY_DOWN_K1: printf("K1键按下 \r\n"); break; /* K2键按下 */ case KEY_DOWN_K2: printf("K2键按下\r\n"); break; /* K3键按下 */ case KEY_DOWN_K3: printf("K3键按下 \r\n"); break; /* 其他的键值不处理 */ default: break; } } os_dly_wait(20); } } /* ********************************************************************************************************* * 函 数 名: AppTaskLED * 功能说明: LED闪烁。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 2 ********************************************************************************************************* */ __task void AppTaskLED(void) { const uint16_t usFrequency = 500; /* 延迟周期 */ /* 设置延迟周期 */ os_itv_set(usFrequency); while(1) { bsp_LedToggle(2); /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/ os_itv_wait(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskMsgPro * 功能说明: 按键检测 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 3 ********************************************************************************************************* */ __task void AppTaskMsgPro(void) { while(1) { bsp_KeyScan(); os_dly_wait(10); } } /* ********************************************************************************************************* * 函 数 名: AppTaskTCPMain * 功能说明: RL-TCPnet测试任务 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 4 ********************************************************************************************************* */ __task void AppTaskTCPMain(void) { while (1) { TCPnetTest(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 5 ********************************************************************************************************* */ __task void AppTaskStart(void) { /* 初始化RL-TCPnet */ init_TcpNet (); /* 创建任务 */ AppTaskCreate(); os_itv_set (100); while(1) { os_itv_wait (); /* RL-TCPnet时间基准更新函数 */ timer_tick (); os_evt_set(0x0001, HandleTaskTCPMain); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现网络主函数main_TcpNet的调用。
#include "includes.h" /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPent测试函数。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { while (1) { os_evt_wait_or(0x0001, 0xFFFF); /* RL-TCPnet主处理函数 */ while (main_TcpNet() == __TRUE); } }
FTP用户接口文件的实现
KEIL官网有提供FTP的接口文件,名为FTP_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:
#include#include #include /*---------------------------------------------------------------------------- * FTP Server File Access Functions *---------------------------------------------------------------------------*/ /*--------------------------- ftp_fopen -------------------------------------*/ void *ftp_fopen (U8 *fname, U8 *mode) { /* Open file 'fname' for reading or writing. Return file handle. */ return (fopen ((const char *)fname, (const char *)mode)); } /*--------------------------- ftp_fclose ------------------------------------*/ void ftp_fclose (void *file) { /* Close the file opened for reading or writing. */ fclose (file); } /*--------------------------- ftp_fread -------------------------------------*/ U16 ftp_fread (void *file, U8 *buf, U16 len) { /* Read 'len' bytes from file to buffer 'buf'. The file will be closed, */ /* when the number of bytes read is less than 'len'. */ return (fread (buf, 1, len, file)); } /*--------------------------- ftp_fwrite ------------------------------------*/ U16 ftp_fwrite (void *file, U8 *buf, U16 len) { /* Write 'len' bytes from buffer 'buf' to a file. */ return (fwrite (buf, 1, len, file)); } /*--------------------------- ftp_fdelete -----------------------------------*/ BOOL ftp_fdelete (U8 *fname) { /* Delete a file, return __TRUE on success. */ if (fdelete((char *)fname) == 0) { return (__TRUE); } return (__FALSE); } /*--------------------------- ftp_frename -----------------------------------*/ BOOL ftp_frename (U8 *fname, U8 *newn) { /* Rename a file, return __TRUE on success. */ if (frename((char *)fname, (char *)newn) == 0) { return (__TRUE); } return (__FALSE); } /*--------------------------- ftp_ffind -------------------------------------*/ U16 ftp_ffind (U8 code, U8 *buf, U8 *mask, U16 buflen) { /* This function is called by the FTP server to find file names and other */ /* file information. The output data is stored in ascii format to output */ /* buffer 'buf' Parameter 'code' specifies requested file information. */ /* Values for 'code': */ /* 0 - read file size */ /* 1 - read last-modified time of a file */ /* 2 - list file names only (first call) */ /* 3 - list file directory in extended format (first call) */ /* 4 - list file names only (repeated call) */ /* 5 - list file directory in extended format (repeated call) */ static FINFO info; U32 rlen,v; U8 *tp; if (code < 4) { /* First call to ffind, initialize the info. */ info.fileID = 0; } rlen = 0; next: if (ffind ((char *)mask, &info) == 0) { /* File found, print file information. */ if (info.name[0] == '.') { if ((info.name[1] == 0) || (info.name[1] == '.' && info.name[2] == 0)) { /* Ignore the '.' and '..' folders. */ goto next; } } switch (code) { case 0: /* Return file size as decimal number. */ rlen = sprintf ((char *)buf,"%d\r\n", info.size); break; case 1: /* Return last-modified time in format "YYYYMMDDhhmmss". */ rlen = sprintf ((char *)buf,"%04d%02d%02d", info.time.year, info.time.mon, info.time.day); rlen += sprintf ((char *)&buf[rlen],"%02d%02d%02d\r\n", info.time.hr, info.time.min, info.time.sec); break; case 2: case 4: /* List file names only. */ rlen = sprintf ((char *)buf,"%s\r\n", info.name); break; case 3: case 5: /* List directory in extended format. */ rlen = sprintf ((char *)buf,"%02d-%02d-%02d", info.time.mon, info.time.day, info.time.year%100); /* Convert time to "AM/PM" format. */ v = info.time.hr % 12; if (v == 0) v = 12; if (info.time.hr < 12) tp = "AM"; else tp = "PM"; rlen += sprintf ((char *)&buf[rlen]," %02d:%02d%s",v,info.time.min,tp); if (info.attrib & ATTR_DIRECTORY) { rlen += sprintf ((char *)&buf[rlen],"%-21s"," "); } else { rlen += sprintf ((char *)&buf[rlen],"%21d", info.size); } rlen += sprintf ((char *)&buf[rlen]," %s\r\n", info.name); break; } } return (rlen); } /*--------------------------- ftp_accept_host -------------------------------*/ #if 0 BOOL ftp_accept_host (U8 *rem_ip, U16 rem_port) { /* This function checks if a connection from remote host is accepted or */ /* not. If this function is missing, all remote hosts are accepted. */ if (rem_ip[0] == 192 && rem_ip[1] == 168 && rem_ip[2] == 1 && rem_ip[3] == 1) { /* Accept a connection. */ return (__TRUE); } /* Deny a connection. */ return (__FALSE); } #endif /*--------------------------- ftp_evt_notify --------------------------------*/ #if 0 void ftp_evt_notify (U8 evt) { /* This function notifies the user application about events in FTP server.*/ switch (evt) { case FTP_EVT_LOGIN: /* User logged in, FTP session is busy. */ break; case FTP_EVT_LOGOUT; /* User logged out, session is idle. */ break; case FTP_EVT_LOGFAIL: /* User login failed (invalid credentials). */ break; case FTP_EVT_DOWNLOAD: /* File download ended. */ break; case FTP_EVT_UPLOAD: /* File upload ended. */ break; case FTP_EVT_DELETE: /* File deleted. */ break; case FTP_EVT_RENAME: /* File or directory renamed. */ break; case FTP_EVT_MKDIR: /* Directory created. */ break; case FTP_EVT_RMDIR: /* Directory removed. */ break; case FTP_EVT_ERRLOCAL: /* Local file operation error. */ break; case FTP_EVT_DENIED: /* Requested file operation denied. */ break; case FTP_EVT_ERROR: /* Generic file operation or protocol error. */ break; } } #endif /*---------------------------------------------------------------------------- * end of file *---------------------------------------------------------------------------*/
36.6.2 STM32F429开发板实验
配套例子:
V6-1051_RL-TCPnet实验_FTP服务器(RTX)
实验目的:
- 学习RL-TCPnet的FTP服务器实现。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- FTP服务器的存储器是采用的SD卡,所以测试本例子前务必准备好一个SD卡并插上。
- 文件系统是采用的RL-FlashFS,此文件系统的文件名仅支持ASCII字符,不支持中文,特别注意!
- FTP服务器的访问方法在本实例配套教程里面有详细讲解。可以使用FTP客户端软件访问,也可以在“我的电脑”地址栏输入ftp://armfly进行访问。
- FTP服务器的用户名admin,密码123456。
实验操作:
详见本章节36.5小节。
配置向导文件设置(Net_Config.c):
详见本章节36.3小节。
调试文件设置(Net_Debug.c):
详见本章节36.4小节。
RTX配置:
RTX配置向导详情如下:
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任务调试信息:
程序设计:
任务栈大小分配:
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类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main (void) { /* 初始化外设 */ bsp_Init(); /* 创建启动任务 */ os_sys_init_user (AppTaskStart, /* 任务函数 */ 5, /* 任务优先级 */ &AppTaskStartStk, /* 任务栈 */ sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */ while(1); }
硬件外设初始化
硬件外设的初始化是在 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, /* 任务函数 */ 1, /* 任务优先级 */ &AppTaskUserIFStk, /* 任务栈 */ sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */ HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */ 2, /* 任务优先级 */ &AppTaskLEDStk, /* 任务栈 */ sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */ HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */ 3, /* 任务优先级 */ &AppTaskMsgProStk, /* 任务栈 */ sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */ HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain, /* 任务函数 */ 4, /* 任务优先级 */ &AppTaskTCPMainStk, /* 任务栈 */ sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */ }
五个RTX任务的实现:
/* ********************************************************************************************************* * 函 数 名: AppTaskUserIF * 功能说明: 按键消息处理 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反) ********************************************************************************************************* */ __task void AppTaskUserIF(void) { uint8_t ucKeyCode; while(1) { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下 */ case KEY_DOWN_K1: printf("K1键按下 \r\n"); break; /* K2键按下 */ case KEY_DOWN_K2: printf("K2键按下\r\n"); break; /* K3键按下 */ case KEY_DOWN_K3: printf("K3键按下 \r\n"); break; /* 其他的键值不处理 */ default: break; } } os_dly_wait(20); } } /* ********************************************************************************************************* * 函 数 名: AppTaskLED * 功能说明: LED闪烁。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 2 ********************************************************************************************************* */ __task void AppTaskLED(void) { const uint16_t usFrequency = 500; /* 延迟周期 */ /* 设置延迟周期 */ os_itv_set(usFrequency); while(1) { bsp_LedToggle(2); /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/ os_itv_wait(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskMsgPro * 功能说明: 按键检测 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 3 ********************************************************************************************************* */ __task void AppTaskMsgPro(void) { while(1) { bsp_KeyScan(); os_dly_wait(10); } } /* ********************************************************************************************************* * 函 数 名: AppTaskTCPMain * 功能说明: RL-TCPnet测试任务 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 4 ********************************************************************************************************* */ __task void AppTaskTCPMain(void) { while (1) { TCPnetTest(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 5 ********************************************************************************************************* */ __task void AppTaskStart(void) { /* 初始化RL-TCPnet */ init_TcpNet (); /* 创建任务 */ AppTaskCreate(); os_itv_set (100); while(1) { os_itv_wait (); /* RL-TCPnet时间基准更新函数 */ timer_tick (); os_evt_set(0x0001, HandleTaskTCPMain); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现网络主函数main_TcpNet的调用。
#include "includes.h" /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPent测试函数。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { while (1) { os_evt_wait_or(0x0001, 0xFFFF); /* RL-TCPnet主处理函数 */ while (main_TcpNet() == __TRUE); } }
FTP用户接口文件的实现
KEIL官网有提供FTP的接口文件,名为FTP_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:
#include#include #include /*---------------------------------------------------------------------------- * FTP Server File Access Functions *---------------------------------------------------------------------------*/ /*--------------------------- ftp_fopen -------------------------------------*/ void *ftp_fopen (U8 *fname, U8 *mode) { /* Open file 'fname' for reading or writing. Return file handle. */ return (fopen ((const char *)fname, (const char *)mode)); } /*--------------------------- ftp_fclose ------------------------------------*/ void ftp_fclose (void *file) { /* Close the file opened for reading or writing. */ fclose (file); } /*--------------------------- ftp_fread -------------------------------------*/ U16 ftp_fread (void *file, U8 *buf, U16 len) { /* Read 'len' bytes from file to buffer 'buf'. The file will be closed, */ /* when the number of bytes read is less than 'len'. */ return (fread (buf, 1, len, file)); } /*--------------------------- ftp_fwrite ------------------------------------*/ U16 ftp_fwrite (void *file, U8 *buf, U16 len) { /* Write 'len' bytes from buffer 'buf' to a file. */ return (fwrite (buf, 1, len, file)); } /*--------------------------- ftp_fdelete -----------------------------------*/ BOOL ftp_fdelete (U8 *fname) { /* Delete a file, return __TRUE on success. */ if (fdelete((char *)fname) == 0) { return (__TRUE); } return (__FALSE); } /*--------------------------- ftp_frename -----------------------------------*/ BOOL ftp_frename (U8 *fname, U8 *newn) { /* Rename a file, return __TRUE on success. */ if (frename((char *)fname, (char *)newn) == 0) { return (__TRUE); } return (__FALSE); } /*--------------------------- ftp_ffind -------------------------------------*/ U16 ftp_ffind (U8 code, U8 *buf, U8 *mask, U16 buflen) { /* This function is called by the FTP server to find file names and other */ /* file information. The output data is stored in ascii format to output */ /* buffer 'buf' Parameter 'code' specifies requested file information. */ /* Values for 'code': */ /* 0 - read file size */ /* 1 - read last-modified time of a file */ /* 2 - list file names only (first call) */ /* 3 - list file directory in extended format (first call) */ /* 4 - list file names only (repeated call) */ /* 5 - list file directory in extended format (repeated call) */ static FINFO info; U32 rlen,v; U8 *tp; if (code < 4) { /* First call to ffind, initialize the info. */ info.fileID = 0; } rlen = 0; next: if (ffind ((char *)mask, &info) == 0) { /* File found, print file information. */ if (info.name[0] == '.') { if ((info.name[1] == 0) || (info.name[1] == '.' && info.name[2] == 0)) { /* Ignore the '.' and '..' folders. */ goto next; } } switch (code) { case 0: /* Return file size as decimal number. */ rlen = sprintf ((char *)buf,"%d\r\n", info.size); break; case 1: /* Return last-modified time in format "YYYYMMDDhhmmss". */ rlen = sprintf ((char *)buf,"%04d%02d%02d", info.time.year, info.time.mon, info.time.day); rlen += sprintf ((char *)&buf[rlen],"%02d%02d%02d\r\n", info.time.hr, info.time.min, info.time.sec); break; case 2: case 4: /* List file names only. */ rlen = sprintf ((char *)buf,"%s\r\n", info.name); break; case 3: case 5: /* List directory in extended format. */ rlen = sprintf ((char *)buf,"%02d-%02d-%02d", info.time.mon, info.time.day, info.time.year%100); /* Convert time to "AM/PM" format. */ v = info.time.hr % 12; if (v == 0) v = 12; if (info.time.hr < 12) tp = "AM"; else tp = "PM"; rlen += sprintf ((char *)&buf[rlen]," %02d:%02d%s",v,info.time.min,tp); if (info.attrib & ATTR_DIRECTORY) { rlen += sprintf ((char *)&buf[rlen],"%-21s"," "); } else { rlen += sprintf ((char *)&buf[rlen],"%21d", info.size); } rlen += sprintf ((char *)&buf[rlen]," %s\r\n", info.name); break; } } return (rlen); } /*--------------------------- ftp_accept_host -------------------------------*/ #if 0 BOOL ftp_accept_host (U8 *rem_ip, U16 rem_port) { /* This function checks if a connection from remote host is accepted or */ /* not. If this function is missing, all remote hosts are accepted. */ if (rem_ip[0] == 192 && rem_ip[1] == 168 && rem_ip[2] == 1 && rem_ip[3] == 1) { /* Accept a connection. */ return (__TRUE); } /* Deny a connection. */ return (__FALSE); } #endif /*--------------------------- ftp_evt_notify --------------------------------*/ #if 0 void ftp_evt_notify (U8 evt) { /* This function notifies the user application about events in FTP server.*/ switch (evt) { case FTP_EVT_LOGIN: /* User logged in, FTP session is busy. */ break; case FTP_EVT_LOGOUT; /* User logged out, session is idle. */ break; case FTP_EVT_LOGFAIL: /* User login failed (invalid credentials). */ break; case FTP_EVT_DOWNLOAD: /* File download ended. */ break; case FTP_EVT_UPLOAD: /* File upload ended. */ break; case FTP_EVT_DELETE: /* File deleted. */ break; case FTP_EVT_RENAME: /* File or directory renamed. */ break; case FTP_EVT_MKDIR: /* Directory created. */ break; case FTP_EVT_RMDIR: /* Directory removed. */ break; case FTP_EVT_ERRLOCAL: /* Local file operation error. */ break; case FTP_EVT_DENIED: /* Requested file operation denied. */ break; case FTP_EVT_ERROR: /* Generic file operation or protocol error. */ break; } } #endif /*---------------------------------------------------------------------------- * end of file *---------------------------------------------------------------------------*/
36.7 总结
本章节就为大家讲解这么多,其中FTP的测试稍麻烦些,希望大家实际动手操作一遍,并将其熟练掌握。