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

GD32F450系列以太网配置

GD32F450VET6+LAN8720+FreeRTOS

UDP连接

TCP客户端

TCP服务器

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

一、基础配置:

  1. 引进外设初始化,选择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_ADDRSTRLEN];

    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显示如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值