在GD32F4xx上移植LWIP

今天继续总结LWIP的移植过程。

参考代码

LWIP源码包

在官网下载的源码包:lwip-2.1.3.zip

还有移植软件包:contrib-2.1.0.zip

GD32固件库

GD32F4xx_Firmware_Library_V3.1.0.7z

重点关注TELNET例程:

GD32F4xx_Firmware_Library_V3.1.0.7z\GD32F4xx_Firmware_Library_V3.1.0\Examples\ENET\Telnet

移植过程

拷贝代码

1,拷贝lwip的src目录;

lwip-2.1.3.zip\lwip-2.1.3\src

2,拷贝GD的ENET例程下的arch和Basic目录;

GD32F4xx_Firmware_Library_V3.1.0\Examples\ENET\Telnet\lwip-2.1.2\port\GD32F4xx\arch

GD32F4xx_Firmware_Library_V3.1.0\Examples\ENET\Telnet\lwip-2.1.2\port\GD32F4xx\Basic

3,拷贝GD的ENET例程中src目录下的netconf.c, netconf.h, lwipopts.h

4,拷贝应用层程序:tcp_echo

contrib-2.1.0\apps\tcpecho_raw下面的tcpecho_raw.c和tcpecho.h文件:

也可以参考ST的tcp_echoserver:

STSW_STM32070_V1.1.1.zip\STM32F4x7_ETH_LwIP_V1.1.1\Project\Standalone\tcp_echo_server\src\tcp_echoserver.c

或者:

STM32Cube\Repository\STM32Cube_FW_F4_V1.27.0\Projects\STM32469I_EVAL\Applications\LwIP\LwIP_TCP_Echo_Server\Src\tcp_echoserver.c

改写代码

新建一个任务,代替main函数。

原始main函数:

int main(void)
{

    /* setup ethernet system(GPIOs, clocks, MAC, DMA, systick) */
    enet_system_setup();

    /* initilaize the LwIP stack */
    lwip_stack_init();

    while(1) {

        /* check if any packet received */
        if(enet_rxframe_size_get()) {
            /* process received ethernet packet */
            lwip_pkt_handle();
        }

        /* handle periodic timers for LwIP */
        lwip_periodic_handle(g_localtime);

    }
}

新建一个模块,代替gd32f4xx_enet_eval.c文件。里面的主要内容是:

/*!
    \brief      setup ethernet system(GPIOs, clocks, MAC, DMA, systick)
    \param[in]  none
    \param[out] none
    \retval     none
*/
void enet_system_setup(void)
{
    uint32_t ahb_frequency = 0;

#ifdef USE_ENET_INTERRUPT
    nvic_configuration();
#endif /* USE_ENET_INTERRUPT */

    /* configure the GPIO ports for ethernet pins */
    enet_gpio_config();

    /* configure the ethernet MAC/DMA */
    enet_mac_dma_config();

    if(0 == enet_init_status) {
        while(1) {
        }
    }

#ifdef USE_ENET_INTERRUPT
    enet_interrupt_enable(ENET_DMA_INT_NIE);
    enet_interrupt_enable(ENET_DMA_INT_RIE);
#endif /* USE_ENET_INTERRUPT */

#ifdef SELECT_DESCRIPTORS_ENHANCED_MODE
    enet_desc_select_enhanced_mode();
#endif /* SELECT_DESCRIPTORS_ENHANCED_MODE */

    /* configure systick clock source as HCLK */
    systick_clksource_set(SYSTICK_CLKSOURCE_HCLK);

    /* an interrupt every 10ms */
    ahb_frequency = rcu_clock_freq_get(CK_AHB);
    SysTick_Config(ahb_frequency / 100);
}

/*!
    \brief      configures the ethernet interface
    \param[in]  none
    \param[out] none
    \retval     none
*/
static void enet_mac_dma_config(void)
{
    ErrStatus reval_state = ERROR;

    /* enable ethernet clock  */
    rcu_periph_clock_enable(RCU_ENET);
    rcu_periph_clock_enable(RCU_ENETTX);
    rcu_periph_clock_enable(RCU_ENETRX);

    /* reset ethernet on AHB bus */
    enet_deinit();

    reval_state = enet_software_reset();
    if(ERROR == reval_state) {
        while(1) {}
    }

    /* configure the parameters which are usually less cared for enet initialization */
//  enet_initpara_config(HALFDUPLEX_OPTION, ENET_CARRIERSENSE_ENABLE|ENET_RECEIVEOWN_ENABLE|ENET_RETRYTRANSMISSION_DISABLE|ENET_BACKOFFLIMIT_10|ENET_DEFERRALCHECK_DISABLE);
//  enet_initpara_config(DMA_OPTION, ENET_FLUSH_RXFRAME_ENABLE|ENET_SECONDFRAME_OPT_ENABLE|ENET_NORMAL_DESCRIPTOR);

#ifdef CHECKSUM_BY_HARDWARE
    enet_init_status = enet_init(ENET_AUTO_NEGOTIATION, ENET_AUTOCHECKSUM_DROP_FAILFRAMES, ENET_BROADCAST_FRAMES_PASS);
#else
    enet_init_status = enet_init(ENET_AUTO_NEGOTIATION, ENET_NO_AUTOCHECKSUM, ENET_BROADCAST_FRAMES_PASS);
#endif /* CHECKSUM_BY_HARDWARE */

}

测试

这样,已经可以ping通了:

测试TCP连接和数据收发,都是正常的。

核心代码解读

硬件外设初始化

配置ENET模式:RMII,使能ENET收发时钟。

	/* enable SYSCFG clock */
	rcu_periph_clock_enable(RCU_SYSCFG);

	// RMII接口
	syscfg_enet_phy_interface_config(SYSCFG_ENET_PHY_RMII);

	/* enable ethernet clock  */
	rcu_periph_clock_enable(RCU_ENET);
	rcu_periph_clock_enable(RCU_ENETTX);
	rcu_periph_clock_enable(RCU_ENETRX);

效果:

RCU组中APB2EN寄存器中SYSCFGEN位置1:开启时钟;

SYSCFG组中CFG1寄存器的ENET_PHY_SEL位置1:设置为RMII模式;

RCU组中AHB1EN寄存器的ENETEN、ENETTXEN、ENETRXEN三个位置1:开启时钟;

配置GPIO

略。

ENET系统配置

代码参考:gd32f4xx_enet_eval.c文件中的enet_system_setup()函数。

/*!
    \brief      setup ethernet system(GPIOs, clocks, MAC, DMA, systick)
    \param[in]  none
    \param[out] none
    \retval     none
*/
void enet_system_setup(void)
{
    uint32_t ahb_frequency = 0;

#ifdef USE_ENET_INTERRUPT
    nvic_configuration();
#endif /* USE_ENET_INTERRUPT */

    /* configure the GPIO ports for ethernet pins */
    enet_gpio_config();

    /* configure the ethernet MAC/DMA */
    enet_mac_dma_config();

    if(0 == enet_init_status) {
        while(1) {
        }
    }

#ifdef USE_ENET_INTERRUPT
    enet_interrupt_enable(ENET_DMA_INT_NIE);
    enet_interrupt_enable(ENET_DMA_INT_RIE);
#endif /* USE_ENET_INTERRUPT */

#ifdef SELECT_DESCRIPTORS_ENHANCED_MODE
    enet_desc_select_enhanced_mode();
#endif /* SELECT_DESCRIPTORS_ENHANCED_MODE */

    /* configure systick clock source as HCLK */
    systick_clksource_set(SYSTICK_CLKSOURCE_HCLK);

    /* an interrupt every 10ms */
    ahb_frequency = rcu_clock_freq_get(CK_AHB);
    SysTick_Config(ahb_frequency / 100);
}

去除中断和sysTick相关的代码后,最主要的就是调用enet_mac_dma_config()函数。

/*!
    \brief      configures the ethernet interface
    \param[in]  none
    \param[out] none
    \retval     none
*/
static void enet_mac_dma_config(void)
{
    ErrStatus reval_state = ERROR;

    /* enable ethernet clock  */
    rcu_periph_clock_enable(RCU_ENET);
    rcu_periph_clock_enable(RCU_ENETTX);
    rcu_periph_clock_enable(RCU_ENETRX);

    /* reset ethernet on AHB bus */
    enet_deinit();

    reval_state = enet_software_reset();
    if(ERROR == reval_state) {
        while(1) {}
    }

    /* configure the parameters which are usually less cared for enet initialization */
//  enet_initpara_config(HALFDUPLEX_OPTION, ENET_CARRIERSENSE_ENABLE|ENET_RECEIVEOWN_ENABLE|ENET_RETRYTRANSMISSION_DISABLE|ENET_BACKOFFLIMIT_10|ENET_DEFERRALCHECK_DISABLE);
//  enet_initpara_config(DMA_OPTION, ENET_FLUSH_RXFRAME_ENABLE|ENET_SECONDFRAME_OPT_ENABLE|ENET_NORMAL_DESCRIPTOR);

#ifdef CHECKSUM_BY_HARDWARE
    enet_init_status = enet_init(ENET_AUTO_NEGOTIATION, ENET_AUTOCHECKSUM_DROP_FAILFRAMES, ENET_BROADCAST_FRAMES_PASS);
#else
    enet_init_status = enet_init(ENET_AUTO_NEGOTIATION, ENET_NO_AUTOCHECKSUM, ENET_BROADCAST_FRAMES_PASS);
#endif /* CHECKSUM_BY_HARDWARE */

}

简化之后:


static void enet_mac_dma_config(void)
{
    /* reset ethernet on AHB bus */
    enet_deinit();

    reval_state = enet_software_reset();

    enet_init_status = enet_init(ENET_AUTO_NEGOTIATION, ENET_AUTOCHECKSUM_DROP_FAILFRAMES, ENET_BROADCAST_FRAMES_PASS);
}

解读:先反初始化,再软件复位,再初始化。

其中,软件复位函数enet_software_reset()是将ENET_DMA组中的DMA总线控制寄存器(DMA_BCTL)的SWR位置1。然后等待硬件清零(复位完成后由硬件清零)。

其中,初始化函数enet_init()的主要任务有:

1,配置媒体模式enet_mediamode_enum:是10M还是100M;是半双工还是全双工,是否回环。

对应ENET_MAC组中MAC_CFG寄存器的SPD、DPM、LBM位。

2,配置校验和enet_chksumconf_enum:

/* IP frame checksum function */
typedef enum
{
    ENET_NO_AUTOCHECKSUM                = (uint32_t)0x00000000U,                    /*!< disable IP frame checksum function */
    ENET_AUTOCHECKSUM_DROP_FAILFRAMES   = ENET_MAC_CFG_IPFCO,                       /*!< enable IP frame checksum function */
    ENET_AUTOCHECKSUM_ACCEPT_FAILFRAMES = (ENET_MAC_CFG_IPFCO|ENET_DMA_CTL_DTCERFD) /*!< enable IP frame checksum function, and the received frame
                                                                                         with only payload error but no other errors will not be dropped */
}enet_chksumconf_enum;

对应ENET_MAC组中MAC_CFG寄存器的IPFCO位:

以及ENET_DMA组中DMA_CTRL寄存器的DTCERFD位:

3,配置帧接收滤波模式

/* received frame filter function */
typedef enum
{
    ENET_PROMISCUOUS_MODE           = ENET_MAC_FRMF_PM,                             /*!< promiscuous mode enabled */
    ENET_RECEIVEALL                 = (int32_t)ENET_MAC_FRMF_FAR,                   /*!< all received frame are forwarded to application */
    ENET_BROADCAST_FRAMES_PASS      = (uint32_t)0x00000000U,                        /*!< the address filters pass all received broadcast frames */
    ENET_BROADCAST_FRAMES_DROP      = ENET_MAC_FRMF_BFRMD                           /*!< the address filters filter all incoming broadcast frames */
}enet_frmrecept_enum;

4,根据全局变量enet_initpara中的参数配置其他项目。

该变量在gd32f4xx_enet.c文件中定义,可以使用enet_initpara_config()函数来更改配置。

LWIP协议栈初始化

对应netconf.c文件中的lwip_stack_init()函数。

精简后代码如下:

void lwip_stack_init(void)
{
    ip_addr_t ipaddr;
    ip_addr_t netmask;
    ip_addr_t gw;

    /* initializes the dynamic memory heap defined by MEM_SIZE */
    mem_init();

    /* initializes the memory pools defined by MEMP_NUM_x */
    memp_init();

    IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
    IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3);
    IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);


    netif_add(&g_mynetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);

    /* registers the default network interface */
    netif_set_default(&g_mynetif);

    /* when the netif is fully configured this function must be called */
    netif_set_up(&g_mynetif);
}

可以看出,主要工作是,LWIP内存堆和内存池初始化;设置IP地址;添加网卡;设置为默认网卡;启用网卡;

其中,添加网卡时需要的参数有:网卡初始化函数指针;网卡输入函数指针。

网卡初始化

在添加网卡函数netif_add()里面,会调用网卡初始化函数:

对应的函数是ethernetif_init()函数:

err_t ethernetif_init(struct netif *netif)
{
    LWIP_ASSERT("netif != NULL", (netif != NULL));
  
#if LWIP_NETIF_HOSTNAME
    /* Initialize interface hostname */
    netif->hostname = "Gigadevice.COM_lwip";
#endif /* LWIP_NETIF_HOSTNAME */

    netif->name[0] = IFNAME0;
    netif->name[1] = IFNAME1;
    /* We directly use etharp_output() here to save a function call.
     * You can instead declare your own function an call etharp_output()
     * from it if you have to do some checks before sending (e.g. if link
     * is available...) */
    netif->output = etharp_output;
    netif->linkoutput = low_level_output;

    /* initialize the hardware */
    low_level_init(netif);

    return ERR_OK;
}

该函数除了配置网卡的基本属性外,最重要的工作有两点:

  1. 指定链路输出函数指针;
  2. 调用网卡底层初始化;

底层初始化

主要任务:

  • 设置MAC地址;
  • 初始化DMA描述符;
  • 使能网络外设;

代码如下:

static void low_level_init(struct netif *netif)
{
    int i; 

    /* set MAC hardware address length */
    netif->hwaddr_len = ETHARP_HWADDR_LEN;

    /* set MAC hardware address */
    netif->hwaddr[0] =  MAC_ADDR0;
    netif->hwaddr[1] =  MAC_ADDR1;
    netif->hwaddr[2] =  MAC_ADDR2;
    netif->hwaddr[3] =  MAC_ADDR3;
    netif->hwaddr[4] =  MAC_ADDR4;
    netif->hwaddr[5] =  MAC_ADDR5;
    
    /* initialize MAC address in ethernet MAC */ 
    enet_mac_address_set(ENET_MAC_ADDRESS0, netif->hwaddr);

    /* maximum transfer unit */
    netif->mtu = 1500;

    /* device capabilities */
    /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

    /* initialize descriptors list: chain/ring mode */

    enet_descriptors_chain_init(ENET_DMA_TX);
    enet_descriptors_chain_init(ENET_DMA_RX);
    

    /* enable ethernet Rx interrrupt */
    {   int i;
        for(i=0; i<ENET_RXBUF_NUM; i++){ 
           enet_rx_desc_immediate_receive_complete_interrupt(&rxdesc_tab[i]);
        }
    }


    /* enable the TCP, UDP and ICMP checksum insertion for the Tx frames */
    for(i=0; i < ENET_TXBUF_NUM; i++){
        enet_transmit_checksum_config(&txdesc_tab[i], ENET_CHECKSUM_TCPUDPICMP_FULL);
    }


    /* note: TCP, UDP, ICMP checksum checking for received frame are enabled in DMA config */

    /* enable MAC and DMA transmission and reception */
    enet_enable();
}

注意:MAC地址既要设置给LWIP的网卡,又要设置给MAC寄存器。

其中,DMA描述符初始化为链式结构。

关于DMA描述符,后面单独写一篇。

ENET使能

分别使能发送和接收:

/*!
    \brief    ENET Tx and Rx function enable (include MAC and DMA module)
    \param[in]  none
    \param[out] none
    \retval     none
*/
void enet_enable(void)
{
    enet_tx_enable();
    enet_rx_enable();
}

里面又包含MAC使能和DMA使能:

网卡输入函数

主要是调用低层输入函数获取数据,然后调用网卡的input()函数通知其处理收到的数据:

err_t ethernetif_input(struct netif *netif)
{
    err_t err;
    struct pbuf *p;

    /* move received packet into a new pbuf */
    p = low_level_input(netif);

    /* no packet could be read, silently ignore this */
    if (p == NULL) return ERR_MEM;

    /* entry point to the LwIP stack */
    err = netif->input(p, netif);
    
    if (err != ERR_OK){
        LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
        pbuf_free(p);
        p = NULL;
    }
    return err;
}

 底层输入函数

就是把ENET控制器收到的数据,拷贝到LWIP的pbuf中。

static struct pbuf * low_level_input(struct netif *netif)
{
    struct pbuf *p, *q;
    u16_t len;
    int l =0;
    uint8_t *buffer;
     
    p = NULL;
    
    /* obtain the size of the packet and put it into the "len" variable. */
    len = enet_desc_information_get(dma_current_rxdesc, RXDESC_FRAME_LENGTH);
    buffer = (uint8_t *)(enet_desc_information_get(dma_current_rxdesc, RXDESC_BUFFER_1_ADDR));
    
    /* we allocate a pbuf chain of pbufs from the Lwip buffer pool */
    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    
    /* copy received frame to pbuf chain */
    if (p != NULL){
        for (q = p; q != NULL; q = q->next){ 
            memcpy((uint8_t *)q->payload, (u8_t*)&buffer[l], q->len);
            l = l + q->len;
        }    
    }
      
    ENET_NOCOPY_FRAME_RECEIVE();

    return p;
}

要想理解这里的for循环,就需要知道pbuf的结构,如下图:

enet驱动中的数据是保存到一个数组里的,而LWIP的数据是保存在pbuf中的,而pbuf是一块一块链接起来的,因此,数据拷贝时就需要for循环。

网卡输出函数(底层输出函数)

就是把LWIP的输出缓冲区的数据拷贝到ENET控制器的DMA。

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
    struct pbuf *q;
    int framelength = 0;
    uint8_t *buffer;

    while((uint32_t)RESET != (dma_current_txdesc->status & ENET_TDES0_DAV)){
    }  
    buffer = (uint8_t *)(enet_desc_information_get(dma_current_txdesc, TXDESC_BUFFER_1_ADDR));
    
    /* copy frame from pbufs to driver buffers */
    for(q = p; q != NULL; q = q->next){ 
        memcpy((uint8_t *)&buffer[framelength], q->payload, q->len);
        framelength = framelength + q->len;
    }
    
    /* note: padding and CRC for transmitted frame 
       are automatically inserted by DMA */

    /* transmit descriptors to give to DMA */    
    ENET_NOCOPY_FRAME_TRANSMIT(framelength);

    return ERR_OK;
}

应用任务初始化

应用层任务初始化的工作:申请一个新的TCP协议控制块,然后绑定bind、监听listen、注册接收函数accept。

void
tcpecho_raw_init(void)
{
  tcpecho_raw_pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
  if (tcpecho_raw_pcb != NULL) {
    err_t err;

    err = tcp_bind(tcpecho_raw_pcb, IP_ANY_TYPE, 7);
    if (err == ERR_OK) {
      tcpecho_raw_pcb = tcp_listen(tcpecho_raw_pcb);
      tcp_accept(tcpecho_raw_pcb, tcpecho_raw_accept);
    }
  }
}

协议栈主循环

void APP_EthernetCommRun(void)
{
	/* check if any packet received */
	if (enet_rxframe_size_get())
	{
		/* process received ethernet packet */
		lwip_pkt_handle();
	}
	g_localtime = TMR_GetTickCount();
	lwip_periodic_handle(g_localtime);
}

void lwip_pkt_handle(void)
{
    /* read a received packet from the Ethernet buffers and send it to the lwIP for handling */
    ethernetif_input(&g_mynetif);
}

void lwip_periodic_handle(__IO uint32_t localtime)
{
    /* TCP periodic process every 250 ms */
    if(localtime - tcp_timer >= TCP_TMR_INTERVAL) {
        tcp_timer =  localtime;
        tcp_tmr();
    }

    /* ARP periodic process every 5s */
    if((localtime - arp_timer) >= ARP_TMR_INTERVAL) {
        arp_timer = localtime;
        etharp_tmr();
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值