前言:
由于国名技术官方资料库中只提供了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 文档