本章节为大家讲解RL-TCPnet的TFTP客户端应用,学习本章节前,务必要优先学习第38章的TFTP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。
本章教程含STM32F407开发板和STM32F429开发板。
40.1 初学者重要提示
40.2 TFTP函数
40.3 TFTP服务器端软件和板子的操作步骤
40.4 实验例程说明(RTX)
40.5 总结
使用如下11个函数可以实现RL-TCPnet的TFTP:
关于这11个函数的讲解及其使用方法可以看教程第 3 章 3.4 小节里面说的参考资料 rlarm.chm 文件:
这里我们重点的说以下6个函数,因为本章节配套的例子使用的是这6个函数:
关于这些函数注意以下三点:
函数原型:
void* tftpc_fopen (
U8* fname, /* 文件名地址 */
U8* mode ); /* 操作模式 */
函数描述:
函数tftpc_fopen用于打开本地文件(TFTP客户端的文件)。此函数在MDK安装目录中的TFTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
使用这个函数要注意以下问题:
使用举例:
void *tftpc_fopen (U8 *fname, U8 *mode) {
/* 打开文件,如果返回NULL,表示打开失败 */
return (fopen ((char *)fname, (char *)mode));
}
函数原型:
void tftpc_fclose (
FILE* file ); /* 文件句柄地址 */
函数描述:
函数tftpc_fclose用于关闭文件。此函数在MDK安装目录中的TFTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
使用这个函数要注意以下问题:
使用举例:
void tftpc_fclose (void *file) {
/* 关闭文件 */
fclose (file);
}
函数原型:
U16 tftpc_fread (
FILE* file, /* 文件句柄地址 */
U8* buf, /* 数据缓冲地址 */
U16 len ); /* 要读取的字节数 */
函数描述:
函数tftpc_fread用于从文件中读出len个字节数据。此函数在MDK安装目录中的TFTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
使用这个函数要注意以下问题:
使用举例:
U16 tftpc_fread (void *file, U8 *buf, U16 len) {
/* 读取len字节到buf中,返回值是实际读取的字节数,返回数值小于len的话,表示文件已经读取完毕,
文件将被关闭 */
return (fread (buf, 1, len, file));
}
函数原型:
U16 tftpc_fwrite (
FILE* file, /* 文件句柄地址 */
U8* buf, /* 数据缓冲地址 */
U16 len ); /* 要写入的字节数 */
函数描述:
函数tftpc_fwrite用于往文件中写入len个字节数据。此函数在MDK安装目录中的TFTPC_uif.c文件里面,属于底层接口函数,用户要在此函数里面添加具体的操作。
使用这个函数要注意以下问题:
使用举例:
U16 tftpc_fwrite (void *file, U8 *buf, U16 len) {
/* 将buf中的len字节写入到文件中,如果返回数值(实际写入的字节数)不等于len,数据传输将终止 */
return (fwrite (buf, 1, len, file));
}
函数原型:
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进行连接。
使用这个函数要注意以下问题:
使用举例:
/*
*********************************************************************************************************
* 宏定义,远程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[4] = {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 (1)
{
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);
}
}
函数原型:
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进行连接。
使用这个函数要注意以下问题:
使用举例:
/*
*********************************************************************************************************
* 宏定义,远程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[4] = {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 (1)
{
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);
}
}
本章节的测试稍麻烦些,需要大家配置工程,并且在电脑端建立一个TFTP服务器,而开发板是作为客户端,并且采用SD卡作为存储介质(测试前要准备好一个SD卡插到开发板上面),所以大家测试本章节配套的例子前,务必将这里的操作步骤全部看完才可以做测试!
另外有一点特别注意,我们使用的是RL-FlashFS文件系统,此文件系统的文件名仅支持ASCII字符,不支持中文,对于中文名的文件夹或者文件是无法操作的,因此,电脑端创建TFTP服务器的时候,使用的文件名也不要有中文。
获取电脑IP地址的方法很多,可以在网上邻居获取,也可以通过输入命令ipconfig获取:
获得电脑的IP地址是192.168.1.4。
根据刚获得的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,无需改动 */
第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服务器功能了,为了使得设置的功能起作用,务必关闭软件,然后重新打开。
设置完毕后,就可以测试文件的上传和下载功能了。
第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.5.4小节中下载到开发板SD卡中的client.pdf文件上传到电脑端。上传后的名字不换,还叫client.pdf。对于TFTP服务器软件,还继续用之前设置好的,这里什么都不用修改,用户仅需按下开发板上的K3按键,之后就可以看到如下上传进度,速度1MB/S左右。
上传完毕后,为了验证下载是否成功,需要大家查看之前创建的good文件夹中client.pdf文件是否可以正常打开并浏览,如果没有问题,说明上传成功,否则失败。
并且上传成功后,串口调试助手会打印如下信息:
至此,TFTP客户端的文件上传和下载功能就都测试完毕了。
配套例子:
V5-1060_RL-TCPnet实验_TFTP客户端(RTX)
实验目的:
实验内容:
实验操作:
详见本章节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, /* 任务函数 */
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键按下,直接发送事件标志给任务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(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功能的测试,此文件主要实现开发板从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[4] = {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 (1)
{
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, 1, 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, 1, len, file));
}
/*----------------------------------------------------------------------------
* end of file
*---------------------------------------------------------------------------*/
配套例子:
V6-1060_RL-TCPnet实验_TFTP客户端(RTX)
实验目的:
实验内容:
实验操作:
详见本章节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, /* 任务函数 */
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键按下,直接发送事件标志给任务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(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功能的测试,此文件主要实现开发板从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[4] = {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 (1)
{
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, 1, 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, 1, len, file));
}
/*----------------------------------------------------------------------------
* end of file
*---------------------------------------------------------------------------*/
本章节就为大家讲解这么多,其中TFTP的测试稍麻烦些,希望大家实际动手操作一遍,并将其熟练掌握。