第40章 RL-TCPnet之TFTP客户端
本章节为大家讲解RL-TCPnet的TFTP客户端应用,学习本章节前,务必要优先学习第38章的TFTP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。
本章教程含STM32F407开发板和STM32F429开发板。
40.1 初学者重要提示
40.2 TFTP函数
40.3 TFTP服务器端软件和板子的操作步骤
40.4 实验例程说明(RTX)
40.5 总结
40.1 初学者重要提示
- 学习本章节前,务必保证已经学习了第38章的基础知识。
- 本章配套的例子是将开发板作为TFTP客户端,使用开发板上面的SD卡作为客户端的存储介质。所以测试本章节的例子,务必要准备一个SD卡。
- 由于配套例子的文件系统是采用的RL-FlashFS,此文件系统的文件名仅支持ASCII字符,不支持中文,特别注意!
- 具体电脑端TFTP服务器的创建方法和板子的操作步骤在本章的40.5小节有详细说明。做本章节配套的实验,必须要看!
40.2 TFTP函数
使用如下11个函数可以实现RL-TCPnet的TFTP:
- tftp_accept_host
- tftp_fclose
- tftp_fopen
- tftp_fread
- tftp_fwrite
- tftpc_fclose
- tftpc_fopen
- tftpc_fread
- tftpc_fwrite
- tftpc_get
- tftpc_put
关于这11个函数的讲解及其使用方法可以看教程第 3 章 3.4 小节里面说的参考资料 rlarm.chm 文件:
这里我们重点的说以下6个函数,因为本章节配套的例子使用的是这6个函数:
- tftpc_fclose
- tftpc_fopen
- tftpc_fread
- tftpc_fwrite
- tftpc_get
- tftpc_put
关于这些函数注意以下三点:
- TFTP的所有函数都不支持重入,也就是不支持多任务调用。
- 以tftp_开头的函数是用于TFTP服务器的。
- 以tftpc_开头的函数是用于TFTP客户端的。
40.2.1 函数tftpc_fopen
函数原型:
void* tftpc_fopen ( U8* fname, /* 文件名地址 */ U8* mode ); /* 操作模式 */
函数描述:
函数tftpc_fopen用于打开本地文件(TFTP客户端的文件)。此函数在MDK安装目录中的TFTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
- 第1个参数是文件名地址。
- 第2个参数是操作模式,可以是读操作或者写操作,具体支持的形参类型如下:
- 返回值,打开文件成功的话,返回指向此文件的指针变量,否则返回NULL。
使用这个函数要注意以下问题:
- 此接口函数是用于TFTP客户端的。
使用举例:
void *tftpc_fopen (U8 *fname, U8 *mode) { /* 打开文件,如果返回NULL,表示打开失败 */ return (fopen ((char *)fname, (char *)mode)); }
40.2.2 函数tftpc_fclose
函数原型:
void tftpc_fclose ( FILE* file ); /* 文件句柄地址 */
函数描述:
函数tftpc_fclose用于关闭文件。此函数在MDK安装目录中的TFTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
- 第1个参数是要关闭的文件句柄地址。
使用这个函数要注意以下问题:
- 此接口函数是用于TFTP客户端的。
使用举例:
void tftpc_fclose (void *file) { /* 关闭文件 */ fclose (file); }
40.2.3 函数tftpc_fread
函数原型:
U16 tftpc_fread ( FILE* file, /* 文件句柄地址 */ U8* buf, /* 数据缓冲地址 */ U16 len ); /* 要读取的字节数 */
函数描述:
函数tftpc_fread用于从文件中读出len个字节数据。此函数在MDK安装目录中的TFTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
- 第1个参数是要读取数据的文件句柄地址。
- 第2个参数是数据缓冲地址,用于存储读取出来的数据。
- 第3个参数是要读取出来的数据大小,单位字节。
- 返回值,返回从文件中实际读出的字节数。
使用这个函数要注意以下问题:
- 设置读取函数时,必须设置指定大小的字节数。如果实际读出的字节数小于len,将停止读取并关闭TFTP会话,这种情况一般都是文件已经读取完毕。
- 此接口函数是用于TFTP客户端的。
使用举例:
U16 tftpc_fread (void *file, U8 *buf, U16 len) { /* 读取len字节到buf中,返回值是实际读取的字节数,返回数值小于len的话,表示文件已经读取完毕, 文件将被关闭 */ return (fread (buf, , len, file)); }
40.2.4 函数tftpc_fwrite
函数原型:
U16 tftpc_fwrite ( FILE* file, /* 文件句柄地址 */ U8* buf, /* 数据缓冲地址 */ U16 len ); /* 要写入的字节数 */
函数描述:
函数tftpc_fwrite用于往文件中写入len个字节数据。此函数在MDK安装目录中的TFTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
- 第1个参数是要写入数据的文件句柄地址。
- 第2个参数是数据缓冲地址,存储了要写入的数据。
- 第3个参数是要写入的数据大小,单位字节。
- 返回值,返回实际写入文件的字节数。
使用这个函数要注意以下问题:
- 设置写函数时,必须设置指定大小的字节数。如果实际写入的字节数小于len,TFTP客户端将停止写入,终止数据传输并关闭TFTP会话,这种情况一般是写操作出错了。
- 此接口函数是用于TFTP客户端的。
使用举例:
U16 tftpc_fwrite (void *file, U8 *buf, U16 len) { /* 将buf中的len字节写入到文件中,如果返回数值(实际写入的字节数)不等于len,数据传输将终止 */ return (fwrite (buf, , len, file)); }
40.2.5 函数tftpc_get
函数原型:
BOOL tftpc_get ( U8* ipadr, /* 远程TFTP服务器的IP地址 */ U16 port, /* 远程TFTP服务器的端口号 */ const char *src, /* 远程TFTP服务器上的文件名 */ const char *dst, /* 保存到本地的文件名 */ void (*cbfunc)(U8 event) ); /* 回调函数 */
函数描述:
函数tftpc_get用于启动RL-TCPnet系统上的TFTP客户端,将文件从远程TFTP服务器下载到本地系统。 这样TFTP客户端就可以通过连接到UDP端口号为port(本函数的第2个形参)的TFTP服务器来启动TFTP会话。如果第2个参数的端口号填0,系统将使用TFTP服务器的标准端口号69进行连接。
- 第1个参数填TFTP服务器的IP地址。
- 第2个参数填TFTP服务器的端口号。
- 第3个参数是TFTP服务器上的文件名,即TFTP客户端要下载的文件名。
- 第4个参数是TFTP客户端上新建文件的文件名,用于存储从TFTP客户端下载的文件。如果这个参数填NULL,那么此文件在TFTP服务器上的文件名是什么,下载后还是什么。
- 第5个参数填此函数的回调函数,当TFTP会话即将结束时,会调用这个函数。此回调函数只有一个形参,形参类型如下:
- 返回值,返回__TRUE表示TFTP客户端启动成功(注意,仅仅是客户端启动成功,并不是文件传输已经完成),返回__FALSE表示启动失败。
使用这个函数要注意以下问题:
- 标准TFTP的端口号是用的UDP端口69。
- 用户是通过此函数启动RL-TCPnet的TFTP客户端下载TFTP服务器上的文件。
使用举例:
/* ********************************************************************************************************* * 宏定义,远程FTP服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程FTP服务器IP配置 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 4 #define PORT_NUM 69 /* FTP服务器,默认端口号是69,无需改动 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t ServerIP[] = {IP1, IP2, IP3, IP4}; /* ********************************************************************************************************* * 函 数 名: ftpc_notify * 功能说明: 函数tftpc_put和tftpc_get的回调函数。 * 形 参: event 事件类型 * 返 回 值: 无 ********************************************************************************************************* */ static void tftpc_notify (U8 event) { switch (event) { /* 文件传输成功 */ case TFTPC_EVT_SUCCESS: printf_debug ("File successfully transferred.\r\n"); break; /* TFTP服务器响应超时,因此TFTP客户端终止操作 */ case TFTPC_EVT_TIMEOUT: printf_debug ("TFTP Server timeout.\r\n"); break; /* 访问的是禁止操作的文件 */ case TFTPC_EVT_NOACCESS: printf_debug ("File access on TFTP server is not allowed for a specified file.\r\n"); break; /* 在TFTP服务器上找不到要访问的文件 */ case TFTPC_EVT_NOTFOUND: printf_debug ("Requested file is not found on TFTP server.\r\n"); break; /* TFTP服务器的存储器空间已经满,无法再进行文件传输 */ case TFTPC_EVT_DISKFULL: printf_debug ("The TFTP server has run out of disk space.\r\n"); break; /* 文件传输过程中,TFTP服务器遇到一个错误 */ case TFTPC_EVT_ERROR: printf_debug ("The TFTP server has encountered an error during file transfer process.\r\n"); break; /* 其它未定义 */ default: printf_debug ("No Defined.\n"); break; } } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPent测试函数。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { OS_RESULT xResult; while () { os_evt_wait_or(0x0007, 0xFFFF); xResult = os_evt_get (); switch (xResult) { /* 接收到K2键按下,从TFTP服务器下载文件server.pdf,并重命名为client.pdf */ case KEY2_BIT1: if (tftpc_get (ServerIP, PORT_NUM, "server.pdf", "client.pdf", tftpc_notify) == __FALSE) { printf_debug("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 其他的键值不处理 */ default: break; } while (main_TcpNet() == __TRUE); } }
40.2.6 函数ftpc_put
函数原型:
BOOL tftpc_put ( U8* ipadr, /* 远程TFTP服务器的IP地址. */ U16 port, /* 远程TFTP服务器的IP地址. */ const char *src, /* 本地要上传文件的文件名 */ const char *dst, /* 远程TFTP服务器上文件的文件名 */ void (*cbfunc)(U8 event) ); /* 回调函数 */
函数描述:
函数tftpc_put用于启动RL-TCPnet系统上的TFTP客户端,将本地文件上传到TFTP服务器。 这样TFTP客户端就可以通过连接到UDP端口号为port(本函数的第2个形参)的TFTP服务器来启动TFTP会话。如果第2个参数的端口号填0,系统将使用TFTP服务器的标准端口号69进行连接。
- 第1个参数填TFTP服务器的IP地址。
- 第2个参数填TFTP服务器的端口号。
- 第3个参数是TFTP客户端上的文件名,此文件是要被上传到TFTP服务器。
- 第4个参数是TFTP服务器上新建文件的文件名,用于存储从TFTP客户端上传的文件。如果这个参数填NULL,那么此文件在TFTP客户端上文件名是什么,上传到TFTP服务器后还是什么。
- 第5个参数填此函数的回调函数,当TFTP会话即将结束时,会调用这个函数。此回调函数只有一个形参,形参类型如下:
- 返回值,返回__TRUE表示TFTP客户端启动成功(注意,仅仅是客户端启动成功,并不是文件传输已经完成),返回__FALSE表示启动失败。
使用这个函数要注意以下问题:
- 标准TFTP的端口号是用的UDP端口69。
- 用户是通过此函数启动RL-TCPnet的TFTP客户端上传本地文件到TFTP服务器。
使用举例:
/* ********************************************************************************************************* * 宏定义,远程FTP服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程FTP服务器IP配置 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 4 #define PORT_NUM 69 /* FTP服务器,默认端口号是69,无需改动 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t ServerIP[] = {IP1, IP2, IP3, IP4}; /* ********************************************************************************************************* * 函 数 名: ftpc_notify * 功能说明: 函数tftpc_put和tftpc_get的回调函数。 * 形 参: event 事件类型 * 返 回 值: 无 ********************************************************************************************************* */ static void tftpc_notify (U8 event) { switch (event) { /* 文件传输成功 */ case TFTPC_EVT_SUCCESS: printf_debug ("File successfully transferred.\r\n"); break; /* TFTP服务器响应超时,因此TFTP客户端终止操作 */ case TFTPC_EVT_TIMEOUT: printf_debug ("TFTP Server timeout.\r\n"); break; /* 访问的是禁止操作的文件 */ case TFTPC_EVT_NOACCESS: printf_debug ("File access on TFTP server is not allowed for a specified file.\r\n"); break; /* 在TFTP服务器上找不到要访问的文件 */ case TFTPC_EVT_NOTFOUND: printf_debug ("Requested file is not found on TFTP server.\r\n"); break; /* TFTP服务器的存储器空间已经满,无法再进行文件传输 */ case TFTPC_EVT_DISKFULL: printf_debug ("The TFTP server has run out of disk space.\r\n"); break; /* 文件传输过程中,TFTP服务器遇到一个错误 */ case TFTPC_EVT_ERROR: printf_debug ("The TFTP server has encountered an error during file transfer process.\r\n"); break; /* 其它未定义 */ default: printf_debug ("No Defined.\n"); break; } } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPent测试函数。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { OS_RESULT xResult; while () { os_evt_wait_or(0x0007, 0xFFFF); xResult = os_evt_get (); switch (xResult) { /* 接收到K3键按下,K2按键按下后下载的client.pdf文件上传到TFTP服务器 */ case KEY3_BIT2: if (tftpc_put (ServerIP, PORT_NUM, "client.pdf", NULL, tftpc_notify) == __FALSE) { printf("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 其他的键值不处理 */ default: break; } while (main_TcpNet() == __TRUE); } }
40.3 TFTP服务器端软件和板子的操作步骤
本章节的测试稍麻烦些,需要大家配置工程,并且在电脑端建立一个TFTP服务器,而开发板是作为客户端,并且采用SD卡作为存储介质(测试前要准备好一个SD卡插到开发板上面),所以大家测试本章节配套的例子前,务必将这里的操作步骤全部看完才可以做测试!
另外有一点特别注意,我们使用的是RL-FlashFS文件系统,此文件系统的文件名仅支持ASCII字符,不支持中文,对于中文名的文件夹或者文件是无法操作的,因此,电脑端创建TFTP服务器的时候,使用的文件名也不要有中文。
40.3.1 获取电脑的IP地址
获取电脑IP地址的方法很多,可以在网上邻居获取,也可以通过输入命令ipconfig获取:
- WIN+R组合键打开“运行”窗口,输入cmd。
- 弹出的命令窗口中,输入ipconfig。
- 输入ipconfig后,回车。
获得电脑的IP地址是192.168.1.4。
40.3.2 在程序中配置要访问的TFTP服务器IP地址和端口
根据刚获得的IP地址,需要大家配置程序中app_tcpnet_lib.c文件开头的宏定义:
/* ********************************************************************************************************* * 宏定义,远程TFTP服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程FTP服务器IP配置 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 4 #define PORT_NUM 69 /* TFTP服务器,默认端口号是69,无需改动 */
40.3.3 TFTP服务器软件安装和设置
第1步:下载TFTP服务器软件。
TFTP软件推荐采用TFTPD32,客户端和服务器都支持,分32bit和64bit两个版本,大家根据自己电脑系统选择相应版本进行安装,另外推荐绿色版,无需安装,使用起来简单省事。下载地址:http://bbs.armfly.com/read.php?tid=32486 。
第2步:下载绿色版后,解压出来就可以使用,打开软件的效果如下(我的系统是WIN7 64bit,所以使用的是64位版本):
第3步:关闭不需要的功能,仅留下TFTP Server(不是必须的,仅剩下服务器功能,看着简洁些)。
首先点击settings:
在弹出的窗口里面仅选择TFTP Server:
设置后,点击OK按键,弹出如下窗口,继续点击OK:
经过这么设置后,就仅剩下TFTP服务器功能了,为了使得设置的功能起作用,务必关闭软件,然后重新打开。
设置完毕后,就可以测试文件的上传和下载功能了。
40.3.4 开发板下载TFTP服务器上的文件
第1步:准备一个测试文件:
简单的在电脑桌面上创建一个文件夹,起名为good(任何其它地方均可,但建议不要有中文,防止测试不成功)
为了方便查看上传和下载文件的效果,找一个稍大些的文件放到此文件夹,这里将我们之前做的FreeRTOS教程放到这个新建的文件夹里面(已经将这个文件放在了本章节配套例子的Doc文件夹),起名为server.pdf,务必且只能设置成此名字,因为我们的程序中是配置成访问此文件。
仅放这一个文件即可。
第2步:配置文件访问路径:
现在需要将good文件夹路径添加到TFTP服务器软件上。
第3步:选择电脑端用于通信的网口IP:
设置完毕后就可以测试开发板下载TFTP服务器上的server.pdf文件了,首先需要用户先将SD卡插到开发板上,因为文件server.pdf是下载到开发板中的SD里面,然后下载本章节配套的程序,程序下载后,串口调试助手会打印网络初始化过程(波特率115200,数据位8,奇偶校验位无,停止位1):
上面的6条信息都打印出来后,就可以按下K2按键了,之后就可以看到开发板从TFTP服务器下载文件的进度,速度有1MB/S左右。
下载完毕后,大家可以查看SD卡中是否有一个client.pdf文件(程序中将下载的server.pdf文件重命名成client.pdf),然后查看此文件是否可以正常打开并浏览,如果正常的话,说明下载成功,否则下载失败。并且下载成功后,串口调试助手会打印如下信息:
40.3.5 开发板上传文件到TFTP服务器
为了方便测试,我们这里直接将40.5.4小节中下载到开发板SD卡中的client.pdf文件上传到电脑端。上传后的名字不换,还叫client.pdf。对于TFTP服务器软件,还继续用之前设置好的,这里什么都不用修改,用户仅需按下开发板上的K3按键,之后就可以看到如下上传进度,速度1MB/S左右。
上传完毕后,为了验证下载是否成功,需要大家查看之前创建的good文件夹中client.pdf文件是否可以正常打开并浏览,如果没有问题,说明上传成功,否则失败。
并且上传成功后,串口调试助手会打印如下信息:
至此,TFTP客户端的文件上传和下载功能就都测试完毕了。
40.4 实验例程说明(RTX)
40.4.1 STM32F407开发板实验
配套例子:
V5-1060_RL-TCPnet实验_TFTP客户端(RTX)
实验目的:
- 学习RL-TCPnet的TFTP客户端实现。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- TFTP客户端的存储器是采用的SD卡,所以测试本例子前务必准备好一个SD卡并插上。
- 文件系统是采用的RL-FlashFS,此文件系统的文件名仅支持ASCII字符,不支持中文,特别注意!
- 远程TFTP服务器的IP地址和端口号是在文件app_tcpnet_lib.c开头的宏定义设置。
- 测试本例子,需要在电脑端先建立TFTP服务器,具体建立方法和本例子的测试步骤在本实例配套教程里面有详细讲解,必看!!
- K2按键按下,将TFTP服务器上的server.pdf文件下载到开发板的SD卡中,重命名为client.pdf。
- K3按键按下,将K2按键按下后下载的client.pdf文件上传回TFTP服务器。所以务必要优先测试K2按键的文件下载功能。
实验操作:
详见本章节40.5小节。
配置向导文件设置(Net_Config.c):
详见本章节40.3小节。
调试文件设置(Net_Debug.c):
详见本章节40.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, /* 任务函数 */ , /* 任务优先级 */ &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; /* 其他的键值不处理 */ 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功能的测试,此文件主要实现开发板从TFTP服务器下载文件和上传文件到TFTP服务器以及网络主函数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 69 /* FTP服务器,默认端口号是69,无需改动 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t ServerIP[] = {IP1, IP2, IP3, IP4}; /* ********************************************************************************************************* * 函 数 名: ftpc_notify * 功能说明: 函数tftpc_put和tftpc_get的回调函数。 * 形 参: event 事件类型 * 返 回 值: 无 ********************************************************************************************************* */ static void tftpc_notify (U8 event) { switch (event) { /* 文件传输成功 */ case TFTPC_EVT_SUCCESS: printf_debug ("File successfully transferred.\r\n"); break; /* TFTP服务器响应超时,因此TFTP客户端终止操作 */ case TFTPC_EVT_TIMEOUT: printf_debug ("TFTP Server timeout.\r\n"); break; /* 访问的是禁止操作的文件 */ case TFTPC_EVT_NOACCESS: printf_debug ("File access on TFTP server is not allowed for a specified file.\r\n"); break; /* 在TFTP服务器上找不到要访问的文件 */ case TFTPC_EVT_NOTFOUND: printf_debug ("Requested file is not found on TFTP server.\r\n"); break; /* TFTP服务器的存储器空间已经满,无法再进行文件传输 */ case TFTPC_EVT_DISKFULL: printf_debug ("The TFTP server has run out of disk space.\r\n"); break; /* 文件传输过程中,TFTP服务器遇到一个错误 */ case TFTPC_EVT_ERROR: printf_debug ("The TFTP server has encountered an error during file transfer process.\r\n"); break; /* 其它未定义 */ default: printf_debug ("No Defined.\n"); break; } } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPent测试函数。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { OS_RESULT xResult; while () { os_evt_wait_or(0x0007, 0xFFFF); xResult = os_evt_get (); switch (xResult) { /* 接收到K2键按下,从TFTP服务器下载文件server.pdf,并重命名为client.pdf */ case KEY2_BIT1: if (tftpc_get (ServerIP, PORT_NUM, "server.pdf", "client.pdf", tftpc_notify) == __FALSE) { printf_debug("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 接收到K3键按下,将K2按键按下后下载的client.pdf文件上传到TFTP服务器 */ case KEY3_BIT2: if (tftpc_put (ServerIP, PORT_NUM, "client.pdf", NULL, tftpc_notify) == __FALSE) { printf("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 其他的键值不处理 */ default: break; } while (main_TcpNet() == __TRUE); } }
TFTP用户接口文件的实现
KEIL官网有提供TFTP的接口文件,名为TFTPC_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:
#include <stdio.h> #include <Net_Config.h> /*---------------------------------------------------------------------------- * TFTP Client File Access Functions *---------------------------------------------------------------------------*/ /*--------------------------- tftpc_fopen -----------------------------------*/ void *tftpc_fopen (U8 *fname, U8 *mode) { /* Open local file for reading or writing. */ return (fopen ((char *)fname, (char *)mode)); } /*--------------------------- tftpc_fclose ----------------------------------*/ void tftpc_fclose (void *file) { /* Close a local file. */ fclose (file); } /*--------------------------- tftpc_read ------------------------------------*/ U16 tftpc_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 < 'len' */ return (fread (buf, , len, file)); } /*--------------------------- tftpc_write -----------------------------------*/ U16 tftpc_fwrite (void *file, U8 *buf, U16 len) { /* Write data to file. Return number of bytes actually written. */ return (fwrite (buf, , len, file)); } /*---------------------------------------------------------------------------- * end of file *---------------------------------------------------------------------------*/
40.4.2 STM32F429开发板实验
配套例子:
V6-1060_RL-TCPnet实验_TFTP客户端(RTX)
实验目的:
- 学习RL-TCPnet的TFTP客户端实现。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- TFTP客户端的存储器是采用的SD卡,所以测试本例子前务必准备好一个SD卡并插上。
- 文件系统是采用的RL-FlashFS,此文件系统的文件名仅支持ASCII字符,不支持中文,特别注意!
- 远程TFTP服务器的IP地址和端口号是在文件app_tcpnet_lib.c开头的宏定义设置。
- 测试本例子,需要在电脑端先建立TFTP服务器,具体建立方法和本例子的测试步骤在本实例配套教程里面有详细讲解,必看!!
- K2按键按下,将TFTP服务器上的server.pdf文件下载到开发板的SD卡中,重命名为client.pdf。
- K3按键按下,将K2按键按下后下载的client.pdf文件上传回TFTP服务器。所以务必要优先测试K2按键的文件下载功能。
实验操作:
详见本章节40.5小节。
配置向导文件设置(Net_Config.c):
详见本章节40.3小节。
调试文件设置(Net_Debug.c):
详见本章节40.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, /* 任务函数 */ , /* 任务优先级 */ &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; /* 其他的键值不处理 */ 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功能的测试,此文件主要实现开发板从TFTP服务器下载文件和上传文件到TFTP服务器以及网络主函数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 69 /* FTP服务器,默认端口号是69,无需改动 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t ServerIP[] = {IP1, IP2, IP3, IP4}; /* ********************************************************************************************************* * 函 数 名: ftpc_notify * 功能说明: 函数tftpc_put和tftpc_get的回调函数。 * 形 参: event 事件类型 * 返 回 值: 无 ********************************************************************************************************* */ static void tftpc_notify (U8 event) { switch (event) { /* 文件传输成功 */ case TFTPC_EVT_SUCCESS: printf_debug ("File successfully transferred.\r\n"); break; /* TFTP服务器响应超时,因此TFTP客户端终止操作 */ case TFTPC_EVT_TIMEOUT: printf_debug ("TFTP Server timeout.\r\n"); break; /* 访问的是禁止操作的文件 */ case TFTPC_EVT_NOACCESS: printf_debug ("File access on TFTP server is not allowed for a specified file.\r\n"); break; /* 在TFTP服务器上找不到要访问的文件 */ case TFTPC_EVT_NOTFOUND: printf_debug ("Requested file is not found on TFTP server.\r\n"); break; /* TFTP服务器的存储器空间已经满,无法再进行文件传输 */ case TFTPC_EVT_DISKFULL: printf_debug ("The TFTP server has run out of disk space.\r\n"); break; /* 文件传输过程中,TFTP服务器遇到一个错误 */ case TFTPC_EVT_ERROR: printf_debug ("The TFTP server has encountered an error during file transfer process.\r\n"); break; /* 其它未定义 */ default: printf_debug ("No Defined.\n"); break; } } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPent测试函数。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { OS_RESULT xResult; while () { os_evt_wait_or(0x0007, 0xFFFF); xResult = os_evt_get (); switch (xResult) { /* 接收到K2键按下,从TFTP服务器下载文件server.pdf,并重命名为client.pdf */ case KEY2_BIT1: if (tftpc_get (ServerIP, PORT_NUM, "server.pdf", "client.pdf", tftpc_notify) == __FALSE) { printf_debug("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 接收到K3键按下,将K2按键按下后下载的client.pdf文件上传到TFTP服务器 */ case KEY3_BIT2: if (tftpc_put (ServerIP, PORT_NUM, "client.pdf", NULL, tftpc_notify) == __FALSE) { printf("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 其他的键值不处理 */ default: break; } while (main_TcpNet() == __TRUE); } }
TFTP用户接口文件的实现
KEIL官网有提供TFTP的接口文件,名为TFTPC_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:
#include <stdio.h> #include <Net_Config.h> /*---------------------------------------------------------------------------- * TFTP Client File Access Functions *---------------------------------------------------------------------------*/ /*--------------------------- tftpc_fopen -----------------------------------*/ void *tftpc_fopen (U8 *fname, U8 *mode) { /* Open local file for reading or writing. */ return (fopen ((char *)fname, (char *)mode)); } /*--------------------------- tftpc_fclose ----------------------------------*/ void tftpc_fclose (void *file) { /* Close a local file. */ fclose (file); } /*--------------------------- tftpc_read ------------------------------------*/ U16 tftpc_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 < 'len' */ return (fread (buf, , len, file)); } /*--------------------------- tftpc_write -----------------------------------*/ U16 tftpc_fwrite (void *file, U8 *buf, U16 len) { /* Write data to file. Return number of bytes actually written. */ return (fwrite (buf, , len, file)); } /*---------------------------------------------------------------------------- * end of file *---------------------------------------------------------------------------*/
40.5 总结
本章节就为大家讲解这么多,其中TFTP的测试稍麻烦些,希望大家实际动手操作一遍,并将其熟练掌握。