zoukankan      html  css  js  c++  java
  • 【STM32H7】第13章 RL-TCPnet V7.X之创建多个TCP客户端

    最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=95243

    第13章       RL-TCPnet之创建多个TCP客户端

    本章节为大家讲解RL-TCPnet的TCP多客户端实现,因为多客户端在实际项目中用到的地方还挺多,所以我们也专门开启一个章节做讲解。另外,学习本章节前,务必要优先学习第12章TCP客户端。学会创建一个TCP客户端了,创建多个客户端是一样的。

    13.1 初学者重要提示

    13.2 创建多个TCP客户端连接服务器

    13.3 系统配置说明(Net_Config.c)

    13.4 TCP配置说明(Net_Config_TCP.h)

    13.5 以太网配置说明(Net_Config_ETH.h)

    13.6 网络调试说明(Net_Debug.c)

    13.7 TCP客户端的实现方法

    13.8 网络调试助手和板子的调试操作步骤

    13.9 实验例程说明(RTX5)

    13.10 实验例程说明(FreeRTOS)

    13.11 总结

     

    13.1 初学者重要提示

    1.   学习本章节前,务必保证已经学习了第11章的TCP基础知识和第12章的TCP客户端。
    2.   相比前面章节的TCP服务器,TCP客户端的测试要稍麻烦些,例子中默认访问的TCP服务器端IP地址是192.168.1.2,端口号1001。大家测试时要根据自己电脑的实际IP地址设置app_tcpnet_lib.c文件中远程IP和端口。具体测试方法详看本章节的13.7小节。

    13.2 创建多个TCP客户端连接服务器

    本章节为大家讲解一个多TCP客户端连接服务器的实例,因为实际项目中,这种情况还比较多,所以也作为一期教程进行专门的讲解。

    有了上期教程的基础,本期教程也比较好实现,用户仅需多创建几个TCP客户端,并配置Net_Config_TCP.h中可以创建的TCP Socket个数即可。本章节配套的例子是创建了三个TCP客户端跟电脑端的服务器连接通信。

    13.3 系统配置说明(Net_Config.c)

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

     

      Network System Settings

    •   Local Host Name

    局域网域名。

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

    •   Memory Pool size

    参数范围1536-262144字节。

    内存池大小配置,单位字节。

    •   Start System Services

    开启系统服务。如果使能了此选项(打上对勾表示使能),系统将自动开启系统服务,比如HTTP, FTP, TFTP server等。如果没有使能,需要用户调用专门的API使能。

    •   OS Resource Settings
    • Core Thread Stack Size

    RL-TCPnet内核任务需要的栈大小,单位字节,范围512到65535。

    •   NET_THREAD_PRIORITY

    RL-TCPnet内核任务的优先级。

    这个选择在配置向导里面没有展示,需要大家点击上图左下角的Text Editor按钮查看宏定义修改。

    13.4 TCP配置说明(Net_Config_TCP.h)

    TCP配置文件:

     

      TCP Sockets

    •   Number of TCP Sockets

    范围1-20。

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

    • Number of Retries

    范围0-20。

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

    •   Retry Timeout in seconds

    范围1-10,单位秒。

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

    •   Default Connect Timeout in seconds

    范围1-600,单位秒。

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

    •  Maximum Segment Size

    范围536-1440,单位字节。

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

    •   Receive Window Size

    范围536-65535,单位字节。

    TCP接收窗口大小。

    13.5 以太网配置说明(Net_Config_ETH_0.h)

    以太网涉及到的配置选项比较多:

     

    Ethernet Network Interface 0

      Connect to hardware via Driver_ETH#

    用于指定驱动号,这个一般不需要用户去设置,比如RTE创建的文件名是Net_Config_ETH_0.h,就会自动将此参数设置为0。

      VLAN

    虚拟局域网。

    •   ETH0_VLAN_ID

    VLAN的ID号,12bit数值,范围1到4093。

      MAC Address

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

    注意,MAC地址的第1个字节的最后一个bit一定要是0。

      IP Address

    IP地址。

     Subnet mask

    子网掩码。

      Default Gateway

    默认网关。

      Primary DNS Server

    首选DNS服务器地址。

      Secondary DNS Server

    备选DNS服务器地址。

      IP Fragmentation

    使用发送IP报文的分片处理和接收IP报文的重组。

    •   MTU Size

    范围576-1500字节。

    最大的传输单元。

      ARP Address Resolution

    地址解析协议

    •   Cache Table size

    ARP Cache表大小。

    •   Cache Timeout in seconds

    Cache表超时时间。

    •   Number of Retries

    尝试解析IP地址的次数

    •   Resend Timeout in seconds

    每次解析请求的时间间隔。

    •   Send Notification on Address changes

    启用此选项后,嵌入式主机将在启动时或设备IP地址已更改时发送ARP通知。

      IGMP Group Management

    IGMP分组管理。

    •   Membership Table size

    此主机可以加入的分组数。

      NetBIOS Name Service

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

      Dynaminc Host Configuration

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

    •   Vendor Class Identifier

    厂商ID,如果设置了的话,会将其加到DHCP的请求消息中,用于识别网络设备的不同厂商。

    •   Bootfile Name

    从DHCP 服务器获取的引导文件名。

    •   NTP Servers

    从DCHP服务器获得NTP服务器列表。

    13.6 网络调试说明(Net_Debug.c)

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

     

      Print Time Stamp

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

      其它所有的选项

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

     

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

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

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

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

    13.7 TCP客户端的实现方法

    有了本章前面小节的配置后,剩下的问题就是TCP客户端的创建和TCP客户端数据收发的实现。

    13.7.1 创建三个TCP客户端

    TCP服务器的创建比较简单,调用函数netTCP_GetSocket即可(此函数的使用方法和注意事项在第12章有讲解),为了更好的管理这三个TCP客户端,专门为每个TCP客户端单独做一个C文件:

     

    app_tcpnet_lib.c,app_tcpclient1.c和app_tcpclient2.c分别是TCP客户端1,2和3。每个客户端都是通过单独创建一个任务实现:

    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskMsgPro
    *    功能说明: TCPnet应用任务
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal2  
    *********************************************************************************************************
    */
    void AppTaskMsgPro(void *argument)
    {
        while(1)
        {
            TCPnetTest();
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskClient1
    *    功能说明: TCPnet应用任务
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal2  
    *********************************************************************************************************
    */
    void AppTaskClient1(void *argument)
    {
        while(1)
        {
            TCPnetClient1();
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskClient2
    *    功能说明: TCPnet应用任务
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal2  
    *********************************************************************************************************
    */
    void AppTaskClient2(void *argument)
    {
        while(1)
        {
            TCPnetClient2();
        }    
    }

    下面以TCP客户端1为例说明,即任务AppTaskMsgPro里面创建的客户端:

    /*
    *********************************************************************************************************
    *                              宏定义,远程服务器的IP和端口
    *********************************************************************************************************
    */
    /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
    #define IP1            192
    #define IP2            168
    #define IP3            1
    #define IP4            2
    
    #define PORT_NUM         1001
    
    /* 这个是本地端口 */
    #define LocalPort_NUM    1024
    
    
    /*
    *********************************************************************************************************
    *    函 数 名: TCPnetClient1
    *    功能说明: TCPnet应用
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */    
    void TCPnetClient1(void)
    {
        int32_t iCount;
        uint8_t *sendbuf;
        uint32_t maxlen;
        netStatus res;
        const uint16_t usMaxBlockTime = 2; /* 延迟周期 */
        uint32_t EvtFlag;
    
        tcp_sock1 = netTCP_GetSocket (tcp_cb_client1);
        
        if (tcp_sock1 > 0) 
        {
            /* 使能TCP_TYPE_KEEP_ALIVE,会一直保持连接 */
            netTCP_SetOption (tcp_sock1, netTCP_OptionKeepAlive, 1);        
        }
    
        while (1) 
        {
            
            EvtFlag = osThreadFlagsWait(0x0000000FU, osFlagsWaitAny, usMaxBlockTime);
            
            /* 按键消息的处理 */
            switch (EvtFlag)
            {
                    
                   省略未写
                /* 接收到摇杆OK键按下,连接远程服务器 */
                case KEY4_BIT3:              
                    if (tcp_sock1 > 0) 
                    {
                        if(netTCP_GetState(tcp_sock1) != netTCP_StateESTABLISHED)
                        {
                            res = netTCP_Connect (tcp_sock1, (NET_ADDR *)&addr, LocalPort_NUM);
                            printf_debug("%s
    ", ReVal_Table1[res]);
                        }
                    }
                    break;
    
                 /* 其他的键值不处理 */
                default:                     
                    break;
            }
        }
    }

    代码比较好理解,由函数netTCP_GetSocket创建,再通过netTCP_Connect连接服务器。

    13.7.2 TCP数据发送

    TCP Socket的数据发送一定要注意各个函数调用顺序和使用方法,非常重要!否则,数据发送很容易失败。数据发送所用到函数的使用方法和注意事项在第12章有讲解。下面的代码中对数据发送专门做了处理,支持任意字节大小的数据发送,仅需修改计数变量iCount的初始值即可,初始值是多少,就是发送多少字节。

    /*
    *********************************************************************************************************
    *    函 数 名: TCPnetTest
    *    功能说明: TCPnet应用
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */    
    void TCPnetTest(void)
    {
        int32_t iCount;
        uint8_t *sendbuf;
        uint32_t maxlen;
        netStatus res;
        const uint16_t usMaxBlockTime = 2; /* 延迟周期 */
        uint32_t EvtFlag;
    
        tcp_sock = netTCP_GetSocket (tcp_cb_client);
        
        if (tcp_sock > 0) 
        {
            /* 使能TCP_TYPE_KEEP_ALIVE,会一直保持连接 */
            netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);        
        }
    
        while (1) 
        {
            
            EvtFlag = osThreadFlagsWait(0x0000000FU, osFlagsWaitAny, usMaxBlockTime);
            
            /* 按键消息的处理 */
            switch (EvtFlag)
            {
                /* 接收到K1键按下,给远程TCP客户端发送4096字节数据 */
                case KEY1_BIT0:              
                    iCount = 4096;
                    if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
                    {
                        do
                        {
                            if(netTCP_SendReady(tcp_sock) == true )
                            {
                                maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
    
                                iCount -= maxlen;
                                
                                if(iCount < 0)
                                {
                                    /* 这么计算没问题的 */
                                    maxlen = iCount + maxlen;
                                }
                                
                                sendbuf = netTCP_GetBuffer (maxlen);
                                sendbuf[0] = '1';
                                sendbuf[1] = '2';
                                sendbuf[2] = '3';
                                sendbuf[3] = '4';
                                sendbuf[4] = '5';
                                sendbuf[5] = '6';
                                sendbuf[6] = '7';
                                sendbuf[7] = '8';
                                
                                /* 必须使用申请的内存空间 */
                                netTCP_Send (tcp_sock, sendbuf, maxlen);
                            }
                            
                        }while(iCount > 0);
                    }
                    break;
                    
                 /* 其他的键值不处理 */
                default:                     
                    break;
            }
        }
    }

    13.7.3 TCP数据接收

    TCP数据接收主要是通过函数netTCP_GetSocket的回调函数实现(RTX5和FreeRTOS):

    /*
    *********************************************************************************************************
    *    函 数 名: tcp_cb_client
    *    功能说明: TCP Socket的回调函数
    *    形    参: socket  句柄
    *             event   事件类型
    *             addr    NET_ADDR类型变量,记录IP地址,端口号。
    *             buf     ptr指向的缓冲区记录着接收到的TCP数据。
    *             len     记录接收到的数据个数。
    *    返 回 值: 
    *********************************************************************************************************
    */
    uint32_t tcp_cb_client (int32_t socket, netTCP_Event event,
                            const NET_ADDR *addr, const uint8_t *buf, uint32_t len) 
    {
        switch (event) 
        {
            /*
                远程客户端连接消息
                1、数组ptr存储远程设备的IP地址,par中存储端口号。
                2、返回数值1允许连接,返回数值0禁止连接。
            */
            case netTCP_EventConnect:
                return (1);
    
            /* Socket远程连接已经建立 */
            case netTCP_EventEstablished:
                printf_debug("Socket is connected to remote peer
    ");
                break;
    
            /* 连接断开 */
            case netTCP_EventClosed:
                printf_debug("Connection has been closed
    ");
                break;
    
            /* 连接终止 */
            case netTCP_EventAborted:
                break;
    
            /* 发送的数据收到远程设备应答 */
            case netTCP_EventACK:
                break;
    
            /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
            case netTCP_EventData:
                printf_debug("Data length = %d
    ", len);
                printf ("%.*s
    ",len, buf);
                break;
        }
        return (0);
    }

    TCP服务器的数据接收主要是通过回调函数的TCP_EVT_DATA消息实现,进入消息后,指针变量buf是接收数据缓冲区首地址,变量len记录接收到的数据长度,单位字节。

    13.8 网络调试助手和板子的调试操作步骤

    我们这里使用下面这款调试助手,任何其它网络调试助手均可,不限制:

     http://www.armbbs.cn/forum.php?mod=viewthread&tid=1568

    重要提示,操作的过程中务必要优先在电脑端创建TCP服务器并开启,然后再操作板子进行连接。因为本章节配套的实例在按键按下后调用函数netTCP_Connect只进行一次连接,如果在Net_Config_TCP.c文件中配置的重连次数范围内无法连接上,就不会再进行连接了,需要再次点击按键进行连接。

    13.8.1 获取板子IP地址

    (说明,对于TCP客户端实验,这步已经不需要了,不过大家还可以进行测试)

    首先,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置中使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址。测试方法如下:

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

     

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

     

    •   输入ping armfly后,回车。

     

    获得IP地址是192.168.1.6。

    13.8.2 获取电脑的IP地址

    获取电脑IP地址的方法很多,可以在网上邻居获取,也可以通过输入命令ipconfig获取,方法跟上面13.8.1小节中的方式一样:

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

     

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

     

    •   输入ipconfig后,回车。

     

    获得电脑的IP地址是192.168.1.2。

    13.8.3 在程序中配置要访问的远程IP地址和端口

    据前面13.8.2小节获取的电脑端IP地址,需要大家配置程序中app_tcpnet_lib.c文件开头的宏定义,其中IP地址填前面获取的192.168.1.2,大家要根据电脑实际的IP地址填写。而端口号,我们这里随意配置一个即可,配置为1001,后面电脑端使用网络调试助手创建TCP服务器时,务必要跟这个端口号统一:

    /*
    *********************************************************************************************************
    *                              宏定义,远程服务器的IP和端口
    *********************************************************************************************************
    */
    /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
    #define IP1            192
    #define IP2            168
    #define IP3            1
    #define IP4            2
    
    #define PORT_NUM         1001

    13.8.4 网络调试助手创建TCP服务器

    •   打开调试助手,点击左上角创建服务器:

     

    •   弹出如下界面,指定IP设置为192.168.1.2,一定要跟13.8.2小节中获得的电脑IP地址一致,端口号1001,最后点击确定:

     

    •   确定后的界面效果如下:

     

    •   然后点击启动服务器:

     

    如果开发板下载了TCP客户端的程序,并且开发板已经上电,按下板子上面的K1按键,可以看到三个TCP客户端已经加入:

     

    13.8.5 TCP客户端发送数据

    板子和网络调试助手建立连接后就可以互相收发数据了。对于发送数据,三个TCP客户端都可以给服务器发送数据。

    •    摇杆上键按下,TCP客户端1发送4096字节,每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。

     

    •  摇杆左键按下,TCP客户端2发送4096字节,每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。

     

    •   摇杆右键按下,TCP客户端3发送4096字节,每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。

     

    13.8.6 TCP客户端接收数据

    TCP服务器接收数据的测试也比较方便,我们这里通过网络调试助手给板子发送1到5,共5个字符。

    •   TCP客户端1数据接收测试。

     

    点击发送后,可以看到串口软件打印出接收到的5个字符:

     

    测试也是没问题的。

    •   TCP客户端2数据接收测试。

     

    点击发送后,可以看到串口软件打印出接收到的5个字符:

     

    测试也是没问题的。

    •   TCP客户端3数据接收测试

    点击发送后,可以看到串口软件打印出接收到的5个字符:

     

    测试也是没问题的。

    13.9 实验例程说明(RTX5)

    配套例子:

    V7-1010_RL-TCPnet V7.X实验_多个TCP客户端连接(RTX5)

    实验目的:

    1. 学习RTX5 + RL-TCPnet的多个TCP客户端制作。

    实验内容:

    1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。客户端的例子相比服务器的例子稍麻烦些,因为客户端的例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要根据实际情况重新配置,如果不会配置,看本例程对应的教程即可):

      #define IP1            192

      #define IP2            168

      #define IP3            1

      #define IP4            2                

      #define PORT_NUM       1001

    2. 本例程可以创建三个TCP Client,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,三个TCP Client的端口号分别是1024,1025和1026。用户可以在电脑端用网络调试软件创建TCP Server跟这三个客户端建立连接。执行下面5--8步的操作时,优先将电脑端的TCP Server建立起来!!
    3. 摇杆OK键按下,创建三个TCP客户端,端口号分别是1024,1025和1026。
    4. 按键K1按下,TCP客户端1发送4096字节数据给服务器。
    5. 按键K2按下,TCP客户端2发送4096字节数据给服务器。
    6. 按键K3按下,TCP客户端3发送4096字节数据给服务器。

    实验操作:

    详见本章节13.8小节。

    系统配置说明(Net_Config.c):

    详见本章节13.3小节。

    TCP配置说明(Net_Config_TCP.h):

    详见本章节13.4小节。

    以太网配置说明(Net_Config_ETH_0.h):

    详见本章节13.5小节。

    网络调试说明(Net_Debug.c):

    详见本章节13.6小节。

    RTX5配置:

    RTX5配置向导详情如下:

     

      System Configuration

    系统配置

    •   Global Dynamic Memory size

    全局动态内存大小,单位字节。

    当前配置为20480字节。

    •   Kernel Tick Frequency

    内核滴答时钟频率。

    当前配置为1KHz

    •   Round-Robin Thread switching

    使能时间片调度,并把时间片设置为5个,即5ms。

    •   OS_ISR_FIFO_QUEUE

    中断服务程序里面调用RTX5的API,需要用到这个FIFO队列,当前FIFO大小设置为16个。

    •   OS_ISR_FIFO_QUEUE

    中断服务程序里面调用RTX

      Thread Configuration

    任务配置。

    •   Default Thread Stack size

    默认的任务栈大小,单位字节。

    •   Idle Thread Stack size

    空闲任务栈大小,单位字节。

    •   Idle Thread Stack size

    空闲任务栈大小,单位字节。

    •   Stack overrun checking

    使能栈溢出检测。

    •   Stack usage watermark

    栈使用率。

    •   Privileged mode

    使能特权模式。

    RTX5任务调试信息:

     

    RL-TCPnet协议栈调试信息:

     

    程序设计:

      任务分配:

    AppTaskUserIF任务   : 按键消息处理。

    AppTaskLED任务     : LED闪烁。

    AppTaskMsgPro任务 : TCPnet应用任务,TCP客户端1。

    AppTaskClient1任务  : TCP客户端2。

    AppTaskClient2任务  : TCP客户端3。

    AppTaskEthCheck    : 网线插拔状态检测。

    AppTaskStart任务    : 启动任务,也是最高优先级任务,这里用作BSP驱动包处理。

    netCore_Thread任务  : TCPnet内核任务。

    netEth0_Thread任务  : TCPnet以太网接口任务。

    osRtxTimerThread任务: 定时器任务,TCPnet时间基准。

      系统栈大小分配:

     

      RTX5初始化

    /*
    *********************************************************************************************************
    *    函 数 名: main
    *    功能说明: 标准c程序入口。
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    int main (void) 
    {    
        /* HAL库,MPU,Cache,时钟等系统初始化 */
        System_Init();
    
        /* 内核开启前关闭HAL的时间基准 */
        HAL_SuspendTick();
        
        /* 内核初始化 */
        osKernelInitialize();                                  
    
        /* 创建启动任务 */
        ThreadIdStart = osThreadNew(AppTaskStart, NULL, &ThreadStart_Attr);  
    
        /* 开启多任务 */
        osKernelStart();
        
        while(1);
    }

      硬件外设初始化

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

    /*
    *********************************************************************************************************
    *    函 数 名: System_Init
    *    功能说明: 系统初始化,主要是MPU,Cache和系统时钟配置
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void System_Init(void)
    {
        
        /* 配置MPU */
        MPU_Config();
        
        /* 使能L1 Cache */
        CPU_CACHE_Enable();
    
        /* 
           STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
           - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
           - 设置NVIV优先级分组为4。
         */
        HAL_Init();
    
        /* 
           配置系统时钟到400MHz
           - 切换使用HSE。
           - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
        */
        SystemClock_Config();
    
        /* 
           Event Recorder:
           - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
           - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
        */    
    #if Enable_EventRecorder == 1  
        /* 初始化EventRecorder并开启 */
        EventRecorderInitialize(EventRecordAll, 1U);
        EventRecorderStart();
    #endif
    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: bsp_Init
    *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_Init(void)
    {
        bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       
        bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
        bsp_InitUart();    /* 初始化串口 */
        bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
        bsp_InitLed();        /* 初始化LED */    
        bsp_InitTimer();      /* 初始化滴答定时器 */
    }

      RTX任务创建:

    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskCreate
    *    功能说明: 创建应用任务
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void AppTaskCreate (void)
    {
        ThreadIdTaskEthCheck = osThreadNew(AppTaskEthCheck, NULL, &ThreadEthCheck_Attr);      
        ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr);  
        ThreadIdTaskUserIF = osThreadNew(AppTaskUserIF, NULL, &ThreadUserIF_Attr);  
    }

      几个RTX任务的实现:

    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskUserIF
    *    功能说明: 按键消息处理        
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal (数值越小优先级越低,这个跟uCOS相反)
    *********************************************************************************************************
    */
    void AppTaskUserIF(void *argument)
    {
        uint8_t ucKeyCode;
    
        while(1)
        {
            ucKeyCode = bsp_GetKey();
            
            if (ucKeyCode != KEY_NONE)
            {
                switch (ucKeyCode)
                {
                    /* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */
                    case KEY_DOWN_K1:
                        printf("K1键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit0被设置
    ");
                        osThreadFlagsSet(ThreadIdTaskMsgPro, KEY1_BIT0);                    
                        break;    
    
                    /* K2键按下,直接发送事件标志给任务AppTaskClient1,设置bit0 */
                    case KEY_DOWN_K2:
                        printf("K2键按下,直接发送事件标志给任务AppTaskClient1,bit0被设置
    ");
                        osThreadFlagsSet(ThreadIdTaskClient1, KEY1_BIT0);
                        break;
                    
                    /* K3键按下,直接发送事件标志给任务AppTaskClient2,设置bit0 */
                    case KEY_DOWN_K3:
                        printf("K3键按下,直接发送事件标志给任务AppTaskClient2,bit0被设置
    ");
                        osThreadFlagsSet(ThreadIdTaskClient2, KEY1_BIT0);
                        break;
                    
                    /* 摇杆OK键按下,直接发送事件标志给三个TCP任务,设置bit3 */
                    case JOY_DOWN_OK:
                        printf("OK键按下,直接发送事件标志给三个TCP任务,bit3被设置
    ");
                        osThreadFlagsSet(ThreadIdTaskMsgPro, KEY4_BIT3);
                        osThreadFlagsSet(ThreadIdTaskClient1, KEY4_BIT3);
                        osThreadFlagsSet(ThreadIdTaskClient2, KEY4_BIT3);
                        break;                
                    
                    /* 其他的键值不处理 */
                    default:                     
                        break;
                }
            }
            
            osDelay(20);
        }
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskLED
    *    功能说明: LED闪烁。
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal1 
    *********************************************************************************************************
    */
    void AppTaskLED(void *argument)
    {
        const uint16_t usFrequency = 200; /* 延迟周期 */
        uint32_t tick;
    
        /* 获取当前时间 */
        tick = osKernelGetTickCount(); 
        
        while(1)
        {
            bsp_LedToggle(2);
            /* 相对延迟 */
            tick += usFrequency;                          
            osDelayUntil(tick);
        }
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskMsgPro
    *    功能说明: TCPnet应用任务
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal2  
    *********************************************************************************************************
    */
    void AppTaskMsgPro(void *argument)
    {
        while(1)
        {
            TCPnetTest();
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskClient1
    *    功能说明: TCPnet应用任务
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal2  
    *********************************************************************************************************
    */
    void AppTaskClient1(void *argument)
    {
        while(1)
        {
            TCPnetClient1();
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskClient2
    *    功能说明: TCPnet应用任务
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal2  
    *********************************************************************************************************
    */
    void AppTaskClient2(void *argument)
    {
        while(1)
        {
            TCPnetClient2();
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskEthCheck
    *    功能说明: 检查网线插拔状态。
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal3  
    *********************************************************************************************************
    */
    void AppTaskEthCheck(void *argument)
    {
    
        /* 初始化变量 */
        ThreadIdTaskMsgPro = NULL;
        ThreadIdTaskClient1 = NULL;
        ThreadIdTaskClient2 = NULL;
        g_ucEthLinkStatus = 0;
        
        /* 初始化网络 */
        netInitialize();
        
        while(1)
        {
            /* 网线插拔处理,方便移植,大家也可以根据需要发送任务事件标志做处理 */
            switch (g_ucEthLinkStatus)
            {
                /* 插拔临时状态,无需处理 */
                case 0:
                case 1:    
                    break;    
    
                /* 网线插入,创应用任务 */
                case 2:
                    if(ThreadIdTaskMsgPro == NULL)
                    {    
                        printf_taskdbg("网线插入,创建应用任务
    ");
                        ThreadIdTaskMsgPro = osThreadNew(AppTaskMsgPro, NULL, &ThreadMsgPro_Attr);
                        ThreadIdTaskClient1 = osThreadNew(AppTaskClient1, NULL, &ThreadTCPClient1_Attr);
                        ThreadIdTaskClient2 = osThreadNew(AppTaskClient2, NULL, &ThreadTCPClient2_Attr);
                    }
                    break;
                
                /* 网线拔掉,复位网络,删除应用任务 */
                case 3:
                    printf_taskdbg("网线拔掉,复位网络,删除应用任务
    ");
    
                    /* 释放所有网络资源,含TCPnet内核任务和ETH接口任务 */    
                    netUninitialize();
                
                    printf_taskdbg("netUninitialize
    ");
    
                    /* 删除TCPnet应用任务 */
                    osThreadTerminate(ThreadIdTaskMsgPro);
                    osThreadTerminate(ThreadIdTaskClient1);
                    osThreadTerminate(ThreadIdTaskClient2);
    
                    ThreadIdTaskMsgPro = NULL;
                    g_ucEthLinkStatus = 0;
                
                    /* 重新初始化 */
                    netInitialize();
                
                    printf_taskdbg("netInitialize
    ");
    
                /* 其他的键值不处理 */
                default:                     
                    break;
            }
            
            osDelay(10);
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskStart
    *    功能说明: 启动任务,这里用作BSP驱动包处理。
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal4  
    *********************************************************************************************************
    */
    void AppTaskStart(void *argument)
    {
        const uint16_t usFrequency = 1; /* 延迟周期 */
        uint32_t tick;
        
        /* 初始化外设 */
        HAL_ResumeTick();
        bsp_Init();
    
        /* 创建任务 */
        AppTaskCreate();
    
        /* 获取当前时间 */
        tick = osKernelGetTickCount(); 
        
        while(1)
        {
            /* 需要周期性处理的程序,对应裸机工程调用的SysTick_ISR */
            bsp_ProPer1ms();
            
            /* 相对延迟 */
            tick += usFrequency;                          
            osDelayUntil(tick);
        }
    }

      RL-TCPnet功能测试

    三个TCP客户端的创建是类似的,这里以TCP客户端1的创建为例进行说明,在文件app_tcpnet_lib.c里面实现。

    /*
    *********************************************************************************************************
    *                              宏定义,远程服务器的IP和端口
    *********************************************************************************************************
    */
    /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
    #define IP1            192
    #define IP2            168
    #define IP3            0
    #define IP4            102
    
    #define PORT_NUM         1001
    
    /* 这个是本地端口 */
    #define LocalPort_NUM    1024
    
    /*
    *********************************************************************************************************
    *                                         变量
    *********************************************************************************************************
    */
    NET_ADDR4 addr = { NET_ADDR_IP4, PORT_NUM, IP1,IP2,IP3,IP4};
    int32_t tcp_sock;
    
    
    /* TCPnet API的返回值 */
    static const char * ReVal_Table[]= 
    {
        "netOK: Operation succeeded",
        "netBusy: Process is busy",
        "netError: Unspecified error",
        "netInvalidParameter: Invalid parameter specified",
        "netWrongState: Wrong state error",
        "netDriverError: Driver error",
        "netServerError: Server error",
        "netAuthenticationFailed: User authentication failed",
        "netDnsResolverError: DNS host resolver failed",
        "netFileError: File not found or file r/w error",
        "netTimeout: Operation timeout",
    };
    
    /*
    *********************************************************************************************************
    *    函 数 名: tcp_cb_client
    *    功能说明: TCP Socket的回调函数
    *    形    参: socket  句柄
    *             event   事件类型
    *             addr    NET_ADDR类型变量,记录IP地址,端口号。
    *             buf     ptr指向的缓冲区记录着接收到的TCP数据。
    *             len     记录接收到的数据个数。
    *    返 回 值: 
    *********************************************************************************************************
    */
    uint32_t tcp_cb_client (int32_t socket, netTCP_Event event,
                            const NET_ADDR *addr, const uint8_t *buf, uint32_t len) 
    {
        switch (event) 
        {
            /*
                远程客户端连接消息
                1、数组ptr存储远程设备的IP地址,par中存储端口号。
                2、返回数值1允许连接,返回数值0禁止连接。
            */
            case netTCP_EventConnect:
                return (1);
    
            /* Socket远程连接已经建立 */
            case netTCP_EventEstablished:
                printf_debug("Socket is connected to remote peer
    ");
                break;
    
            /* 连接断开 */
            case netTCP_EventClosed:
                printf_debug("Connection has been closed
    ");
                break;
    
            /* 连接终止 */
            case netTCP_EventAborted:
                break;
    
            /* 发送的数据收到远程设备应答 */
            case netTCP_EventACK:
                break;
    
            /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
            case netTCP_EventData:
                printf_debug("Data length = %d
    ", len);
                printf ("%.*s
    ",len, buf);
                break;
        }
        return (0);
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: TCPnetTest
    *    功能说明: TCPnet应用
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */    
    void TCPnetTest(void)
    {
        int32_t iCount;
        uint8_t *sendbuf;
        uint32_t maxlen;
        netStatus res;
        const uint16_t usMaxBlockTime = 2; /* 延迟周期 */
        uint32_t EvtFlag;
    
        tcp_sock = netTCP_GetSocket (tcp_cb_client);
        
        if (tcp_sock > 0) 
        {
            /* 使能TCP_TYPE_KEEP_ALIVE,会一直保持连接 */
            netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);        
        }
    
        while (1) 
        {
            
            EvtFlag = osThreadFlagsWait(0x0000000FU, osFlagsWaitAny, usMaxBlockTime);
            
            /* 按键消息的处理 */
            switch (EvtFlag)
            {
                /* 接收到K1键按下,给远程TCP客户端发送4096字节数据 */
                case KEY1_BIT0:              
                    iCount = 4096;
                    if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
                    {
                        do
                        {
                            if(netTCP_SendReady(tcp_sock) == true )
                            {
                                maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
    
                                iCount -= maxlen;
                                
                                if(iCount < 0)
                                {
                                    /* 这么计算没问题的 */
                                    maxlen = iCount + maxlen;
                                }
                                
                                sendbuf = netTCP_GetBuffer (maxlen);
                                sendbuf[0] = '1';
                                sendbuf[1] = '2';
                                sendbuf[2] = '3';
                                sendbuf[3] = '4';
                                sendbuf[4] = '5';
                                sendbuf[5] = '6';
                                sendbuf[6] = '7';
                                sendbuf[7] = '8';
                                
                                /* 必须使用申请的内存空间 */
                                netTCP_Send (tcp_sock, sendbuf, maxlen);
                            }
                            
                        }while(iCount > 0);
                    }
                    break;
                
                /* 接收到摇杆OK键按下,连接远程服务器 */
                case KEY4_BIT3:              
                    if (tcp_sock > 0) 
                    {
                        if(netTCP_GetState(tcp_sock) != netTCP_StateESTABLISHED)
                        {
                            res = netTCP_Connect (tcp_sock, (NET_ADDR *)&addr, LocalPort_NUM);
                            printf_debug("%s
    ", ReVal_Table[res]);
                        }
                    }
                    break;
                    
                 /* 其他的键值不处理 */
                default:                     
                    break;
            }
        }
    }

    13.10          实验例程说明(FreeRTOS)

    配套例子:

    V7-1011_RL-TCPnet V7.X实验_多个TCP客户端连接(FreeRTOS)

    实验目的:

    1. 学习FreeROS + RL-TCPnet的多个TCP客户端制作。

    实验内容:

    1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。客户端的例子相比服务器的例子稍麻烦些,因为客户端的例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要根据实际情况重新配置,如果不会配置,看本例程对应的教程即可):

      #define IP1            192

      #define IP2            168

      #define IP3            1

      #define IP4            2                

      #define PORT_NUM       1001

    2. 本例程可以创建三个TCP Client,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,三个TCP Client的端口号分别是1024,1025和1026。用户可以在电脑端用网络调试软件创建TCP Server跟这三个客户端建立连接。执行下面5--8步的操作时,优先将电脑端的TCP Server建立起来!!
    3. 摇杆OK键按下,创建三个TCP客户端,端口号分别是1024,1025和1026。
    4. 按键K1按下,TCP客户端1发送4096字节数据给服务器。
    5. 按键K2按下,TCP客户端2发送4096字节数据给服务器。
    6. 按键K3按下,TCP客户端3发送4096字节数据给服务器。

    实验操作:

    详见本章节13.8小节。

    系统配置说明(Net_Config.c):

    详见本章节13.3小节。

    TCP配置说明(Net_Config_TCP.h):

    详见本章节13.4小节。

    以太网配置说明(Net_Config_ETH_0.h):

    详见本章节13.5小节。

    网络调试说明(Net_Debug.c):

    详见本章节13.6小节。

    FreeRTOS配置:

    FreeRTOS配置向导详情如下:

     

    •   Minimal stack size

    最小任务栈大小,主要是空闲任务,单位字(4个字节)。

    当前设置的是512字节。

    •   Total heap size

    FreeRTOS总的堆大小,单位字节。

    当前设置的30960字节。

    •   Kernel tick frequency

    FreeRTOS的系统时钟节拍。

    当前设置的是1KHz。

    •   Timer task stack depth

    定时器任务栈大小,单位字(4字节)。

    当前设置的2048字节。

    •   Timer task priority

    定时器任务优先级。

    当前设置的48。

    •   Timer queue length

    定时器消息队列大小。

    •   Use time slicing

    使能时间片调度,这个选项非常重要,RL-TCPnet V7.X用于FreeRTOS版要用到。

    FreeRTOS任务调试信息:

     

    RL-TCPnet协议栈调试信息:

     

    程序设计:

      任务分配:

    AppTaskUserIF任务   : 按键消息处理。

    AppTaskLED任务     : LED闪烁。

    AppTaskMsgPro任务 : TCPnet应用任务,TCP客户端1。

    AppTaskClient1任务  : TCP客户端2。

    AppTaskClient2任务  : TCP客户端3。

    AppTaskEthCheck    : 网线插拔状态检测。

    AppTaskStart任务    : 启动任务,也是最高优先级任务,这里用作BSP驱动包处理。

    netCore_Thread任务  : TCPnet内核任务。

    netEth0_Thread任务  : TCPnet以太网接口任务。

    osRtxTimerThread任务: 定时器任务,TCPnet时间基准。

      系统栈大小分配:

     

      FreeRTOS初始化

    /*
    *********************************************************************************************************
    *    函 数 名: main
    *    功能说明: 标准c程序入口。
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    int main (void) 
    {    
        /* HAL库,MPU,Cache,时钟等系统初始化 */
        System_Init();
    
        /* 内核开启前关闭HAL的时间基准 */
        HAL_SuspendTick();
        
        /* 内核初始化 */
        osKernelInitialize();                                  
    
        /* 创建启动任务 */
        ThreadIdStart = osThreadNew(AppTaskStart, NULL, &ThreadStart_Attr);  
    
        /* 开启多任务 */
        osKernelStart();
        
        while(1);
    }

      硬件外设初始化

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

    /*
    *********************************************************************************************************
    *    函 数 名: System_Init
    *    功能说明: 系统初始化,主要是MPU,Cache和系统时钟配置
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void System_Init(void)
    {
        
        /* 配置MPU */
        MPU_Config();
        
        /* 使能L1 Cache */
        CPU_CACHE_Enable();
    
        /* 
           STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
           - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
           - 设置NVIV优先级分组为4。
         */
        HAL_Init();
    
        /* 
           配置系统时钟到400MHz
           - 切换使用HSE。
           - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
        */
        SystemClock_Config();
    
        /* 
           Event Recorder:
           - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
           - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
        */    
    #if Enable_EventRecorder == 1  
        /* 初始化EventRecorder并开启 */
        EventRecorderInitialize(EventRecordAll, 1U);
        EventRecorderStart();
    #endif
    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: bsp_Init
    *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_Init(void)
    {
        bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       
        bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
        bsp_InitUart();    /* 初始化串口 */
        bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
        bsp_InitLed();        /* 初始化LED */    
        bsp_InitTimer();      /* 初始化滴答定时器 */
    }

      FreeRTOS任务创建:

    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskCreate
    *    功能说明: 创建应用任务
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void AppTaskCreate (void)
    {
        ThreadIdTaskEthCheck = osThreadNew(AppTaskEthCheck, NULL, &ThreadEthCheck_Attr);      
        ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr);  
        ThreadIdTaskUserIF = osThreadNew(AppTaskUserIF, NULL, &ThreadUserIF_Attr);  
    }

      几个FreeRTOS任务的实现:

    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskUserIF
    *    功能说明: 按键消息处理        
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal (数值越小优先级越低,这个跟uCOS相反)
    *********************************************************************************************************
    */
    void AppTaskUserIF(void *argument)
    {
        uint8_t ucKeyCode;
    
        while(1)
        {
            ucKeyCode = bsp_GetKey();
            
            if (ucKeyCode != KEY_NONE)
            {
                switch (ucKeyCode)
                {
                    /* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */
                    case KEY_DOWN_K1:
                        printf("K1键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit0被设置
    ");
                        osThreadFlagsSet(ThreadIdTaskMsgPro, KEY1_BIT0);                    
                        break;    
    
                    /* K2键按下,直接发送事件标志给任务AppTaskClient1,设置bit0 */
                    case KEY_DOWN_K2:
                        printf("K2键按下,直接发送事件标志给任务AppTaskClient1,bit0被设置
    ");
                        osThreadFlagsSet(ThreadIdTaskClient1, KEY1_BIT0);
                        break;
                    
                    /* K3键按下,直接发送事件标志给任务AppTaskClient2,设置bit0 */
                    case KEY_DOWN_K3:
                        printf("K3键按下,直接发送事件标志给任务AppTaskClient2,bit0被设置
    ");
                        osThreadFlagsSet(ThreadIdTaskClient2, KEY1_BIT0);
                        break;
                    
                    /* 摇杆OK键按下,直接发送事件标志给三个TCP任务,设置bit3 */
                    case JOY_DOWN_OK:
                        printf("OK键按下,直接发送事件标志给三个TCP任务,bit3被设置
    ");
                        osThreadFlagsSet(ThreadIdTaskMsgPro, KEY4_BIT3);
                        osThreadFlagsSet(ThreadIdTaskClient1, KEY4_BIT3);
                        osThreadFlagsSet(ThreadIdTaskClient2, KEY4_BIT3);
                        break;                
                    
                    /* 其他的键值不处理 */
                    default:                     
                        break;
                }
            }
            
            osDelay(20);
        }
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskLED
    *    功能说明: LED闪烁。
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal1 
    *********************************************************************************************************
    */
    void AppTaskLED(void *argument)
    {
        const uint16_t usFrequency = 200; /* 延迟周期 */
        uint32_t tick;
    
        /* 获取当前时间 */
        tick = osKernelGetTickCount(); 
        
        while(1)
        {
            bsp_LedToggle(2);
            /* 相对延迟 */
            tick += usFrequency;                          
            osDelayUntil(tick);
        }
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskMsgPro
    *    功能说明: TCPnet应用任务
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal2  
    *********************************************************************************************************
    */
    void AppTaskMsgPro(void *argument)
    {
        while(1)
        {
            TCPnetTest();
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskClient1
    *    功能说明: TCPnet应用任务
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal2  
    *********************************************************************************************************
    */
    void AppTaskClient1(void *argument)
    {
        while(1)
        {
            TCPnetClient1();
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskClient2
    *    功能说明: TCPnet应用任务
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal2  
    *********************************************************************************************************
    */
    void AppTaskClient2(void *argument)
    {
        while(1)
        {
            TCPnetClient2();
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskEthCheck
    *    功能说明: 检查网线插拔状态。
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal3  
    *********************************************************************************************************
    */
    void AppTaskEthCheck(void *argument)
    {
    
        /* 初始化变量 */
        ThreadIdTaskMsgPro = NULL;
        ThreadIdTaskClient1 = NULL;
        ThreadIdTaskClient2 = NULL;
        g_ucEthLinkStatus = 0;
        
        /* 初始化网络 */
        netInitialize();
        
        while(1)
        {
            /* 网线插拔处理,方便移植,大家也可以根据需要发送任务事件标志做处理 */
            switch (g_ucEthLinkStatus)
            {
                /* 插拔临时状态,无需处理 */
                case 0:
                case 1:    
                    break;    
    
                /* 网线插入,创应用任务 */
                case 2:
                    if(ThreadIdTaskMsgPro == NULL)
                    {    
                        printf_taskdbg("网线插入,创建应用任务
    ");
                        ThreadIdTaskMsgPro = osThreadNew(AppTaskMsgPro, NULL, &ThreadMsgPro_Attr);
                        ThreadIdTaskClient1 = osThreadNew(AppTaskClient1, NULL, &ThreadTCPClient1_Attr);
                        ThreadIdTaskClient2 = osThreadNew(AppTaskClient2, NULL, &ThreadTCPClient2_Attr);
                    }
                    break;
                
                /* 网线拔掉,复位网络,删除应用任务 */
                case 3:
                    printf_taskdbg("网线拔掉,复位网络,删除应用任务
    ");
    
                    /* 释放所有网络资源,含TCPnet内核任务和ETH接口任务 */    
                    netUninitialize();
                
                    printf_taskdbg("netUninitialize
    ");
    
                    /* 删除TCPnet应用任务 */
                    osThreadTerminate(ThreadIdTaskMsgPro);
                    osThreadTerminate(ThreadIdTaskClient1);
                    osThreadTerminate(ThreadIdTaskClient2);
    
                    ThreadIdTaskMsgPro = NULL;
                    g_ucEthLinkStatus = 0;
                
                    /* 重新初始化 */
                    netInitialize();
                
                    printf_taskdbg("netInitialize
    ");
    
                /* 其他的键值不处理 */
                default:                     
                    break;
            }
            
            osDelay(10);
        }    
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: AppTaskStart
    *    功能说明: 启动任务,这里用作BSP驱动包处理。
    *    形    参: 无
    *    返 回 值: 无
    *   优 先 级: osPriorityNormal4  
    *********************************************************************************************************
    */
    void AppTaskStart(void *argument)
    {
        const uint16_t usFrequency = 1; /* 延迟周期 */
        uint32_t tick;
        
        /* 初始化外设 */
        HAL_ResumeTick();
        bsp_Init();
    
        /* 创建任务 */
        AppTaskCreate();
    
        /* 获取当前时间 */
        tick = osKernelGetTickCount(); 
        
        while(1)
        {
            /* 需要周期性处理的程序,对应裸机工程调用的SysTick_ISR */
            bsp_ProPer1ms();
            
            /* 相对延迟 */
            tick += usFrequency;                          
            osDelayUntil(tick);
        }
    }

      RL-TCPnet功能测试

    三个TCP客户端的创建是类似的,这里以TCP客户端1的创建为例进行说明,在文件app_tcpnet_lib.c里面实现。

    /*
    *********************************************************************************************************
    *                              宏定义,远程服务器的IP和端口
    *********************************************************************************************************
    */
    /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
    #define IP1            192
    #define IP2            168
    #define IP3            1
    #define IP4            2
    
    #define PORT_NUM         1001
    
    /* 这个是本地端口 */
    #define LocalPort_NUM    1024
    
    /*
    *********************************************************************************************************
    *                                         变量
    *********************************************************************************************************
    */
    NET_ADDR4 addr = { NET_ADDR_IP4, PORT_NUM, IP1,IP2,IP3,IP4};
    int32_t tcp_sock;
    
    
    /* TCPnet API的返回值 */
    static const char * ReVal_Table[]= 
    {
        "netOK: Operation succeeded",
        "netBusy: Process is busy",
        "netError: Unspecified error",
        "netInvalidParameter: Invalid parameter specified",
        "netWrongState: Wrong state error",
        "netDriverError: Driver error",
        "netServerError: Server error",
        "netAuthenticationFailed: User authentication failed",
        "netDnsResolverError: DNS host resolver failed",
        "netFileError: File not found or file r/w error",
        "netTimeout: Operation timeout",
    };
    
    /*
    *********************************************************************************************************
    *    函 数 名: tcp_cb_client
    *    功能说明: TCP Socket的回调函数
    *    形    参: socket  句柄
    *             event   事件类型
    *             addr    NET_ADDR类型变量,记录IP地址,端口号。
    *             buf     ptr指向的缓冲区记录着接收到的TCP数据。
    *             len     记录接收到的数据个数。
    *    返 回 值: 
    *********************************************************************************************************
    */
    uint32_t tcp_cb_client (int32_t socket, netTCP_Event event,
                            const NET_ADDR *addr, const uint8_t *buf, uint32_t len) 
    {
        switch (event) 
        {
            /*
                远程客户端连接消息
                1、数组ptr存储远程设备的IP地址,par中存储端口号。
                2、返回数值1允许连接,返回数值0禁止连接。
            */
            case netTCP_EventConnect:
                return (1);
    
            /* Socket远程连接已经建立 */
            case netTCP_EventEstablished:
                printf_debug("Socket is connected to remote peer
    ");
                break;
    
            /* 连接断开 */
            case netTCP_EventClosed:
                printf_debug("Connection has been closed
    ");
                break;
    
            /* 连接终止 */
            case netTCP_EventAborted:
                break;
    
            /* 发送的数据收到远程设备应答 */
            case netTCP_EventACK:
                break;
    
            /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
            case netTCP_EventData:
                printf_debug("Data length = %d
    ", len);
                printf ("%.*s
    ",len, buf);
                break;
        }
        return (0);
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: TCPnetTest
    *    功能说明: TCPnet应用
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */    
    void TCPnetTest(void)
    {
        int32_t iCount;
        uint8_t *sendbuf;
        uint32_t maxlen;
        netStatus res;
        const uint16_t usMaxBlockTime = 2; /* 延迟周期 */
        uint32_t EvtFlag;
    
        tcp_sock = netTCP_GetSocket (tcp_cb_client);
        
        if (tcp_sock > 0) 
        {
            /* 使能TCP_TYPE_KEEP_ALIVE,会一直保持连接 */
            netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);        
        }
    
        while (1) 
        {
            
            EvtFlag = osThreadFlagsWait(0x0000000FU, osFlagsWaitAny, usMaxBlockTime);
            
            /* 按键消息的处理 */
            switch (EvtFlag)
            {
                /* 接收到K1键按下,给远程TCP客户端发送4096字节数据 */
                case KEY1_BIT0:              
                    iCount = 4096;
                    if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
                    {
                        do
                        {
                            if(netTCP_SendReady(tcp_sock) == true )
                            {
                                maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
    
                                iCount -= maxlen;
                                
                                if(iCount < 0)
                                {
                                    /* 这么计算没问题的 */
                                    maxlen = iCount + maxlen;
                                }
                                
                                sendbuf = netTCP_GetBuffer (maxlen);
                                sendbuf[0] = '1';
                                sendbuf[1] = '2';
                                sendbuf[2] = '3';
                                sendbuf[3] = '4';
                                sendbuf[4] = '5';
                                sendbuf[5] = '6';
                                sendbuf[6] = '7';
                                sendbuf[7] = '8';
                                
                                /* 必须使用申请的内存空间 */
                                netTCP_Send (tcp_sock, sendbuf, maxlen);
                            }
                            
                        }while(iCount > 0);
                    }
                    break;
                
                /* 接收到摇杆OK键按下,连接远程服务器 */
                case KEY4_BIT3:              
                    if (tcp_sock > 0) 
                    {
                        if(netTCP_GetState(tcp_sock) != netTCP_StateESTABLISHED)
                        {
                            res = netTCP_Connect (tcp_sock, (NET_ADDR *)&addr, LocalPort_NUM);
                            printf_debug("%s
    ", ReVal_Table[res]);
                        }
                    }
                    break;
                    
                 /* 其他的键值不处理 */
                default:                     
                    break;
            }
        }
    }

    13.11          总结

    本章节就为大家讲解这么多,希望大家多做测试,再多创建几个客户端进行测试。

  • 相关阅读:
    算法洗脑系列(8篇)——第五篇 分治思想
    算法洗脑系列(8篇)——第七篇 动态规划
    算法洗脑系列(8篇)——第四篇 枚举思想
    8天学通MongoDB——第二天 细说增删查改
    12篇学通C#网络编程——第一篇 基础之进程线程
    算法系列15天速成——第十五天 图【下】(大结局)
    算法洗脑系列(8篇)——第一篇 递推思想
    8天学通MongoDB——第三天 细说高级操作
    8天学通MongoDB——第四天 索引操作
    算法洗脑系列(8篇)——第八篇 概率思想
  • 原文地址:https://www.cnblogs.com/armfly/p/13793413.html
Copyright © 2011-2022 走看看