基于N32G457VEL7实现client代码

本文详细描述了如何在N32G45x平台上使用LWIP库实现TCP客户端,包括获取唯一MAC地址、设置默认IP配置以及创建和连接TCPsocket。代码中展示了如何初始化MAC地址和设置静态IP,以及心跳检测机制的配置。
摘要由CSDN通过智能技术生成

前言:

        由于国名技术官方资料库中只提供了HttpServer与TcpServer的例程,所以自己编写一份的TcpClient的代码。本文代码是在上一篇代码基础上进行编写。开发环境可参考我之前的一篇文章,链接:基于FreeRTOS的N32G457VEL7之LWIP网络协议栈移植-CSDN博客


1、实现client代码项目如下,其实就是在原有12 Nationstech.N32G45x_ETH这个代码的基础上进行实现,并修改文件夹名称,代码目录如下:

2、为了便于程序可读性,新建lwip_comm.c与lwip_comm.h,并添加到工程中。这两个文件用于保存网卡相关信息,并根据UID生成唯一的MAC地址,

参考国名技术用户手册可知UID 寄存器相关信息。

/**
 * @file lwip_comm.c
 */
#include "lwip_comm.h"
#include "drv_log.h"

__lwip_dev g_lwipdev = {0};                                       /* lwip控制结构体 */

/**
 * @breif       获取mac地址
 * @param       pMacBuf: 存储MAC地址buffer的首地址
 * @retval      无
 */
static void getmac(uint8_t* pMacBuf)
{
    if(pMacBuf == NULL)
        return;

    uint32_t uiMcuId = 0;
    uint8_t pMcuID[15] = {0};
    int i = 0;
    uint32_t CpuID[3] = {0};

    //获取CPU唯一ID
    CpuID[0]=*(uint32_t*)(0x1FFFF7F0);
    CpuID[1]=*(uint32_t*)(0x1FFFF7F4);
    CpuID[2]=*(uint32_t*)(0x1FFFF7F8);

    log_info("MCU UID: %08X-%08X-%08X\r\n",CpuID[0],CpuID[1],CpuID[2]);
        
    //按字节(8位)读取
    pMcuID[0] = (uint8_t)(CpuID[0] & 0x000000FF);
    pMcuID[1] = (uint8_t)((CpuID[0] & 0xFF00) >>8);
    pMcuID[2] = (uint8_t)((CpuID[0] & 0xFF0000) >>16);
    pMcuID[3] = (uint8_t)((CpuID[0] & 0xFF000000) >>24);
    
    pMcuID[4] = (uint8_t)(CpuID[1] & 0xFF);
    pMcuID[5] = (uint8_t)((CpuID[1] & 0xFF00) >>8);
    pMcuID[6] = (uint8_t)((CpuID[1] & 0xFF0000) >>16);
    pMcuID[7] = (uint8_t)((CpuID[1] & 0xFF000000) >>24);
    
    pMcuID[8] = (uint8_t)(CpuID[2] & 0xFF);
    pMcuID[9] = (uint8_t)((CpuID[2] & 0xFF00) >>8);
    pMcuID[10] = (uint8_t)((CpuID[2] & 0xFF0000) >>16);
    pMcuID[11] = (uint8_t)((CpuID[2] & 0xFF000000) >>24);

    uiMcuId = (CpuID[0]>>1)+(CpuID[1]>>2)+(CpuID[2]>>3);

    for(i=0; i<12; i++)         //获取McuID[12]
    {
            pMcuID[12] += pMcuID[i];        
    }
    for(i=0; i<12; i++)        //获取McuID[13]
    {
            pMcuID[13] ^= pMcuID[i];        
    }

    pMacBuf[0] = (uint8_t)(uiMcuId & 0xF0);
    pMacBuf[1] = (uint8_t)((uiMcuId & 0xFF00) >>8);
    pMacBuf[2] = (uint8_t)((uiMcuId & 0xFF0000) >>16);
    pMacBuf[3] = (uint8_t)((uiMcuId & 0xFF000000) >>24);
    pMacBuf[4] = pMcuID[12];
    pMacBuf[5] = pMcuID[13];
    
    return;
}

/**
 * @breif       lwip 默认IP设置
 * @param       lwipx: lwip控制结构体指针
 * @retval      无
 */
void lwip_comm_default_ip_set(__lwip_dev *lwipx)
{
    uint8_t s_local_mac[6];
	//初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复
	getmac(s_local_mac);//获取N32G45x的唯一MAC地址 

    /* MAC地址设置(高三字节固定为:2.0.0,低三字节用STM32唯一ID) */
    lwipx->mac[0] = s_local_mac[0]; 
    lwipx->mac[1] = s_local_mac[1]; 
    lwipx->mac[2] = s_local_mac[2]; 
    lwipx->mac[3] = s_local_mac[3]; 
    lwipx->mac[4] = s_local_mac[4]; 
    lwipx->mac[5] = s_local_mac[5]; 

    log_info("MAC_ADDR: %x %x %x %x %x %x\r\n",s_local_mac[0],s_local_mac[1],s_local_mac[2],s_local_mac[3],s_local_mac[4],s_local_mac[5]);

    /* 默认远端IP为:192.168.12.245 */
    lwipx->remoteip[0] = 192;
    lwipx->remoteip[1] = 168;
    lwipx->remoteip[2] = 12;
    lwipx->remoteip[3] = 245;
    
    /* 默认本地IP为:192.168.12.247 */
    lwipx->ip[0] = 192;
    lwipx->ip[1] = 168;
    lwipx->ip[2] = 12;
    lwipx->ip[3] = 247;

    /* 默认子网掩码:255.255.255.0 */
    lwipx->netmask[0] = 255;
    lwipx->netmask[1] = 255;
    lwipx->netmask[2] = 255;
    lwipx->netmask[3] = 0;
    
    /* 默认网关:192.168.12.1 */
    lwipx->gateway[0] = 192;
    lwipx->gateway[1] = 168;
    lwipx->gateway[2] = 12;
    lwipx->gateway[3] = 1;
}

3、打开lwip_port.c文件,修改网卡初始化相关代码。

①添加头文件lwip_comm.h。
#include <string.h>

#include "lwip/etharp.h"
#include "lwip/ip_addr.h"
#include "lwip/tcpip.h"
#include "lwip/tcp.h"
#include "lwip/dhcp.h"
#include "log.h"

#include "n32g45x.h"
#include "n32g45x_eth.h"
#include "n32g45x_rcc.h"
#include "misc.h"

#include "FreeRTOS.h"
#include "semphr.h"

#include "lwip_comm.h"
②屏蔽例程代码中默认的mac地址代码。
//const uint8_t mac_address[] = {0x20, 0x34, 0x56, 0x78, 0x9a, 0xbc};
③修改网卡初始化相关代码如下:
#if LWIP_DHCP
    ip.addr  = 0;
    msk.addr = 0;
    gw.addr  = 0;
#else  // !LWIP_DHCP
    lwip_comm_default_ip_set(&g_lwipdev);
    IP4_ADDR(&ip, g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
    IP4_ADDR(&msk, g_lwipdev.netmask[0],g_lwipdev.netmask[1] ,g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
    IP4_ADDR(&gw, g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
#endif // LWIP_DHCP

4、新建test_tcp_client.c文件并添加到工程中

/**
 * @file test_tcp_client.c
 * @author hyl
 * @version v1.0.0 
 * @date 2024/2/21
 */

#include "FreeRTOS.h"
#include "cmsis_os.h"
#include "task.h"
#include "lwip/sockets.h"
#include "lwip/tcpip.h"
#include "lwip/opt.h"
#include "lwip/sys.h"
#include "lwip/api.h"
#include "drv_log.h"
#include "lwip/tcp.h"
#include "lwip_comm.h"

/***********************************task init**********************************/
#define CLIENT_TASK_PRIO                osPriority_10      
#define CLIENT_TASK_STK_SIZE           ((unsigned short)1024)      
TaskHandle_t ClientThread_Handler;           
static void test_tcp_client_task(void const *p);      
/***********************************task end***********************************/


/* 需要自己设置远程IP地址 */
// #define SERVER_IP    "192.168.12.245"
#define SERVER_PORT           8080                              /* 连接的远程端口号 */
#define RECV_BUF_SIZE         1024                              /* 最大接收数据长度 */

/* 接收数据缓冲区 */
static char recv_buff[RECV_BUF_SIZE];  
static char send_buff[RECV_BUF_SIZE];  

/**
 * @brief  echo server.
 * @param  point to task parameters.
 * @param None.
 */
static void test_tcp_client_task(void const *p)
{
    int server_fd = -1; 
    err_t err;
    struct sockaddr_in server_addr;
    ip4_addr_t ipaddr;
    int recv_buf_len, write_buf_len;

    int so_keepalive_val = 1;       //使能心跳机制
    int tcp_keepalive_idle = 60;    //如该连接在60秒内没有任何数据往来,则进行探测 
    int tcp_keepalive_intvl = 5;    //探测时发包的时间间隔为5秒
    int tcp_keepalive_cnt = 3;      //探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.

    IP4_ADDR(&ipaddr,g_lwipdev.remoteip[0],g_lwipdev.remoteip[1],g_lwipdev.remoteip[2],g_lwipdev.remoteip[3]);
    err = err;
	
    while(1)
    {
        server_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (server_fd < 0)
        {
            log_info("server_fd error\r\n");
            close(server_fd);
            vTaskDelay(10);
            continue;
        } 
        //使能心跳机制,默认没有使能
        err = setsockopt(server_fd, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive_val, sizeof(int));
        server_addr.sin_family = AF_INET;                       /* 表示IPv4网络协议 */
        server_addr.sin_port = htons(SERVER_PORT);              /* 远程端口号 */
        server_addr.sin_addr.s_addr = ipaddr.addr;              /* 远程IP地址 */

        if (connect(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) < 0)
        {
            log_info("connect error\r\n");
            close(server_fd);
            vTaskDelay(50);
            continue;
        }
        else
        {
            log_info("connect success\r\n");
            //配置心跳检测参数,默认参数时间很长,不配置的话要等待很长时间。
            //可以在connect之前配置。如果断网,read就会阻塞相应的次数后就不阻塞了,就会一直报错,直到网线重新连接。
            err = setsockopt(server_fd, IPPROTO_TCP, TCP_KEEPIDLE, &tcp_keepalive_idle, sizeof(int));
            err = setsockopt(server_fd, IPPROTO_TCP, TCP_KEEPINTVL, &tcp_keepalive_intvl, sizeof(int));
            err = setsockopt(server_fd, IPPROTO_TCP, TCP_KEEPCNT, &tcp_keepalive_cnt, sizeof(int));
        }

        while (1)
        {
            memset(recv_buff,0,1024);
            memset(send_buff,0,1024);
            recv_buf_len = recv(server_fd, recv_buff, RECV_BUF_SIZE , MSG_WAITALL);
            
            if(recv_buf_len > 0)
            {
                /*send massage to client*/
                write_buf_len = snprintf(send_buff,1024,"Server IP:%s\r\nServer PORT:%d\r\nReceive Message:%s\r\n",\
                                        ipaddr_ntoa((void *)&server_addr.sin_addr.s_addr),\
                                        SERVER_PORT,\
                                        recv_buff);
                send(server_fd,send_buff,write_buf_len,MSG_WAITALL);
            }
            else if(recv_buf_len == 0 && errno == ENOTCONN)  //如果服务器断开
            {
                log_info("recv error %d\r\n",__LINE__);
                close(server_fd);        //关闭连接
                server_fd = -1;          //重新初始化
                break;
            }
            else
            {
                log_info("recv error %d\r\n",__LINE__);
                close(server_fd);
                server_fd = -1;
                break;
            }           
        }
    }
}

/**
 * @brief  Initialize tcp task.
 * @param None.
 * @param None.
 */
void test_tcp_client_init(void)
{
    UBaseType_t uxHighWaterMark;

	taskENTER_CRITICAL();//进入临界区
    osThreadDef(tcp_connect, test_tcp_client_task, CLIENT_TASK_PRIO, 0, CLIENT_TASK_STK_SIZE);
    
    ClientThread_Handler  = osThreadCreate(osThread(tcp_connect), NULL);
    uxHighWaterMark = uxTaskGetStackHighWaterMark(ClientThread_Handler);  
	log_info("test_tcp_client_task: uxHighWaterMark:%ld\r\n",uxHighWaterMark);	

    taskEXIT_CRITICAL();//退出临界区
}

5、修改main.c

/*****************************************************************************
 * Copyright (c) 2019, Nations Technologies Inc.
 *
 * All rights reserved.
 * ****************************************************************************
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Nations' name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY NATIONS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL NATIONS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************/

/**
 * @file main.c
 * @author Nations
 * @version v1.0.0
 *
 * @copyright Copyright (c) 2019, Nations Technologies Inc. All rights reserved.
 */
#include <stdio.h>
#include "drv_log.h"
#include "drv_rcc.h"
#include "drv_log_segger.h"

#include "FreeRTOS.h"
#include "cmsis_os.h"
#include "task.h"
#include "misc.h"

#include "lwip/netif.h"
#include "lwip/api.h"
#include "lwip/sockets.h"
#include "lwipopts.h"
#include "lwip_port.h"

/** @addtogroup N32G45X_StdPeriph_Examples
 * @{
 */
int errno ;
void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName)
{
    printf("stack overflow\n");
}

void vApplicationMallocFailedHook(void)
{
    printf("malloc failed\n");
}
/** @addtogroup USART_Printf
 * @{
 */

/***********************************task init**********************************/
#define START_TASK_PRIO         osPriority_5      
#define START_STK_SIZE          ((unsigned short)128)      
TaskHandle_t StartThread_Handler;           
static void StartThread(void const * argument);     

#define LWIP_DMEO_TASK_PRIO     osPriority_10         
#define LWIP_DMEO_STK_SIZE      ((unsigned short)2*128)     
TaskHandle_t LwipThread_Handler;
static void LwipThread(void const * argument);   
/***********************************task end***********************************/
//uint32_t PreviousWakeTime;

/**
 * @brief  Main program
 */
extern void test_tcp_server_init(void);
extern void test_tcp_client_init(void);
int main(void)
{
	//drv_SysClockInit_144MHZ();
    RCC_ClocksType clks;
	
	drv_log_init();
	drv_log_segger_Init();

    /* Output a message on Hyperterminal using printf function */
    RCC_GetClocksFreqValue(&clks);
    log_info("SYSCLK: %d\r\n", clks.SysclkFreq);
    log_info("HCLK: %d\r\n", clks.HclkFreq); 
    log_info("PCLK1: %d\r\n", clks.Pclk1Freq);
    log_info("PCLK2: %d\r\n", clks.Pclk2Freq);
    log_info("AdcPllClkFreq: %d\r\n", clks.AdcPllClkFreq);	
    log_info("AdcHclkFreq: %d\r\n", clks.AdcHclkFreq);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	/* Initialize all configured peripherals */
    osThreadDef(Start_Thread, StartThread, START_TASK_PRIO, 0, START_STK_SIZE);
    StartThread_Handler  = osThreadCreate(osThread(Start_Thread), NULL);

    /* Start scheduler */
	osKernelStart();	
		
    while (1){}
}

static void StartThread(void const * argument)
{	
	uint16_t u16Val = 0;
	UBaseType_t uxHighWaterMark;
	uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );  
	log_info("1---StartThread: uxHighWaterMark:%ld\r\n",uxHighWaterMark);	
		
	
	for(int i=0;i<5;i++)
	{
		osDelay(500);
		log_info("StartThread\r\n");
	}
	
	/* Initialize all configured peripherals */
    osThreadDef(Lwip_Thread, LwipThread, LWIP_DMEO_TASK_PRIO, 0, LWIP_DMEO_STK_SIZE);
    LwipThread_Handler  = osThreadCreate(osThread(Lwip_Thread), NULL);	
		
	u16Val = uxTaskGetNumberOfTasks();
	log_info("In all tasks:%d\r\n",u16Val);
	
	uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );  
	log_info("2---StartThread: uxHighWaterMark:%ld\r\n",uxHighWaterMark);	

	uxHighWaterMark = uxTaskGetStackHighWaterMark( LwipThread_Handler );  
	log_info("3---LwipThread: uxHighWaterMark:%ld\r\n",uxHighWaterMark);

	uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );  
	log_info("2---StartThread: uxHighWaterMark:%ld\r\n",uxHighWaterMark);		
	
	vTaskDelete(xTaskGetCurrentTaskHandle());
}	

static void LwipThread(void const * argument)
{	
    RCC_EnableAPB2PeriphClk(  RCC_APB2_PERIPH_AFIO  | RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_GPIOB | RCC_APB2_PERIPH_GPIOC
                            | RCC_APB2_PERIPH_GPIOD | RCC_APB2_PERIPH_GPIOE | RCC_APB2_PERIPH_GPIOF
                            | RCC_APB2_PERIPH_GPIOG ,
                            ENABLE);
    RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_ETHMAC, ENABLE);
    if (lwip_port_init())
    {
        printf("LWIP Init Falied!\n");
        while (1)
            ;
    }
	
    log_info("LWIP Init Success!\n");
    //test_tcp_server_init();    
    test_tcp_client_init();
    log_info("TCP Task Success!\n");	
	vTaskDelete(NULL);//删除开始任务
}

6、编译下载测试Demo效果


至此,Demo完全跑通,即便是服务器关闭了,client端也会在一定的时间内保持连接,超时后,也会尝试重新连接服务器。但此代码没有实现网线是否连接检测的功能,可以参考一下正点原子LWIP中的socket client例程,进行优化。2023年新版手把手教你学lwIP — 正点原子资料下载中心 1.0.0 文档

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值