STM32F7xx —— LAN8720
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_PRIORITYHAL_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.hstatic 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 0u32_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;#elseu32_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是用msif(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是用msif(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;// 默认远程IPdev->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;// 本地IPdev->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和DMAreturn 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 Bufferbyteslefttocopy = 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寄存器// 随便写入一个值来将其唤醒,此处我们写0lan8720_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; // 获取接收到的以太网帧的数据bufferif(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 Bufferbuffer = (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 DMAlan8720_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"; // 初始化名称
#endifnetif->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();}
}