小海师 发表于 2025-3-6 19:59

GD32F450之以太网(Lwip2.1.2+FreeRTOS+UDP+TCP)

本文简要介绍GD32单片机关于以太网的应用,参照官方应用手册,基于Lwip2.1.2版本对UDP及TCP通信进行测试。使用PHY芯片为LAN8720,实时操作系统FreeRTOS。实现自适应10M/100M以太网通信。

一、基础配置:
引进外设初始化,选择RMII模式,复用单片机引脚(相关引脚为固定脚,直接复用即可),首先使能总线时钟,选择接口模式(RMII),再复用引脚。
//IO总线时钟使能,此处将所有外设时钟使能

    rcu_periph_clock_enable(RCU_GPIOA);

    rcu_periph_clock_enable(RCU_GPIOB);

    rcu_periph_clock_enable(RCU_GPIOC);

    rcu_periph_clock_enable(RCU_GPIOD);

    rcu_periph_clock_enable(RCU_GPIOG);

    rcu_periph_clock_enable(RCU_GPIOH);

    rcu_periph_clock_enable(RCU_GPIOI);

    //使能系统时钟

    rcu_periph_clock_enable(RCU_SYSCFG);   

    //选择RMII模式

    syscfg_enet_phy_interface_config(SYSCFG_ENET_PHY_RMII);

    //PA1:REF_CLK 时钟引脚,外部PHY提供50M

    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1);

    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_1);



    //PA2: ETH_MDIO

    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_2);

    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_2);



    //PA7: ETH_RMII_CRS_DV

    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_7);

    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_7);

    //引脚复用

    gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_1);

    gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_2);

    gpio_af_set(GPIOA, GPIO_AF_11, GPIO_PIN_7);



    //PB11: ETH_RMII_TX_EN

    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);

    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_11);



    //PB12: ETH_RMII_TXD0

    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12);

    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_12);



    //PB13: ETH_RMII_TXD1

    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13);

    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_13);



    gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_11);

    gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_12);

    gpio_af_set(GPIOB, GPIO_AF_11, GPIO_PIN_13);



    //PC1: ETH_MDC

    gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1);

    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_1);



    //PC4: ETH_RMII_RXD0

    gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_4);

    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_4);



    //PC5: ETH_RMII_RXD1

    gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_5);

    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_5);



    gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_1);

    gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_4);

    gpio_af_set(GPIOC, GPIO_AF_11, GPIO_PIN_5);

2、以太网初始化:使能以太网相关时钟,初始化MAC、DMA相关控制(使用官方默认库函数即可)

ErrStatus reval_state = ERROR;

rcu_periph_clock_enable(RCU_ENET);//使能以太网总线时钟

rcu_periph_clock_enable(RCU_ENETTX);//使能发送时钟

rcu_periph_clock_enable(RCU_ENETRX);//使能接收时钟

enet_deinit();//复位总线时钟AHB

reval_state = enet_software_reset();//等待时钟复位完成

if(ERROR == reval_state){//复位失败

   while(1){

   }

}

//以太网设备初始化

#ifdef CHECKSUM_BY_HARDWARE//不定义

enet_init_status = enet_init(ENET_AUTO_NEGOTIATION, ENET_AUTOCHECKSUM_DROP_FAILFRAMES, ENET_BROADCAST_FRAMES_PASS);

#else//PHY自动协商、禁用IP帧校验功能、地址过滤器通过所有接收到的广播帧ENET_100M_FULLDUPLEX/ENET_AUTO_NEGOTIATION

enet_init(ENET_AUTO_NEGOTIATION, ENET_NO_AUTOCHECKSUM, ENET_BROADCAST_FRAMES_PASS);

#endif /* CHECKSUM_BY_HARDWARE */

    /*注:当前使用的LAN8720PHY芯片只能使用自动协商模式,手动模式无法通信*/

3、中断配置,结合实时操作系统,在中断中接收数据:

//以太网正常中断启用

enet_interrupt_enable(ENET_DMA_INT_NIE);

//接收中断使能

enet_interrupt_enable(ENET_DMA_INT_RIE);

//使能以太网中断请求

    nvic_irq_enable(ENET_IRQn, 5, 0);//配置中断优先级
//中断函数

    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;

    if(SET == enet_interrupt_flag_get(ENET_DMA_INT_FLAG_RS)){

      /* 数据中断后释放信号量(在接收任务中处理) */

      xSemaphoreGiveFromISR(g_rx_semaphore, &xHigherPriorityTaskWoken);

    }

    /* 清除相关标志位 */

    enet_interrupt_flag_clear(ENET_DMA_INT_FLAG_RS_CLR);

    enet_interrupt_flag_clear(ENET_DMA_INT_FLAG_NI_CLR);

    if(pdFALSE != xHigherPriorityTaskWoken){

      portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);

    }

4、Lwip协议栈初始化,主要配置IP、MAC等信息,创建数据收发任务。

ip_addr_t fsl_netif0_ipaddr, fsl_netif0_netmask, fsl_netif0_gw;

tcpip_init(NULL, NULL);   

    inet_aton((char *)&phy_cfg.ip, &fsl_netif0_ipaddr);

    inet_aton((char *)&phy_cfg.netmask, &fsl_netif0_netmask);

    inet_aton((char *)&phy_cfg.gateway, &fsl_netif0_gw);   

    netif_add(&fsl_netif0, &fsl_netif0_ipaddr, &fsl_netif0_netmask, &fsl_netif0_gw, NULL, &ethernetif_init, &ethernet_input);

    netif_set_default(&fsl_netif0);

netif_set_up(&fsl_netif0);
其中ethernetif_init函数需在ethernetif.c文件中修改。此外,需要注意,在官方库中,默认使用的PHY为DP83848,需在“gd32f450xx_enet.h”中修改PHY寄存器地址,当前使用LAN8720。



上述内容大致为以太网的基本配置,此时应该能正常ping设备IP。

二、UDP连接:
UDP连接创建的大致流程为:创建套接字、绑定套接字、数据通信、套接字关闭。

1、创建套接字:socket(),指定协议类型等

int sockfd= socket(AF_INET, SOCK_DGRAM, 0);//UDP,IPV4协议
2、绑定套接字:bind(),将套接字与设备IP端口信息绑定

struct sockaddr_in local_addr;//本地IP、端口



local_addr.sin_family = AF_INET;//IPV4协议

local_addr.sin_port = htons(port);//设备端口

local_addr.sin_addr.s_addr = htons(INADDR_ANY);

bind(sockfd, (struct sockaddr *)& local_addr, sizeof(local_addr));
3、数据通信:

数据接收:

char buff[]={0};

struct sockaddr_in from_addr;//远端IP、端口

socklen_t fromlen;//长度

recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&from_addr, &fromlen); //此函数为阻塞函数,将接收的数据存放于buff中。
数据发送:数据发送需要知道发送的目标信息,可以是接收数据时的IP、端口,也可以自定义。

struct sockaddr_in service_addr;//服务器IP、端口

socklen_t service_len;

void init_service_socket()

{

ip_addr_t sip;//服务器IP

u_int16_t sport;//服务器端口

//初始化服务端的IP和端口

service_addr.sin_family = PF_INET;

service_addr.sin_port = htons(sport);   

service_addr.sin_addr.s_addr = sip.addr;         

service_len = sizeof(mqtt_service_addr);

}

//通过创建的套接字将数据发送至服务端

sendto(sockfd, send_buff,send_len,0,(struct sockaddr *)&service_addr,service_len);

三、TCP客户端
TCP连接创建的大致流程为:创建套接字、绑定套接字、连接服务器、数据通信、套接字关闭。

1、创建套接字:socket(),指定协议类型等

int sockfd= socket(AF_INET, SOCK_STREAM, 0);//TCP,IPV4协议
2、绑定套接字:bind(),将套接字与设备IP端口信息绑定

struct sockaddr_in local_addr;//本地IP、端口
local_addr.sin_family = AF_INET;//IPV4协议

local_addr.sin_port = htons(port);//设备端口

local_addr.sin_addr.s_addr = htons(INADDR_ANY);

bind(sockfd, (struct sockaddr *)& local_addr, sizeof(local_addr));
3、连接服务器:TCP通信是面向连接的,连接前先确定服务器的IP、端口信息。

struct sockaddr_in service_addr;//服务器IP、端口

socklen_t service_len;

void init_service_socket()

{

ip_addr_t sip;//服务器IP

u_int16_t sport;//服务器端口

//初始化服务端的IP和端口

service_addr.sin_family = PF_INET;

service_addr.sin_port = htons(sport);   

service_addr.sin_addr.s_addr = sip.addr;         

service_len = sizeof(mqtt_service_addr);

}

connect(sockfd, (struct sockaddr *)& service_addr, sizeof(service_addr));//与服务器之间建立TCP连接

4、数据通信:

数据接收:套接字只能接收来自建立连接的服务端的数据。

char buff[]={0};

recv(sockfd, buff, sizeof(buff), 0); //此函数为阻塞函数,将接收的数据存放于buff中。

数据发送:通过建立连接的套接字将数据发送至服务端。

//通过创建的套接字将数据发送至服务端

send(sockfd, send_buff,send_len,0);
四、TCP服务器
TCP服务器创建的大致流程为:创建新的TCP协议控制块、绑定IP端口、监听绑定的协议块、初始化接收回调函数。

1、创建TCP协议控制块:

struct tcp_pcb *Tcp_server_pcb=NULL;

Tcp_server_pcb = tcp_new();//创建新的TCP块

if(Tcp_server_pcb == NULL)

{

    printf("tcp new failed");

}
2、绑定IP、端口:固定端口(80),允许任意IP连接(同网段)。

if(tcp_bind(Tcp_server_pcb,(const ip_addr_t*)INADDR_ANY,80))//绑定IP与端口

{

    term_printf("tcp bind failed");

}
3、监听:

Tcp_server_pcb = tcp_listen(Tcp_server_pcb);//监听

if(Tcp_server_pcb == NULL)

{

term_printf("tcp listen failed");

}

tcp_arg(Tcp_server_pcb,Tcp_server_pcb);//确认监听连接
4、初始化接收回调函数:初始化完成后可通过网页端进行测试,也可以telnet连接。

数据解析与响应:

static err_t tcp_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {

if (p != NULL) {

    char *req = (char *)p->payload;//接收的数据

    char client_ip;

    inet_ntop(AF_INET, (char *)&pcb->remote_ip, client_ip, INET_ADDRSTRLEN);//将IP放入数组中

printf("Client connected from %s:%d\n", client_ip, ntohs(pcb->remote_port));//打印客户端IP、端口

//通过网页端测试

    if (strstr(req, "GET / ")) {//来自网页端的请求

      const char *html = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"

      "<html><body>Hello, GD32!</body></html>";

      if(tcp_write(pcb, html, strlen(html), TCP_WRITE_FLAG_COPY))

      {

      printf("数据写入缓冲区失败!");

      }

      if(tcp_output(pcb))

      {

      printf("TCP send data failed");

      }

    }else {

      const char *error = "HTTP/1.1 404 Not Found\r\n\r\n";

      tcp_write(pcb, error, strlen(error), TCP_WRITE_FLAG_MORE);

      if(tcp_output(pcb))

      {

      printf("TCP send data failed");

      }

    }

}

pbuf_free(p);

return ERR_OK;

}

绑定接收回调函数:

static err_t tcp_server_accept(void *arg, struct tcp_pcb *pcb, err_t err)

{

      /* 配置接收回调函数 */

      tcp_recv(pcb, tcp_recv);

      return ERR_OK;

}
初始化回调函数:

tcp_accept(Tcp_server_pcb,tcp_server_accept);//初始化接收回调函数
浏览器输入IP显示如下:



————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_60881302/article/details/145896003

weifeng90 发表于 2025-3-7 08:15

能分享一下源代码吗?谢谢。

pentruman 发表于 2025-3-19 11:14

实现UDP和TCP通信,是一个复杂的任务

saservice 发表于 2025-3-19 12:13

将 LwIP 和 FreeRTOS 的源码添加到项目中。
配置 LwIP 的网络接口,绑定到以太网接口。
创建任务来处理 LwIP 的任务调度。

uiint 发表于 2025-3-19 14:20

应用程序通过LwIP的套接字API发送数据。
LwIP将数据封装成TCP或UDP数据包,并通过以太网MAC控制器发送给PHY芯片。

benjaminka 发表于 2025-3-19 17:27

调用LwIP的初始化函数,配置网络接口。

515192147 发表于 2025-3-20 09:05

本帖最后由 515192147 于 2025-3-20 09:10 编辑

我们用 GD32F427 实现过 TCP UDP 编程,并能实现Web浏览
GD32F427实物图
https://bbs.21ic.com/data/attachment/forum/202310/24/104806mh9bp6vu4kikvtjz.jpg.thumb.jpg
Web浏览图
https://bbs.21ic.com/data/attachment/forum/202310/26/150937mn3t6nny2y2ftvqk.jpg.thumb.jpg
详细咨询见:
https://bbs.21ic.com/icview-3335470-1-1.html

louliana 发表于 2025-3-20 10:02

GD32F450是GigaDevice推出的一款基于ARM Cortex-M4内核的高性能微控制器,它具备丰富的外设接口

maudlu 发表于 2025-3-20 12:39

合理配置中断优先级,确保网络中断能够及时处理。

geraldbetty 发表于 2025-3-20 15:30

如何使用LwIP实现UDP广播?

plsbackup 发表于 2025-3-20 17:33

测试UDP和TCP的基本功能            

FranklinUNK 发表于 2025-3-20 21:39

网络这个容易学吗?

lihuami 发表于 2025-3-21 15:21

FreeRTOS提供了任务调度、同步、通信等机制,有助于实现复杂的网络通信功能。

claretttt 发表于 2025-3-21 17:05

从简单UDP透传开始验证基础功能,逐步扩展TCP复杂场景

jimmhu 发表于 2025-3-21 20:54

LwIP 和 FreeRTOS 都需要使用一定的内存资源,要合理分配内存,避免内存溢出。

gygp 发表于 2025-3-22 17:46

开发基于UDP的应用程序,实现数据包的发送和接收。

10299823 发表于 2025-3-22 18:28

下载并配置 GD32F4xx 的固件库。

wwppd 发表于 2025-3-22 18:51

根据GD32F450的内存资源,配置LwIP的内存管理参数,如缓冲区大小、TCP/IP栈大小等。

hilahope 发表于 2025-3-22 19:07

整合GD32F450的硬件特性、FreeRTOS的实时调度能力及LwIP的轻量级协议栈,可构建高效稳定的网络应用。关键在于正确配置DMA通道、优化内存分配及任务优先级。

yeates333 发表于 2025-3-22 19:40

要根据实际的网络环境正确配置 IP 地址、子网掩码、网关等参数
页: [1] 2
查看完整版本: GD32F450之以太网(Lwip2.1.2+FreeRTOS+UDP+TCP)