STM32F767自带以太网模块,需要外接PHY芯片,完成以太网通信(MII/RMII接口)。LAN8720详细资料看手册。LWIP:1.4.1 FreeRTOS V8.2.3。
#define ETH_CHANNEL ETH
#define ETH_PREEMPT_PRIO ETHERNET_PRIORITY
#define ETH_CLK_ENABLE() __HAL_RCC_ETH_CLK_ENABLE()
#define ETH_IRQ ETH_IRQn
#define ETH_IRQ_FUNC ETH_IRQHandler
#define ETH_RESET_PORT GPIOH
#define ETH_RESET_PIN GPIO_PIN_11
#define ETH_RESET_CONFIG() GPIOConfig(ETH_RESET_PORT, ETH_RESET_PIN, GPIO_MODE_OUTPUT_PP, GPIO_NOPULL);
#define ETH_RESET_HIGH() HAL_GPIO_WritePin(ETH_RESET_PORT, ETH_RESET_PIN, GPIO_PIN_SET)
#define ETH_RESET_LOW() HAL_GPIO_WritePin(ETH_RESET_PORT, ETH_RESET_PIN, GPIO_PIN_RESET)
#define ETH_MDIO_PORT GPIOA
#define ETH_MDIO_PIN GPIO_PIN_2
#define ETH_MDIO_AF GPIO_AF11_ETH
#define ETH_MDIO_CONFIG() GPIOConfigExt(ETH_MDC_PORT, ETH_MDC_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, ETH_MDC_AF);
#define ETH_MDC_PORT GPIOC
#define ETH_MDC_PIN GPIO_PIN_1
#define ETH_MDC_AF GPIO_AF11_ETH
#define ETH_MDC_CONFIG() GPIOConfigExt(ETH_MDIO_PORT, ETH_MDIO_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, ETH_MDIO_AF);
#define ETH_RMII_REF_CLK_PORT GPIOA
#define ETH_RMII_REF_CLK_PIN GPIO_PIN_1
#define ETH_RMII_REF_CLK_AF GPIO_AF11_ETH
#define ETH_RMII_REF_CLK_CONFIG() GPIOConfigExt(ETH_RMII_REF_CLK_PORT, ETH_RMII_REF_CLK_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, ETH_RMII_REF_CLK_AF);
#define ETH_RMII_CRS_DV_PORT GPIOA
#define ETH_RMII_CRS_DV_PIN GPIO_PIN_7
#define ETH_RMII_CRS_DV_AF GPIO_AF11_ETH
#define ETH_RMII_CRS_DV_CONFIG() GPIOConfigExt(ETH_RMII_CRS_DV_PORT, ETH_RMII_CRS_DV_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, ETH_RMII_CRS_DV_AF);
#define ETH_RMII_RXD0_PORT GPIOC
#define ETH_RMII_RXD0_PIN GPIO_PIN_4
#define ETH_RMII_RXD0_AF GPIO_AF11_ETH
#define ETH_RMII_RXD0_CONFIG() GPIOConfigExt(ETH_RMII_RXD0_PORT, ETH_RMII_RXD0_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, ETH_RMII_RXD0_AF);
#define ETH_RMII_RXD1_PORT GPIOC
#define ETH_RMII_RXD1_PIN GPIO_PIN_5
#define ETH_RMII_RXD1_AF GPIO_AF11_ETH
#define ETH_RMII_RXD1_CONFIG() GPIOConfigExt(ETH_RMII_RXD1_PORT, ETH_RMII_RXD1_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, ETH_RMII_RXD1_AF);
#define ETH_RMII_TXEN_PORT GPIOB
#define ETH_RMII_TXEN_PIN GPIO_PIN_11
#define ETH_RMII_TXEN_AF GPIO_AF11_ETH
#define ETH_RMII_TXEN_CONFIG() GPIOConfigExt(ETH_RMII_TXEN_PORT, ETH_RMII_TXEN_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, ETH_RMII_TXEN_AF);
#define ETH_RMII_TXD0_PORT GPIOG
#define ETH_RMII_TXD0_PIN GPIO_PIN_13
#define ETH_RMII_TXD0_AF GPIO_AF11_ETH
#define ETH_RMII_TXD0_CONFIG() GPIOConfigExt(ETH_RMII_TXD0_PORT, ETH_RMII_TXD0_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, ETH_RMII_TXD0_AF);
#define ETH_RMII_TXD1_PORT GPIOG
#define ETH_RMII_TXD1_PIN GPIO_PIN_14
#define ETH_RMII_TXD1_AF GPIO_AF11_ETH
#define ETH_RMII_TXD1_CONFIG() GPIOConfigExt(ETH_RMII_TXD1_PORT, ETH_RMII_TXD1_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, ETH_RMII_TXD1_AF);
lan8720_dev_t lan8720_dev;
static void lan8720_var_init(void)
{
lan8720_dev.dma_rx = MemAlloc(SRAM_TYPE_DTCM, ETH_RXBUFNB * sizeof(ETH_DMADescTypeDef)); //申请内存
lan8720_dev.dma_tx = MemAlloc(SRAM_TYPE_DTCM, ETH_TXBUFNB * sizeof(ETH_DMADescTypeDef)); //申请内存
lan8720_dev.rx_buffer = MemAlloc(SRAM_TYPE_DTCM, ETH_RX_BUF_SIZE * ETH_RXBUFNB); //申请内存
lan8720_dev.tx_buffer = MemAlloc(SRAM_TYPE_DTCM, ETH_TX_BUF_SIZE * ETH_TXBUFNB); //申请内存
}
static void lan8720_gpio_init(void)
{
ETH_CLK_ENABLE();
ETH_RESET_CONFIG();
ETH_MDIO_CONFIG();
ETH_MDC_CONFIG();
ETH_RMII_REF_CLK_CONFIG();
ETH_RMII_CRS_DV_CONFIG();
ETH_RMII_RXD0_CONFIG();
ETH_RMII_RXD1_CONFIG();
ETH_RMII_TXEN_CONFIG();
ETH_RMII_TXD0_CONFIG();
ETH_RMII_TXD1_CONFIG();
}
static void lan8720_mode_init(void)
{
uint8_t mac[6] = {0};
ETH_RESET_CONFIG();
ETH_RESET_HIGH();
delay_ms(100);
ETH_RESET_LOW();
delay_ms(100);
mac[0] = lwip_dev.mac[0];
mac[1] = lwip_dev.mac[1];
mac[2] = lwip_dev.mac[2];
mac[3] = lwip_dev.mac[3];
mac[4] = lwip_dev.mac[4];
mac[5] = lwip_dev.mac[5];
lan8720_dev.handle.Instance = ETH;
lan8720_dev.handle.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE; // 使能自协商模式
lan8720_dev.handle.Init.Speed = ETH_SPEED_100M; // 速度100M,如果开启了自协商模式,此配置就无效
lan8720_dev.handle.Init.DuplexMode = ETH_MODE_FULLDUPLEX; // 全双工模式,如果开启了自协商模式,此配置就无效
lan8720_dev.handle.Init.PhyAddress = LAN8720_PHY_ADDRESS; // LAN8720地址
lan8720_dev.handle.Init.MACAddr = mac; // MAC地址
lan8720_dev.handle.Init.RxMode = ETH_RXINTERRUPT_MODE; // 中断接收模式
lan8720_dev.handle.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; // 硬件帧校验
lan8720_dev.handle.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; // RMII接口
HAL_ETH_Init(&lan8720_dev.handle);
}
static void lan8720_nvic_init(void)
{
HAL_NVIC_SetPriority(ETH_IRQ, 3, 0); // 优先级必须小于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
HAL_NVIC_EnableIRQ(ETH_IRQ);
}
uint32_t LAN8720ReadPHY(uint16_t reg)
{
uint32_t regval;
HAL_ETH_ReadPHYRegister(&lan8720_dev.handle, reg, ®val);
return regval;
}
void LAN8720WritePHY(uint16_t reg, uint16_t value)
{
uint32_t temp = value;
HAL_ETH_ReadPHYRegister(&lan8720_dev.handle, reg, &temp);
}
static uint32_t lan8720_rx_size(ETH_DMADescTypeDef *dma_rx)
{
uint32_t length = 0;
if(((dma_rx->Status & ETH_DMARXDESC_OWN) == (uint32_t)RESET) &&
((dma_rx->Status & ETH_DMARXDESC_ES) == (uint32_t)RESET) &&
((dma_rx->Status & ETH_DMARXDESC_LS) != (uint32_t)RESET))
{
length = ((dma_rx->Status & ETH_DMARXDESC_FL) >> ETH_DMARXDESC_FRAME_LENGTHSHIFT);
}
return length;
}
void LAN8720Init(void)
{
lan8720_var_init();
lan8720_gpio_init();
lan8720_mode_init();
lan8720_nvic_init();
}
void ETH_IRQ_FUNC(void)
{
while(lan8720_rx_size(lan8720_dev.handle.RxDesc))
{
LWIPCommProcess(); // 处理以太网数据,即将数据提交给LWIP
}
//清除中断标志位
__HAL_ETH_DMA_CLEAR_IT(&lan8720_dev.handle, ETH_DMA_IT_R);
__HAL_ETH_DMA_CLEAR_IT(&lan8720_dev.handle, ETH_DMA_IT_NIS);
}
sys_arch文件:
#ifndef _ARCH_SYS_ARCH_H_
#define _ARCH_SYS_ARCH_H_
#include "arch/cc.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "semphr.h"
#define MAX_QUEUE_ENTRIES 20 // 每个消息邮箱的大小
// LWIP消息邮箱结构体
typedef struct
{
QueueHandle_t xQueue;
} lwip_mbox;
typedef SemaphoreHandle_t sys_sem_t; // LWIP使用的信号量
typedef lwip_mbox sys_mbox_t; // LWIP使用的消息邮箱,其实就是UCOS中的消息队列
typedef unsigned char sys_thread_t; // 线程ID,也就是任务优先级
#endif /* _ARCH_SYS_ARCH_H_ */
// 参照lwip_sys.h
static const uint32_t console_null;
// 创建一个消息邮箱
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
if(size > MAX_QUEUE_ENTRIES)
{
size = MAX_QUEUE_ENTRIES;
}
mbox->xQueue = xQueueCreate(size, sizeof(void *));
if(mbox->xQueue != NULL)
{
return ERR_OK;
}
else
{
return ERR_MEM;
}
}
// 释放并删除一个消息邮箱
void sys_mbox_free(sys_mbox_t *mbox)
{
vQueueDelete(mbox->xQueue);
mbox->xQueue = NULL;
}
// 向消息邮箱中发送一条消息(必须发送成功)
void sys_mbox_post(sys_mbox_t *mbox, void *msg)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if(msg == NULL)
{
msg = (void*)&console_null; //当msg为空时 msg等于pvNullPointer指向的值
}
if((SCB_ICSR_REG & 0xFF) == 0) //线程执行
{
while(xQueueSendToBack(mbox->xQueue, &msg, portMAX_DELAY) != pdPASS);//portMAX_DELAY,死等直到发送成功
}
else
{
while(xQueueSendToBackFromISR(mbox->xQueue, &msg, &xHigherPriorityTaskWoken) != pdPASS);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
// 尝试向一个消息邮箱发送消息
// 此函数相对于sys_mbox_post函数只发送一次消息,发送失败后不会尝试第二次发送
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if(msg == NULL)
{
msg = (void*)&console_null; //当消息为空,则用常量NullMessage的地址替换
}
if((SCB_ICSR_REG & 0xFF) == 0)
{
if(xQueueSendToBack(mbox->xQueue, &msg, 0) != pdPASS)
{
return ERR_MEM;
}
}
else
{
if(xQueueSendToBackFromISR(mbox->xQueue, &msg, &xHigherPriorityTaskWoken) != pdPASS)
{
return ERR_MEM;
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
return ERR_OK;
}
// 等待邮箱中的消息
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
#if 0
u32_t rtos_timeout;
BaseType_t temp;
if(timeout == 0)
{
rtos_timeout = 2;
}
else
{
rtos_timeout = timeout;
temp = xQueueReceive(mbox->xQueue, msg, rtos_timeout);
}
return rtos_timeout;
#else
u32_t rtos_timeout, timeout_new;
BaseType_t temp;
temp = xQueueReceive(mbox->xQueue, msg, 0);
if((temp == pdPASS) && (*msg != NULL))
{
if(*msg == (void*)&console_null)
{
*msg = NULL;
}
return 0;
}
if(timeout != 0)
{
rtos_timeout = (timeout * configTICK_RATE_HZ) / 1000; // 转换为节拍数,因为freertos延时使用的是节拍数,而LWIP是用ms
if(rtos_timeout < 1)
{
rtos_timeout = 1; // 至少1个节拍
}
else if(rtos_timeout >= portMAX_DELAY)
{
rtos_timeout = portMAX_DELAY - 1;
}
}
else
{
rtos_timeout = 0;
}
timeout = HAL_GetTick(); //获取系统时间
if(rtos_timeout != 0)
{
temp = xQueueReceive(mbox->xQueue, msg, rtos_timeout); // 请求消息队列,等待时限为rtos_timeout
}
else
{
temp = xQueueReceive(mbox->xQueue, msg, portMAX_DELAY); // 为0则无限等
}
if(temp == errQUEUE_EMPTY)
{
timeout = SYS_ARCH_TIMEOUT; //请求超时
*msg = NULL;
}
else
{
if(*msg != NULL)
{
if(*msg == (void*)&console_null)
{
*msg = NULL;
}
}
timeout_new = HAL_GetTick();
if (timeout_new > timeout)
{
timeout_new = timeout_new - timeout; //算出请求消息或使用的时间
}
else
{
timeout_new = 0xffffffff - timeout + timeout_new;
}
timeout = timeout_new * 1000 / configTICK_RATE_HZ + 1;
}
return timeout;
#endif
}
// 尝试获取消息
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
BaseType_t temp;
temp = xQueueReceive(mbox->xQueue, msg, 0);
if((temp == pdPASS) && (*msg != NULL))
{
if(*msg == (void*)&console_null)
{
*msg = NULL;
}
return 0;
}
else
{
return SYS_MBOX_EMPTY;
}
}
// 检查一个消息邮箱是否有效
// 返回值:1,有效. 0,无效
int sys_mbox_valid(sys_mbox_t *mbox)
{
if(mbox->xQueue != NULL)
{
return 1;
}
return 0;
}
// 设置一个消息邮箱为无效
void sys_mbox_set_invalid(sys_mbox_t *mbox)
{
mbox->xQueue = NULL;
}
// 创建一个信号量
err_t sys_sem_new(sys_sem_t* sem, uint8_t count)
{
*sem = xSemaphoreCreateCounting(0xFF, count);
if(*sem == NULL)
{
return ERR_MEM;
}
return ERR_OK;
}
// 等待一个信号量
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
u32_t rtos_timeout, timeout_new;
BaseType_t temp;
if(xSemaphoreTake(*sem, 0) == pdPASS)
{
return 0;
}
if( timeout != 0)
{
rtos_timeout = (timeout * configTICK_RATE_HZ) / 1000; // 转换为节拍数,因为UCOS延时使用的是节拍数,而LWIP是用ms
if(rtos_timeout < 1)
{
rtos_timeout = 1;
}
}
else
{
rtos_timeout = 0;
}
timeout = HAL_GetTick();
if(rtos_timeout != 0)
{
temp = xSemaphoreTake(*sem, rtos_timeout);
}
else
{
temp = xSemaphoreTake(*sem, portMAX_DELAY);
}
if(temp != pdPASS)
{
timeout = SYS_ARCH_TIMEOUT; // 请求超时
}
else
{
timeout_new = HAL_GetTick();
if (timeout_new >= timeout)
{
timeout_new = timeout_new - timeout;
}
else
{
timeout_new = 0xffffffff - timeout + timeout_new;
}
timeout = (timeout_new * 1000 / configTICK_RATE_HZ + 1); // 算出请求消息或使用的时间(ms)
}
return timeout;
}
// 发送一个信号量
void sys_sem_signal(sys_sem_t *sem)
{
while(xSemaphoreGive(*sem) != pdTRUE);
}
// 释放并删除一个信号量
void sys_sem_free(sys_sem_t *sem)
{
vSemaphoreDelete(*sem);
*sem = NULL;
}
// 查询一个信号量的状态,无效或有效
int sys_sem_valid(sys_sem_t *sem)
{
if(*sem != NULL)
{
return 1;
}
else
{
return 0;
}
}
// 设置一个信号量无效
void sys_sem_set_invalid(sys_sem_t *sem)
{
*sem = NULL;
}
// arch初始化
void sys_init(void)
{
// 不做任何事情
}
TaskHandle_t os_lwip_handle;
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio)
{
xTaskCreate((TaskFunction_t)thread,
(const char* )name,
(uint16_t )stacksize,
(void* )NULL,
(UBaseType_t )prio,
(TaskHandle_t*)&os_lwip_handle);//创建TCP IP内核任务
return 0;
}
// lwip延时函数
void sys_msleep(u32_t ms)
{
delay_ms(ms);
}
// 获取系统时间,LWIP1.4.1增加的函数
// 返回值:当前系统时间(单位:毫秒)
u32_t sys_now(void)
{
return (HAL_GetTick() * 1000 / configTICK_RATE_HZ + 1); // 将节拍数转换为LWIP的时间MS
}
// 用在cc.h的SYS_ARCH_PROTECT(lev)
uint32_t SysCriticalEnter(void)
{
if(SCB_ICSR_REG & 0xFF) //在中断里
{
return taskENTER_CRITICAL_FROM_ISR();
}
else // 在任务
{
taskENTER_CRITICAL();
}
return 0;
}
// 用在cc.YS_ARCH_UNPROTECT(lev)
void SysCriticalExit(uint32_t lev)
{
if(SCB_ICSR_REG & 0xFF) // 在中断里
{
taskEXIT_CRITICAL_FROM_ISR(lev);
}
else // 在任务
{
taskEXIT_CRITICAL();
}
}
cpu.h:#define BYTE_ORDER LITTLE_ENDIAN // 小端模式
cc.h:
#include "cpu.h"
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
// LWIP需要的类型重命名
typedef unsigned char u8_t; /* Unsigned 8 bit quantity */
typedef signed char s8_t; /* Signed 8 bit quantity */
typedef unsigned short u16_t; /* Unsigned 16 bit quantity */
typedef signed short s16_t; /* Signed 16 bit quantity */
typedef unsigned long u32_t; /* Unsigned 32 bit quantity */
typedef signed long s32_t; /* Signed 32 bit quantity */
typedef u32_t mem_ptr_t; /* Unsigned 32 bit quantity */
typedef int sys_prot_t;
// LWIP需要的临界段
#define OS_CRITICAL_METHOD
#ifdef OS_CRITICAL_METHOD
#define SCB_ICSR_REG (*((volatile uint32_t * ) 0xe000ed04))
extern uint32_t SysCriticalEnter(void);
extern void SysCriticalExit(uint32_t lev);
#define SYS_ARCH_DECL_PROTECT(lev) u32_t lev
#define SYS_ARCH_PROTECT(lev) lev = SysCriticalEnter()
#define SYS_ARCH_UNPROTECT(lev) SysCriticalExit(lev)
#endif
// 对齐方案
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"
/*--------------macros--------------------------------------------------------*/
#ifndef LWIP_PLATFORM_ASSERT
#define LWIP_PLATFORM_ASSERT(x) \
do \
{ printf("Assertion \"%s\" failed at line %d in %s\r\n", x, __LINE__, __FILE__); \
} while(0)
#endif
#ifndef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
#endif
lwipopts.h
#ifndef _LWIPOPTS_H_
#define _LWIPOPTS_H_
// 与FreeRTOS配置类似(FreeRTOSConfig.h)
// 线程优先级
#ifndef TCPIP_THREAD_PRIO
#define TCPIP_THREAD_PRIO (configMAX_PRIORITIES - 1) // 定义内核任务的优先级为最高
#endif
#undef DEFAULT_THREAD_PRIO
#define DEFAULT_THREAD_PRIO 8
#define SYS_LIGHTWEIGHT_PROT 1 // 为1时使用实时操作系统的轻量级保护,保护关键代码不被中断打断
#define NO_SYS 0 // 使用UCOS操作系统
#define MEM_ALIGNMENT 4 // 使用4字节对齐模式
#define MEM_SIZE 16000 // 内存堆heap大小
#define MEMP_NUM_PBUF 20 // MEMP_NUM_PBUF:memp结构的pbuf数量,如果应用从ROM或者静态存储区发送大量数据时,这个值应该设置大一点
#define MEMP_NUM_UDP_PCB 6 // MEMP_NUM_UDP_PCB:UDP协议控制块(PCB)数量.每个活动的UDP"连接"需要一个PCB.
#define MEMP_NUM_TCP_PCB 10 // MEMP_NUM_TCP_PCB:同时建立激活的TCP数量
#define MEMP_NUM_TCP_PCB_LISTEN 6 // MEMP_NUM_TCP_PCB_LISTEN:能够监听的TCP连接数量
#define MEMP_NUM_TCP_SEG 15 // MEMP_NUM_TCP_SEG:最多同时在队列中的TCP段数量
#define MEMP_NUM_SYS_TIMEOUT 8 // MEMP_NUM_SYS_TIMEOUT:能够同时激活的timeout个数
// pbuf内存池
#define PBUF_POOL_SIZE 20 // PBUF_POOL_SIZE:pbuf内存池个数
#define PBUF_POOL_BUFSIZE 512 // PBUF_POOL_BUFSIZE:每个pbuf内存池大小
#define LWIP_TCP 1 // 使用TCP
#define TCP_TTL 255 // 生存时间
#undef TCP_QUEUE_OOSEQ
#define TCP_QUEUE_OOSEQ 0 // 当TCP的数据段超出队列时的控制位,当设备的内存过小的时候此项应为0
#undef TCPIP_MBOX_SIZE
#define TCPIP_MBOX_SIZE MAX_QUEUE_ENTRIES // tcpip创建主线程时的消息邮箱大小
#undef DEFAULT_TCP_RECVMBOX_SIZE
#define DEFAULT_TCP_RECVMBOX_SIZE MAX_QUEUE_ENTRIES
#undef DEFAULT_ACCEPTMBOX_SIZE
#define DEFAULT_ACCEPTMBOX_SIZE MAX_QUEUE_ENTRIES
#define TCP_MSS (1500 - 40) // 最大TCP分段,TCP_MSS = (MTU - IP报头大小 - TCP报头大小
#define TCP_SND_BUF (4*TCP_MSS) // TCP发送缓冲区大小(bytes).
#define TCP_SND_QUEUELEN (2* TCP_SND_BUF/TCP_MSS) //TCP_SND_QUEUELEN: TCP发送缓冲区大小(pbuf).这个值最小为(2 * TCP_SND_BUF/TCP_MSS)
#define TCP_WND (2*TCP_MSS) // TCP发送窗口
#define LWIP_ICMP 1 // 使用ICMP协议
#define LWIP_DHCP 0 // 使用DHCP
#define LWIP_UDP 1 // 使用UDP服务
#define UDP_TTL 255 // UDP数据包生存时间
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1
// 帧校验和选项,STM32F7xx允许通过硬件识别和计算IP,UDP和ICMP的帧校验和
#define CHECKSUM_BY_HARDWARE // 定义CHECKSUM_BY_HARDWARE,使用硬件帧校验
#ifdef CHECKSUM_BY_HARDWARE
//CHECKSUM_GEN_IP==0: 硬件生成IP数据包的帧校验和
#define CHECKSUM_GEN_IP 0
//CHECKSUM_GEN_UDP==0: 硬件生成UDP数据包的帧校验和
#define CHECKSUM_GEN_UDP 0
//CHECKSUM_GEN_TCP==0: 硬件生成TCP数据包的帧校验和
#define CHECKSUM_GEN_TCP 0
//CHECKSUM_CHECK_IP==0: 硬件检查输入的IP数据包帧校验和
#define CHECKSUM_CHECK_IP 0
//CHECKSUM_CHECK_UDP==0: 硬件检查输入的UDP数据包帧校验和
#define CHECKSUM_CHECK_UDP 0
//CHECKSUM_CHECK_TCP==0: 硬件检查输入的TCP数据包帧校验和
#define CHECKSUM_CHECK_TCP 0
//CHECKSUM_CHECK_ICMP==1:硬件检查输入的ICMP数据包帧校验和
#define CHECKSUM_GEN_ICMP 0
#else
//CHECKSUM_GEN_IP==1: 软件生成IP数据包帧校验和
#define CHECKSUM_GEN_IP 1
// CHECKSUM_GEN_UDP==1: 软件生成UDOP数据包帧校验和
#define CHECKSUM_GEN_UDP 1
//CHECKSUM_GEN_TCP==1: 软件生成TCP数据包帧校验和
#define CHECKSUM_GEN_TCP 1
// CHECKSUM_CHECK_IP==1: 软件检查输入的IP数据包帧校验和
#define CHECKSUM_CHECK_IP 1
// CHECKSUM_CHECK_UDP==1: 软件检查输入的UDP数据包帧校验和
#define CHECKSUM_CHECK_UDP 1
//CHECKSUM_CHECK_TCP==1: 软件检查输入的TCP数据包帧校验和
#define CHECKSUM_CHECK_TCP 1
//CHECKSUM_CHECK_ICMP==1:软件检查输入的ICMP数据包帧校验和
#define CHECKSUM_GEN_ICMP 1
#endif
#define LWIP_NETCONN 1 // LWIP_NETCONN==1:使能NETCON函数(要求使用api_lib.c)
#define LWIP_SOCKET 1 // LWIP_SOCKET==1:使能Socket API(要求使用sockets.c)
#define LWIP_COMPAT_MUTEX 1
#define LWIP_SO_RCVTIMEO 1 // 通过定义LWIP_SO_RCVTIMEO使能netconn结构体中recv_timeout,使用recv_timeout可以避免阻塞线程
// 有关系统的选项
#define TCPIP_THREAD_STACKSIZE 1024 // 内核任务堆栈大小
#define DEFAULT_UDP_RECVMBOX_SIZE 2000
#define DEFAULT_THREAD_STACKSIZE 512
//LWIP调试选项
#define LWIP_DEBUG 0 // 关闭DEBUG选项
#define ICMP_DEBUG LWIP_DBG_OFF // 开启/关闭ICMPdebug
#endif /* __LWIPOPTS_H__ */
主函数需调用的初始化接口:
lwip_dev_t lwip_dev; // lwip控制结构体
static struct netif lwip_netif; // 网络接口
extern uint32_t memp_get_memorysize(void); // 在memp.c里面定义
extern uint8_t *memp_memory; // 在memp.c里面定义.
extern uint8_t *ram_heap; // 在mem.c里面定义.
// 释放内存
static void lwip_comm_mem_free(void)
{
MemFree(SRAM_TYPE_IN, memp_memory);
MemFree(SRAM_TYPE_IN, ram_heap);
}
// 分配内存
static uint8_t lwip_comm_mem_malloc(void)
{
uint32_t mempsize;
uint32_t ramheapsize;
mempsize = memp_get_memorysize(); // 得到memp_memory数组大小
memp_memory = MemAlloc(SRAM_TYPE_IN, mempsize); // 为memp_memory申请内存
ramheapsize = LWIP_MEM_ALIGN_SIZE(MEM_SIZE) + 2 * LWIP_MEM_ALIGN_SIZE(4 * 3) + MEM_ALIGNMENT; // 得到ram heap大小
ram_heap = MemAlloc(SRAM_TYPE_IN, ramheapsize); // 为ram_heap申请内存
if(!memp_memory || !ram_heap)
{
lwip_comm_mem_free();
return 1;
}
return 0;
}
// 网络默认配置
static void lwip_comm_default_ip_set(lwip_dev_t *dev)
{
uint32_t sn0;
SocIDGet(&sn0, CONFIG_SYSTEM_HARDWARE_TYPE);
// MAC地址设置(高三字节固定为:2.0.0,低三字节用STM32唯一ID)
dev->mac[0] = 2;
dev->mac[1] = 0;
dev->mac[2] = 0;
dev->mac[3] = (sn0 >> 16) & 0xFF;
dev->mac[4] = (sn0 >> 8) & 0xFF;
dev->mac[5] = sn0 & 0xFF;
// 默认远程IP
dev->remoteip[0] = CONFIG_ETHERNET_REMOTE_IP0;
dev->remoteip[1] = CONFIG_ETHERNET_REMOTE_IP1;
dev->remoteip[2] = CONFIG_ETHERNET_REMOTE_IP2;
dev->remoteip[3] = CONFIG_ETHERNET_REMOTE_IP3;
// 本地IP
dev->ip[0] = CONFIG_ETHERNET_LOCAL_IP0;
dev->ip[1] = CONFIG_ETHERNET_LOCAL_IP1;
dev->ip[2] = CONFIG_ETHERNET_LOCAL_IP2;
dev->ip[3] = CONFIG_ETHERNET_LOCAL_IP3;
// 子网掩码
dev->netmask[0] = CONFIG_ETHERNET_NETMASK0;
dev->netmask[1] = CONFIG_ETHERNET_NETMASK1;
dev->netmask[2] = CONFIG_ETHERNET_NETMASK2;
dev->netmask[3] = CONFIG_ETHERNET_NETMASK3;
// 网关
dev->gateway[0] = CONFIG_ETHERNET_GATEWAY0;
dev->gateway[1] = CONFIG_ETHERNET_GATEWAY1;
dev->gateway[2] = CONFIG_ETHERNET_GATEWAY2;
dev->gateway[3] = CONFIG_ETHERNET_GATEWAY3;
dev->dhcpstatus = 0; // 没有DHCP
}
// LWIP初始化(LWIP启动的时候使用)
uint8_t LWIPCommInit(void)
{
struct netif *netif_init; // 调用netif_add()函数时的返回值,用于判断网络初始化是否成功
struct ip_addr ipaddr, netmask, gw; //ip地址 子网掩码 网关
if(lwip_comm_mem_malloc())
{
return LWIP_COMM_STATUS_RAM_ERROR; //内存申请失败
}
lwip_comm_default_ip_set(&lwip_dev); //设置默认IP等信息
LAN8720Init(); // 硬件网口初始化
tcpip_init(NULL, NULL); // 初始化tcp ip内核,该函数里面会创建tcpip_thread内核任务
IP4_ADDR(&ipaddr, lwip_dev.ip[0], lwip_dev.ip[1], lwip_dev.ip[2], lwip_dev.ip[3]);
IP4_ADDR(&netmask, lwip_dev.netmask[0], lwip_dev.netmask[1], lwip_dev.netmask[2], lwip_dev.netmask[3]);
IP4_ADDR(&gw, lwip_dev.gateway[0], lwip_dev.gateway[1], lwip_dev.gateway[2], lwip_dev.gateway[3]);
printf("网卡en的MAC地址%d.%d.%d.%d.%d.%d\r\n", lwip_dev.mac[0], lwip_dev.mac[1], lwip_dev.mac[2], lwip_dev.mac[3], lwip_dev.mac[4], lwip_dev.mac[5]);
printf("静态IP地址.....%d.%d.%d.%d\r\n", lwip_dev.ip[0], lwip_dev.ip[1], lwip_dev.ip[2], lwip_dev.ip[3]);
printf("子网掩码.......%d.%d.%d.%d\r\n", lwip_dev.netmask[0], lwip_dev.netmask[1], lwip_dev.netmask[2], lwip_dev.netmask[3]);
printf("默认网关.......%d.%d.%d.%d\r\n", lwip_dev.gateway[0], lwip_dev.gateway[1], lwip_dev.gateway[2], lwip_dev.gateway[3]);
netif_init = netif_add(&lwip_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input); // 向网卡列表中添加一个网口
if(netif_init == NULL)
{
return LWIP_COMM_STATUS_NET_ADD_FAIL; // 网卡添加失败
}
else // 网口添加成功后,设置netif为默认值,并且打开netif网口
{
netif_set_default(&lwip_netif); // 设置netif为默认网口
netif_set_up(&lwip_netif); // 打开netif网口
}
return LWIP_COMM_STATUS_OK; // OK.
}
// 用于以太网中断调用
void LWIPCommProcess(void)
{
ethernetif_input(&lwip_netif);
}
ethernetif.c:
// 由ethernetif_init()调用用于初始化硬件
// netif:网卡结构体指针
// 返回值:ERR_OK,正常
// 其他,失败
static err_t low_level_init(struct netif *netif)
{
netif->hwaddr_len = ETHARP_HWADDR_LEN; // 设置MAC地址长度,为6个字节
// 初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复
netif->hwaddr[0] = lwip_dev.mac[0];
netif->hwaddr[1] = lwip_dev.mac[1];
netif->hwaddr[2] = lwip_dev.mac[2];
netif->hwaddr[3] = lwip_dev.mac[3];
netif->hwaddr[4] = lwip_dev.mac[4];
netif->hwaddr[5] = lwip_dev.mac[5];
netif->mtu = 1500; //最大允许传输单元,允许该网卡广播和ARP功能
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
HAL_ETH_DMATxDescListInit(&lan8720_dev.handle, lan8720_dev.dma_tx, lan8720_dev.tx_buffer, ETH_TXBUFNB); // 初始化发送描述符
HAL_ETH_DMARxDescListInit(&lan8720_dev.handle, lan8720_dev.dma_rx, lan8720_dev.rx_buffer, ETH_RXBUFNB); // 初始化接收描述符
HAL_ETH_Start(&lan8720_dev.handle); // 开启MAC和DMA
return ERR_OK;
}
// 用于发送数据包的最底层函数(lwip通过netif->linkoutput指向该函数)
// netif:网卡结构体指针
// p:pbuf数据结构体指针
// 返回值:ERR_OK,发送正常
// ERR_MEM,发送失败
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
err_t errval;
struct pbuf *q;
uint8_t *buffer = (uint8_t *)(lan8720_dev.handle.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = 0;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
DmaTxDesc = lan8720_dev.handle.TxDesc;
bufferoffset = 0;
// 从pbuf中拷贝要发送的数据
for(q = p; q != NULL; q = q->next)
{
// 判断此发送描述符是否有效,即判断此发送描述符是否归以太网DMA所有
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error; // 发送描述符无效,不可用
}
byteslefttocopy = q->len; // 要发送的数据长度
payloadoffset = 0;
// 将pbuf中要发送的数据写入到以太网发送描述符中,有时候我们要发送的数据可能大于一个以太网
// 描述符的Tx Buffer,因此我们需要分多次将数据拷贝到多个发送描述符中
while((byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
{
// 将数据拷贝到以太网发送描述符的Tx Buffer中
memcpy((uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset));
// DmaTxDsc指向下一个发送描述符
DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
// 检查新的发送描述符是否有效
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error; // 发送描述符无效,不可用
}
buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr); // 更新buffer地址,指向新的发送描述符的Tx Buffer
byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
// 拷贝剩余的数据
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
bufferoffset = bufferoffset + byteslefttocopy;
framelength = framelength + byteslefttocopy;
}
// 当所有要发送的数据都放进发送描述符的Tx Buffer以后就可发送此帧了
HAL_ETH_TransmitFrame(&lan8720_dev.handle, framelength);
errval = ERR_OK;
error:
// 发送缓冲区发生下溢,一旦发送缓冲区发生下溢TxDMA会进入挂起状态
if((lan8720_dev.handle.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
{
// 清除下溢标志
lan8720_dev.handle.Instance->DMASR = ETH_DMASR_TUS;
// 当发送帧中出现下溢错误的时候TxDMA会挂起,这时候需要向DMATPDR寄存器
// 随便写入一个值来将其唤醒,此处我们写0
lan8720_dev.handle.Instance->DMATPDR = 0;
}
return errval;
}
// 用于接收数据包的最底层函数
// neitif:网卡结构体指针
// 返回值:pbuf数据结构体指针
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p = NULL;
struct pbuf *q;
uint16_t len;
uint8_t *buffer;
__IO ETH_DMADescTypeDef *dmarxdesc;
uint32_t bufferoffset = 0;
uint32_t payloadoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t i = 0;
if(HAL_ETH_GetReceivedFrame(&lan8720_dev.handle) != HAL_OK) // 判断是否接收到数据
{
return NULL;
}
len = lan8720_dev.handle.RxFrameInfos.length; // 获取接收到的以太网帧长度
buffer = (uint8_t *)lan8720_dev.handle.RxFrameInfos.buffer; // 获取接收到的以太网帧的数据buffer
if(len > 0)
{
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); // 申请pbuf
}
if(p != NULL) // pbuf申请成功
{
dmarxdesc = lan8720_dev.handle.RxFrameInfos.FSRxDesc; // 获取接收描述符链表中的第一个描述符
bufferoffset = 0;
for(q = p; q != NULL; q = q->next)
{
byteslefttocopy = q->len;
payloadoffset = 0;
// 将接收描述符中Rx Buffer的数据拷贝到pbuf中
while((byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
{
// 将数据拷贝到pbuf中
memcpy((uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));
// dmarxdesc向下一个接收描述符
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
// 更新buffer地址,指向新的接收描述符的Rx Buffer
buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
// 拷贝剩余的数据
memcpy((uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);
bufferoffset = bufferoffset + byteslefttocopy;
}
}
// 释放DMA描述符
dmarxdesc = lan8720_dev.handle.RxFrameInfos.FSRxDesc;
for(i = 0; i < lan8720_dev.handle.RxFrameInfos.SegCount; i++)
{
dmarxdesc->Status |= ETH_DMARXDESC_OWN; //标记描述符归DMA所有
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
}
lan8720_dev.handle.RxFrameInfos.SegCount = 0; //清除段计数器
if((lan8720_dev.handle.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET) //接收缓冲区不可用
{
// 清除接收缓冲区不可用标志
lan8720_dev.handle.Instance->DMASR = ETH_DMASR_RBUS;
// 当接收缓冲区不可用的时候RxDMA会进去挂起状态,通过向DMARPDR写入任意一个值来唤醒Rx DMA
lan8720_dev.handle.Instance->DMARPDR = 0;
}
return p;
}
// 网卡接收数据(lwip直接调用)
// netif:网卡结构体指针
// 返回值:ERR_OK,发送正常
// ERR_MEM,发送失败
err_t ethernetif_input(struct netif *netif)
{
err_t err;
struct pbuf *p;
p = low_level_input(netif); //调用low_level_input函数接收数据
if(p == NULL)
{
return ERR_MEM;
}
err = netif->input(p, netif); //调用netif结构体中的input字段(一个函数)来处理数据包
if(err != ERR_OK)
{
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
pbuf_free(p);
p = NULL;
}
return err;
}
// 使用low_level_init()函数来初始化网络
// netif:网卡结构体指针
// 返回值:ERR_OK,正常
// 其他,失败
err_t ethernetif_init(struct netif *netif)
{
LWIP_ASSERT("netif!=NULL", (netif != NULL));
#if LWIP_NETIF_HOSTNAME // LWIP_NETIF_HOSTNAME
netif->hostname = "lwip"; // 初始化名称
#endif
netif->name[0] = IFNAME0; // 初始化变量netif的name字段
netif->name[1] = IFNAME1; // 在文件外定义这里不用关心具体值
netif->output = etharp_output; // IP层发送数据包函数
netif->linkoutput = low_level_output; // ARP模块发送数据包函数
low_level_init(netif); // 底层硬件初始化函数
return ERR_OK;
}
TCP客户端实现:
#ifndef _LWIP_TCP_CLIENT_H_
#define _LWIP_TCP_CLIENT_H_
void TCPClientProcess(void);
void TCPClientSend(uint8_t *data, uint16_t length);
#endif /* _LWIP_TCP_CLIENT_H_ */
TaskHandle_t os_tcp_handle;
#define TCP_CLIENT_RX_BUFSIZE 1500 // 接收缓冲区长度
typedef struct
{
struct netconn *sockfd; // TCP CLIENT网络连接结构体
err_t connect_err;
err_t recv_err;
err_t send_err;
// 服务器IP和端口
ip_addr_t server_ipaddr;
uint16_t server_port;
// 客户端IP和端口
ip_addr_t loca_ipaddr;
uint16_t loca_port;
uint8_t state; // 网络连接的状态
uint8_t recv_buf[TCP_CLIENT_RX_BUFSIZE];
uint16_t recv_len;
} tcp_client_t;
static tcp_client_t tcp_client;
static void tcp_client_close(void)
{
netconn_close(tcp_client.sockfd);
netconn_delete(tcp_client.sockfd);
printf("服务器 %d.%d.%d.%d断开连接 \r\n", lwip_dev.remoteip[0], lwip_dev.remoteip[1], lwip_dev.remoteip[2], lwip_dev.remoteip[3]);
}
static void tcp_client_recv(void)
{
struct netbuf *recvbuf;
struct pbuf *q;
while(1)
{
tcp_client.recv_err = netconn_recv(tcp_client.sockfd, &recvbuf);
if(tcp_client.recv_err == ERR_OK) //接收到数据
{
taskENTER_CRITICAL();
tcp_client.recv_len = 0;
for(q = recvbuf->p; q != NULL; q = q->next) // 遍历完整个pbuf链表
{
printf(" %d ... \r\n", q->len);
memcpy(tcp_client.recv_buf + tcp_client.recv_len, q->payload, q->len);
tcp_client.recv_len += q->len;
}
taskEXIT_CRITICAL();
LWIPParse(tcp_client.recv_buf, tcp_client.recv_len);
netbuf_delete(recvbuf);
}
else if(tcp_client.recv_err == ERR_CLSD) //关闭连接
{
tcp_client_close();
break;
}
// 网线断掉 删除连接
tcp_client.state = LAN8720ReadPHY(PHY_BSR);
if((tcp_client.state & 0x04) == 0)
{
printf("close\r\n");
tcp_client_close();
break;
}
}
}
static void task_tcp_client(void *arg)
{
tcp_client.server_port = CONFIG_ETHERNET_REMOTE_PORT;
IP4_ADDR(&tcp_client.server_ipaddr, lwip_dev.remoteip[0], lwip_dev.remoteip[1], lwip_dev.remoteip[2], lwip_dev.remoteip[3]);
while (1)
{
tcp_client.sockfd = netconn_new(NETCONN_TCP); // 创建一个TCP链接
tcp_client.connect_err = netconn_connect(tcp_client.sockfd, &tcp_client.server_ipaddr, tcp_client.server_port); //连接服务器
if(tcp_client.connect_err != ERR_OK)
{
netconn_delete(tcp_client.sockfd); // 删除tcp_clientconn连接
}
else
{
tcp_client.sockfd->recv_timeout = 5;
netconn_getaddr(tcp_client.sockfd, &tcp_client.loca_ipaddr, &tcp_client.loca_port, 1); // 获取本地IP主机IP地址和端口号
printf("连接上服务器: %d.%d.%d.%d, 本机端口:%d\r\n", lwip_dev.remoteip[0], lwip_dev.remoteip[1], lwip_dev.remoteip[2], lwip_dev.remoteip[3], tcp_client.loca_port);
tcp_client_recv();
}
}
}
void TCPClientProcess(void)
{
LWIPCommInit();
xTaskCreate((TaskFunction_t)task_tcp_client,
(const char* )"tcp_client_task",
(uint16_t )OS_TCP_STK_SIZE,
(void* )NULL,
(UBaseType_t )OS_PRIO_TCP,
(TaskHandle_t*)&os_tcp_handle);
}
void TCPClientSend(uint8_t *data, uint16_t length)
{
tcp_client.send_err = netconn_write(tcp_client.sockfd, data, length, NETCONN_COPY); //发送数据
if(tcp_client.send_err != ERR_OK)
{
printf("发送失败\r\n");
tcp_client_close();
}
}