今天继续总结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, ðernetif_init, ðernet_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;
}
该函数除了配置网卡的基本属性外,最重要的工作有两点:
- 指定链路输出函数指针;
- 调用网卡底层初始化;
底层初始化
主要任务:
- 设置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();
}
}