【安富莱】【RL-TCPnet网络教程】第7章 RL-TCPnet网络协议栈移植(裸机)

时间:2022-12-30 15:56:42

第7章        RL-TCPnet网络协议栈移植(裸机)

本章教程为大家讲解RL-TCPnet网络协议栈的裸机移植方式,学习了上个章节讲解的底层驱动接口函数之后,移植就比较容易了,主要是添加库文件、配置文件和驱动文件即可。

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

7.1  移植前准备工作说明

7.2  STM32F407移植RL-TCPnet协议栈

7.3  STM32F429移植RL-TCPnet协议栈

7.4  总结

7.1  移植前准备工作说明

1、学习本章节前,务必要优先学习第6章的底层驱动讲解。

2、RL-TCPnet只有库,没有源码。库分为两个版本,一个用于调试的版本TCPD_CM3.lib和一个正式版本TCP_CM3.lib,当前的例子统一使用调试版本。另外注意,虽然是CM3版本的,但可同时用于CM3和CM4内核的MCU,因为官方没有专门的CM4内核库。

3、测试时,请将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。

而且使能了NetBIOS局域网域名,用户只需在电脑端ping armfly,就可以获得板子的IP地址。

4、如果要使用固定IP进行测试,请看第57章。

5、网口使用的是DM9161/9162(紧挨着9帧串口座的网口),而不是DM9000。

6、找一个简单的工程,最好是跑马灯之类的,越简单越好,我们就在这个简单的工程上面移植即可。

7.2   STM32F407移植RL-TCPnet协议栈

7.2.1      RL-TCPnet网络协议栈移植

首先准备好一个简单的裸机工程模板,工程模板的制作就不做讲解了,这里的重点是教大家移植RL-TCPnet协议栈。准备好的工程模板如下图所示(大家也可以制作其它任意的工程模板,不限制):

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

准备好工程模板后,就可以开始移植了。首先要做的就是将所有需要的文件放到工程模板里面。下面分三步跟大家进行说明,当然,不限制必须使用下面的方法添加源码到工程,只要将需要的文件添加到工程模板即可。

第1步:将我们裸机模板中制作好的RL-ARM文件夹复制粘贴到大家准备好的工程模板中。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

RL-ARM文件夹中有如下七个文件夹:

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Config文件夹用于存放RTX及其中间件的配置文件。

Driver文件夹用于存放中间件的驱动文件,也就是底层移植文件。

RL-CAN文件夹用于存放CAN总线的源码文件。

RL-FlashFS文件夹用于存放文件系统RL-FlashFS的库文件。

RL-RTX文件夹用于存放RTX的源码文件。

RL-TCPnet文件夹用于存放网络协议栈RL-TCPnet的库文件。

RL-USB文件夹用于存放USB协议栈RL-USB的库文件。

也许有用户会问:我们不是仅仅需要移植RL-TCPnet的相关文件就行了吗,为什么把RTX及其所有中间件都添加进来了?这样做的目的是为了以后升级的方便,如果需要添加USB、文件系统、CAN等组件,直接添加到工程即可。

这些文件全部来自MDK4.74的安装目录,库文件位于路径:C:\Keil_v474\ARM\RV31下,而驱动和配置文件位于路径:C:\Keil_v474\ARM\RL下。

第2步:添加RL-TCPnet的库文件、配置文件和驱动文件到工程。

添加完毕后的效果如下:

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Net_Config.c和NET_Debug.c在RL-ARM文件夹的Config文件里面。

TCPD_CM3.lib在RL-ARM文件夹的RL-TCPnet文件里面。

ETH_STM32F4xx.c和ETH_STM32F4xx.h在RL-ARM文件夹的Driver文件里面。

第3步:也是最后一步,添加相应的头文件路径。

在原来工程模板的基础上面新添加的三个路径:

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

至此,RL-TCPnet的移植工作就完成了,剩下就是系统配置和应用了。

7.2.2     RL-TCPnet配置说明(Net_Config.c)

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

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

【安富莱】【RL-TCPnet网络教程】第7章  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。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Ethernet Network Interface

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

(1)MAC Address

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

(2)IP Address

IP地址。

(3)Subnet mask

子网掩码。

(4)Default Gateway

默认网关。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Ethernet Network Interface

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

(1)NetBIOS Name Service

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

(2)Dynaminc Host Configuration

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

UDP Sockets

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

(1)Number of UDP Sockets

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

范围1 – 20。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

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接收窗口大小。

7.2.3     RL-TCPnet调试说明(Net_Debug.c)

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

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Print Time Stamp

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

其它所有的选项

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

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

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

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

关于调试功能的使用会在第11章详细为大家讲解,移植阶段将其全部关闭即可。

7.2.4     RL-TCPnet应用实例

为了验证移植的RL-TCPnet是否可以使用,需要添加测试代码。下面是编写的测试代码,配套的测试例子完整版是:V5-1000_RL-TCPnet实验_工程移植模板(裸机)。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

主函数初始化

在main.c文件实现:

/*

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

*    函 数 名: main

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

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 进入RL-TCPnet测试函数 */

     TCPnetTest();

}

硬件外设初始化

硬件外设的初始化是在 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_InitUart();    /* 初始化串口 */

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

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

     bsp_InitTimer();   /* 初始化滴答定时器 */

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP Server。

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

*                                      用于本文件的调试

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

*/

#define PORT_NUM       1001    /* TCP服务器监听端口号 */

/*

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

*                                         变量

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

*/

uint8_t socket_tcp;

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: soc  TCP Socket类型

*             evt  事件类型

*             ptr  事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址

*             par  事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号

*    返 回 值:

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

*/

U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par)

{

     char buf[];

     uint16_t i;

     /* 确保是socket_tcp的回调 */

     if (soc != socket_tcp)

     {

         return ();

     }

     switch (evt)

     {

         /*

              远程客户端连接消息

             1、数组ptr存储远程设备的IP地址,par中存储端口号。

             2、返回数值1允许连接,返回数值0禁止连接。

         */

         case TCP_EVT_CONREQ:

              sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[], ptr[], ptr[], ptr[]);

              printf_debug("IP:%s  port:%d\r\n", buf, par);

              return ();

         /* 连接终止 */

         case TCP_EVT_ABORT:

              break;

         /* Socket远程连接已经建立 */

         case TCP_EVT_CONNECT:

              printf_debug("Socket is connected to remote peer\r\n");

              break;

         /* 连接断开 */

         case TCP_EVT_CLOSE:

              printf_debug("Connection has been closed\r\n");

              break;

         /* 发送的数据收到远程设备应答 */

         case TCP_EVT_ACK:

              break;

          /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */

         case TCP_EVT_DATA:

              printf_debug("Data length = %d\r\n", par);

              for(i = ; i < par; i++)

              {

                   printf_debug("ptr[%d] = %d\r\n", i, ptr[i]);

              }

              break;

     }

     return ();

}

/*

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

*    函 数 名: TCP_StatusCheck

*    功能说明: 检测TCP的连接状态,主要用于网线插拔的判断

*    形    参: 无

*    返 回 值: __TRUE  连接

*             __FALSE 断开

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

*/

uint8_t TCP_StatusCheck(void)

{

     uint8_t res;

     switch (tcp_get_state(socket_tcp))

     {

         case TCP_STATE_FREE:

         case TCP_STATE_CLOSED:

              res = tcp_listen (socket_tcp, PORT_NUM);

              printf_debug("tcp listen res = %d\r\n", res);

              break;

         case TCP_STATE_LISTEN:

              break;

         case TCP_STATE_CONNECT:

              return (__TRUE);

         default: 

              break;

     }

     return (__FALSE);

}

/*

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

*    函 数 名: tcpnet_poll

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void tcpnet_poll(void)

{

     if(bsp_CheckTimer())

     {

         /* 此函数坚决不可以放在中断里面跑 */

         timer_tick ();

     }

     main_TcpNet ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t ulCount;

     uint8_t *sendbuf;

     uint8_t tcp_status;

     uint16_t maxlen;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /*

        创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。

        但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。

     */

    socket_tcp = tcp_get_socket (TCP_TYPE_SERVER | TCP_TYPE_KEEP_ALIVE, , , tcp_callback);

     if(socket_tcp != )

     {

          res = tcp_listen (socket_tcp, PORT_NUM);

         printf_debug("tcp listen res = %d\r\n", res);

     }

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(, );

     while ()

     {

         /* TCP轮询 */

         tcpnet_poll();

          /* 用于网线插拔的处理 */

         tcp_status = TCP_StatusCheck();

          /* 按键消息的处理 */

         ucKeyCode = bsp_GetKey();

         if ((ucKeyCode != KEY_NONE)&&(tcp_status == __TRUE))

         {

              switch (ucKeyCode)

              {

                   /* K1键按下,给远程TCP客户端发送8字节数据 */

                   case KEY_DOWN_K1:            

                       printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp));

                       ulCount = ;

                       do

                       {

                            tcpnet_poll();

                            if (tcp_check_send (socket_tcp) == __TRUE)

                            {

                                 maxlen = tcp_max_dsize (socket_tcp);

                                 ulCount -= maxlen;

                                 if(ulCount < )

                                 {

                                     /* 这么计算没问题的 */

                                     maxlen = ulCount + maxlen;

                                 }

                                 sendbuf = tcp_get_buf(maxlen);

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 /* 测试发现只能使用获取的内存 */

                                 tcp_send (socket_tcp, sendbuf, maxlen);

                            }

                       }while(ulCount > );

                       break;

                   /* K2键按下,给远程TCP客户端发送1024字节的数据 */

                   case KEY_DOWN_K2:     

                       printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp));

                       ulCount = ;

                       do

                       {

                            tcpnet_poll();

                            if (tcp_check_send (socket_tcp) == __TRUE)

                            {

                                 maxlen = tcp_max_dsize (socket_tcp);

                                 ulCount -= maxlen;

                                 if(ulCount < )

                                 {

                                     /* 这么计算没问题的 */

                                     maxlen = ulCount + maxlen;

                                 }

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf = tcp_get_buf(maxlen);

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 /* 测试发现只能使用获取的内存 */

                                 tcp_send (socket_tcp, sendbuf, maxlen);

                            }

                       }while(ulCount > );                     

                       break;

                   /* K3键按下,给远程TCP客户端发送5MB数据 */

                   case KEY_DOWN_K3:            

                       printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp));

                       ulCount = **;

                       do

                       {

                            tcpnet_poll();

                            if (tcp_check_send (socket_tcp) == __TRUE)

                            {

                                 maxlen = tcp_max_dsize (socket_tcp);

                                 ulCount -= maxlen;

                                 if(ulCount < )

                                 {

                                     /* 这么计算没问题的 */

                                     maxlen = ulCount + maxlen;

                                 }

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf = tcp_get_buf(maxlen);

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 /* 测试发现只能使用获取的内存 */

                                 tcp_send (socket_tcp, sendbuf, maxlen);

                            }

                       }while(ulCount > );

                       break;

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

                   default:                    

                       break;

              }

         }

     }

}

至此,裸机方式移植的RL-TCPnet就可以运行了。

7.2.5     RL-TCPnet实验测试和实验现象

测试前,先将开发板上面的DM9161/9162网口通过网线接到路由器或者交换机上面。

RJ45网络变压器插座上绿灯和黄灯现象

各种网卡、交换机等网络设备都不一样,一般来讲:绿灯分为亮或不亮(代表网络速度),黄灯分为闪烁或不闪烁(代表是否有数据收发)。

绿灯:长亮代表100M; 不亮代表10M。

黄灯:长亮代表无数据收发; 闪烁代表有数据收发。

也有些千兆网卡的灯以颜色区分,不亮代表10M / 绿色代表100M / 黄色代表1000M。现在10M的网络基本看不到了,如果一个灯长亮,基本可以说明100M网络或更高,而另一个灯时而闪烁,那代表有数据收发,具体要看网络设备了。甚至有些低等网卡如TP-LINK,只有一个灯,亮代表连通,闪烁代表数据收发。

对于STM32F407开发板上面的RJ45网络变压器插座上面的灯而言,绿灯代表数据收发,长亮的话表示无数据收发,闪烁代表有数据收发。黄灯代表网络速度,长亮代表100M,不亮代表10M。

底层驱动执行情况

为了验证RL-TCPnet底层驱动接口函数是否有问题,专门在ETH_STM32F4xx.c文件中配置了串口调试打印函数:

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_eth printf

#else

     #define printf_eth(...)

#endif

如果底层驱动正常执行了,打印的效果如下:

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

ping是否正确

ping命令的主要作用是通过发送数据包并接收应答信息来检测两台设备之间的网络是否连通。ping命令成功说明当前主机与目的主机之间存在连通的路径。如果不成功,需要查看网线是否连通、网卡设置是否正确、IP地址是否可用等。测试方法如下:

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

(2)弹出的命令窗口中,输入ping armfly,因为在前面的配置中我们使能了NetBIOS局域网域名,并将名字设置为armfly,而且使能了DHCP,通过ping命令还可以获得板子自动获取的IP地址。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

(3)输入ping armfly后,回车。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

收发相同,没有数据丢失,说明ping命令也是成功的。

电脑端创建一个TCP Client与板子上面的TCP Server通信

具体测试方法,查看第13章的13.6小节即可,因为配套例子实现的功能是一样的。

7.3  STM32F429移植RL-TCPnet协议栈

7.3.1     RL-TCPnet网络协议栈移植

首先准备好一个简单的裸机工程模板,工程模板的制作就不做讲解了,这里的重点是教大家移植RL-TCPnet协议栈。准备好的工程模板如下图所示(大家也可以制作其它任意的工程模板,不限制):

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

准备好工程模板后,就可以开始移植了。首先要做的就是将所有需要的文件放到工程模板里面。下面分三步跟大家进行说明,当然,不限制必须使用下面的方法添加源码到工程,只要将需要的文件添加到工程模板即可。

第1步:将我们裸机模板中制作好的RL-ARM文件夹复制粘贴到大家准备好的工程模板中。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

RL-ARM文件夹中有如下七个文件夹:

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Config文件夹用于存放RTX及其中间件的配置文件。

Driver文件夹用于存放中间件的驱动文件,也就是底层移植文件。

RL-CAN文件夹用于存放CAN总线的源码文件。

RL-FlashFS文件夹用于存放文件系统RL-FlashFS的库文件。

RL-RTX文件夹用于存放RTX的源码文件。

RL-TCPnet文件夹用于存放网络协议栈RL-TCPnet的库文件。

RL-USB文件夹用于存放USB协议栈RL-USB的库文件。

也许有用户会问:我们不是仅仅需要移植RL-TCPnet的相关文件就行了吗,为什么把RTX及其所有中间件都添加进来了?这样做的目的是为了以后升级的方便,如果需要添加USB、文件系统、CAN等组件,直接添加到工程即可。

这些文件全部来自MDK4.74的安装目录,库文件位于路径:C:\Keil_v474\ARM\RV31下,而驱动和配置文件位于路径:C:\Keil_v474\ARM\RL下。

第2步:添加RL-TCPnet的库文件、配置文件和驱动文件到工程。

添加完毕后的效果如下:

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Net_Config.c和NET_Debug.c在RL-ARM文件夹的Config文件里面。

TCPD_CM3.lib在RL-ARM文件夹的RL-TCPnet文件里面。

ETH_STM32F4xx.c和ETH_STM32F4xx.h在RL-ARM文件夹的Driver文件里面。

第3步:也是最后一步,添加相应的头文件路径。

在原来工程模板的基础上面新添加的三个路径:

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

至此,RL-TCPnet的移植工作就完成了,剩下就是系统配置和应用了。

7.3.2     RL-TCPnet配置说明(Net_Config.c)

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

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

【安富莱】【RL-TCPnet网络教程】第7章  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。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Ethernet Network Interface

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

(1)MAC Address

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

(2)IP Address

IP地址。

(3)Subnet mask

子网掩码。

(4)Default Gateway

默认网关。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Ethernet Network Interface

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

(1)NetBIOS Name Service

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

(2)Dynaminc Host Configuration

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

UDP Sockets

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

(1)Number of UDP Sockets

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

范围1 – 20。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

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接收窗口大小。

7.3.3     RL-TCPnet调试说明(Net_Debug.c)

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

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

Print Time Stamp

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

其它所有的选项

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

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

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

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

关于调试功能的使用会在第11章详细为大家讲解,移植阶段将其全部关闭即可。

7.3.4     RL-TCPnet应用实例

为了验证移植的RL-TCPnet是否可以使用,需要添加测试代码。下面是编写的测试代码,配套的测试例子完整版是:V6-1000_RL-TCPnet实验_工程移植模板(裸机)。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

主函数初始化

/*

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

*    函 数 名: main

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

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 进入RL-TCPnet测试函数 */

     TCPnetTest();

}

硬件外设初始化

硬件外设的初始化是在 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_InitUart();    /* 初始化串口 */

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

     bsp_InitTimer();    /* 初始化滴答定时器 */

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

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

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP Server。

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

*                                      用于本文件的调试

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

*/

#define PORT_NUM       1001    /* TCP服务器监听端口号 */

/*

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

*                                         变量

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

*/

uint8_t socket_tcp;

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: soc  TCP Socket类型

*             evt  事件类型

*             ptr  事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址

*             par  事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号

*    返 回 值:

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

*/

U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par)

{

     char buf[];

     uint16_t i;

     /* 确保是socket_tcp的回调 */

     if (soc != socket_tcp)

     {

         return ();

     }

     switch (evt)

     {

         /*

              远程客户端连接消息

             1、数组ptr存储远程设备的IP地址,par中存储端口号。

             2、返回数值1允许连接,返回数值0禁止连接。

         */

         case TCP_EVT_CONREQ:

              sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[], ptr[], ptr[], ptr[]);

              printf_debug("IP:%s  port:%d\r\n", buf, par);

              return ();

         /* 连接终止 */

         case TCP_EVT_ABORT:

              break;

         /* Socket远程连接已经建立 */

         case TCP_EVT_CONNECT:

              printf_debug("Socket is connected to remote peer\r\n");

              break;

         /* 连接断开 */

         case TCP_EVT_CLOSE:

              printf_debug("Connection has been closed\r\n");

              break;

         /* 发送的数据收到远程设备应答 */

         case TCP_EVT_ACK:

              break;

          /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */

         case TCP_EVT_DATA:

              printf_debug("Data length = %d\r\n", par);

              for(i = ; i < par; i++)

              {

                   printf_debug("ptr[%d] = %d\r\n", i, ptr[i]);

              }

              break;

     }

     return ();

}

/*

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

*    函 数 名: TCP_StatusCheck

*    功能说明: 检测TCP的连接状态,主要用于网线插拔的判断

*    形    参: 无

*    返 回 值: __TRUE  连接

*             __FALSE 断开

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

*/

uint8_t TCP_StatusCheck(void)

{

     uint8_t res;

     switch (tcp_get_state(socket_tcp))

     {

         case TCP_STATE_FREE:

         case TCP_STATE_CLOSED:

              res = tcp_listen (socket_tcp, PORT_NUM);

              printf_debug("tcp listen res = %d\r\n", res);

              break;

         case TCP_STATE_LISTEN:

              break;

         case TCP_STATE_CONNECT:

              return (__TRUE);

         default: 

              break;

     }

     return (__FALSE);

}

/*

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

*    函 数 名: tcpnet_poll

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void tcpnet_poll(void)

{

     if(bsp_CheckTimer())

     {

         /* 此函数坚决不可以放在中断里面跑 */

         timer_tick ();

     }

     main_TcpNet ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t ulCount;

     uint8_t *sendbuf;

     uint8_t tcp_status;

     uint16_t maxlen;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /*

        创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。

        但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。

     */

    socket_tcp = tcp_get_socket (TCP_TYPE_SERVER | TCP_TYPE_KEEP_ALIVE, , , tcp_callback);

     if(socket_tcp != )

     {

         res = tcp_listen (socket_tcp, PORT_NUM);

         printf_debug("tcp listen res = %d\r\n", res);

     }

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(, );

     while ()

     {

         /* TCP轮询 */

         tcpnet_poll();

          /* 用于网线插拔的处理 */

         tcp_status = TCP_StatusCheck();

          /* 按键消息的处理 */

         ucKeyCode = bsp_GetKey();

         if ((ucKeyCode != KEY_NONE)&&(tcp_status == __TRUE))

         {

              switch (ucKeyCode)

              {

                   /* K1键按下,给远程TCP客户端发送8字节数据 */

                   case KEY_DOWN_K1:            

                       printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp));

                       ulCount = ;

                       do

                       {

                            tcpnet_poll();

                            if (tcp_check_send (socket_tcp) == __TRUE)

                            {

                                 maxlen = tcp_max_dsize (socket_tcp);

                                 ulCount -= maxlen;

                                 if(ulCount < )

                                 {

                                     /* 这么计算没问题的 */

                                     maxlen = ulCount + maxlen;

                                 }

                                 sendbuf = tcp_get_buf(maxlen);

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 /* 测试发现只能使用获取的内存 */

                                 tcp_send (socket_tcp, sendbuf, maxlen);

                            }

                       }while(ulCount > );

                       break;

                   /* K2键按下,给远程TCP客户端发送1024字节的数据 */

                   case KEY_DOWN_K2:     

                       printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp));

                       ulCount = ;

                       do

                       {

                            tcpnet_poll();

                            if (tcp_check_send (socket_tcp) == __TRUE)

                            {

                                 maxlen = tcp_max_dsize (socket_tcp);

                                 ulCount -= maxlen;

                                 if(ulCount < )

                                 {

                                     /* 这么计算没问题的 */

                                     maxlen = ulCount + maxlen;

                                 }

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf = tcp_get_buf(maxlen);

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 /* 测试发现只能使用获取的内存 */

                                 tcp_send (socket_tcp, sendbuf, maxlen);

                            }

                       }while(ulCount > );                     

                       break;

                   /* K3键按下,给远程TCP客户端发送5MB数据 */

                   case KEY_DOWN_K3:            

                       printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp));

                       ulCount = **;

                       do

                       {

                            tcpnet_poll();

                            if (tcp_check_send (socket_tcp) == __TRUE)

                            {

                                 maxlen = tcp_max_dsize (socket_tcp);

                                 ulCount -= maxlen;

                                 if(ulCount < )

                                 {

                                     /* 这么计算没问题的 */

                                     maxlen = ulCount + maxlen;

                                 }

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf = tcp_get_buf(maxlen);

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 /* 测试发现只能使用获取的内存 */

                                 tcp_send (socket_tcp, sendbuf, maxlen);

                            }

                       }while(ulCount > );

                       break;

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

                   default:                    

                       break;

              }

         }

     }

}

至此,裸机方式移植的RL-TCPnet就可以运行了。

7.3.5     RL-TCPnet实验测试和实验现象

测试前,先将开发板上面的DM9161/9162网口通过网线接到路由器或者交换机上面。

RJ45网络变压器插座上绿灯和黄灯现象

各种网卡、交换机等网络设备都不一样,一般来讲:绿灯分为亮或不亮(代表网络速度),黄灯分为闪烁或不闪烁(代表是否有数据收发)。

绿灯:长亮代表100M; 不亮代表10M。

黄灯:长亮代表无数据收发; 闪烁代表有数据收发。

也有些千兆网卡的灯以颜色区分,不亮代表10M / 绿色代表100M / 黄色代表1000M。现在10M的网络基本看不到了,如果一个灯长亮,基本可以说明100M网络或更高,而另一个灯时而闪烁,那代表有数据收发,具体要看网络设备了。甚至有些低等网卡如TP-LINK,只有一个灯,亮代表连通,闪烁代表数据收发。

对于STM32F429开发板上面的RJ45网络变压器插座上面的灯而言,绿灯代表数据收发,长亮的话表示无数据收发,闪烁代表有数据收发。黄灯代表网络速度,长亮代表100M,不亮代表10M。

底层驱动执行情况

为了验证RL-TCPnet底层驱动接口函数是否有问题,专门在ETH_STM32F4xx.c文件中配置了串口调试打印函数:

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_eth printf

#else

     #define printf_eth(...)

#endif

如果底层驱动正常执行了,打印的效果如下:

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

ping是否正确

ping命令的主要作用是通过发送数据包并接收应答信息来检测两台设备之间的网络是否连通。ping命令成功说明当前主机与目的主机之间存在连通的路径。如果不成功,需要查看网线是否连通、网卡设置是否正确、IP地址是否可用等。测试方法如下:

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

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

(2)弹出的命令窗口中,输入ping armfly,因为在前面的配置中我们使能了NetBIOS局域网域名,并将名字设置为armfly,而且使能了DHCP,通过ping命令还可以获得板子自动获取的IP地址。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

(3)输入ping armfly后,回车。

【安富莱】【RL-TCPnet网络教程】第7章  RL-TCPnet网络协议栈移植(裸机)

收发相同,没有数据丢失,说明ping命令也是成功的。

电脑端创建一个TCP Client与板子上面的TCP Server通信

具体测试方法,查看第13章的13.6小节即可,因为配套例子实现的功能是一样的。

7.4  总结

本章节为大家讲解了RL-TCPnet网络协议栈的裸机移植方法,移植相对比较简单。另一个重要内容是Net_Config.c配置向导文件的说明,这个比较重要,初学者要好好熟悉下。