当前位置: 代码迷 >> 综合 >> LWIP学习系列(二):STM32中ETH外设的配置与LWIP的结合使用
  详细解决方案

LWIP学习系列(二):STM32中ETH外设的配置与LWIP的结合使用

热度:84   发布时间:2024-02-28 07:44:52.0

目录

一、STM32中ETH外设的配置流程(HAL库)

(1)ETH大致初始化与使用流程

(2)访问外部PHY寄存器相关函数

二、LWIP关于ethernet的底层驱动移植

(1)LWIP网卡需要由用户实现的函数部分

(2)ethernetif_init 函数相关内容整理

(3)ethernetif_input 函数相关整理

(4)low_level_init 相关内容整理

(5)low_level_input 相关内容整理

(6)low_level_output 相关内容整理

三、总结


一、STM32中ETH外设的配置流程(HAL库)

(1)ETH大致初始化与使用流程

  1. 声明 ETH_HandleTypeDef hethETH外设句柄
  2. 对ETH句柄根据实际情况进行赋值、然后调用 HAL_ETH_Init() 来初始ETH外设(MAC,DMA, ......)
  3. 通过 HAL_ETH_MspInit() 来初始化ETH的底层资源:                                                                                                           (1)ETH时钟ETH的GPIO时钟                                                                                                                                             (2)ETH的GPIO管脚                                                                                                                                                               (3)ETH的中断向量NVIC       
  4. 初始化ETH DMA 描述符 并指向分配好的buffer 
    HAL_ETH_DMATxDescListInit(); 
    HAL_ETH_DMARxDescListInit();
  5.  启动ETH外设           
    HAL_ETH_Start();
  6. 接受数据帧或者发送数据帧
    发送:HAL_ETH_TransmitFrame();
    接收:HAL_ETH_GetReceivedFrame();(应该放入死循环中,不停接受数据) 
    从中断中接收:HAL_ETH_GetReceivedFrame_IT(); 

(2)访问外部PHY寄存器相关函数

  1. 读取PHY 寄存器函数:
    HAL_ETH_ReadPHYRegister();
  2. 写PHY 指定寄存器函数:
    HAL_ETH_WritePHYRegister();
  3. 初始化ETH后,调整ethernet MAC函数
    HAL_ETH_ConfigMAC(); 
  4. 初始化ETH后,调整ETH DMA函数
    HAL_ETH_ConfigDMA();

二、LWIP关于ethernet的底层驱动移植

(1)LWIP网卡需要由用户实现的函数部分

注:LWIP提供了有关网卡接口的一系列函数需要用户自己实现。

需要用户自己实现的内容有:

  1. err_t  ethernetif_init(struct netif *netif) 【网卡初始化函数,具体初始化由 low_level_init 来实现
  2. void  ethernetif_input(struct netif *netif); 【从网卡中读取接收到的数据,具体接收函数由low_level_input来实现
  3. void low_level_init(struct netif *netif) 【网卡的底层初始化函数】

  4. struct pbuf * low_level_input(struct netif *netif)【网卡的底层接收函数】

  5. err_t low_level_output(struct netif *netif, struct pbuf *p)【网卡的底层发送函数】

(2)ethernetif_init 函数相关内容整理

ethernetif_init  函数,主要是给netif 网卡结构体进行赋值,将底层的输入函数传给netif 网卡结构体中

  • STM32CubeMX生成的lwIP ethernetif_init
err_t ethernetif_init(struct netif *netif)
{LWIP_ASSERT("netif != NULL", (netif != NULL));#if LWIP_NETIF_HOSTNAME/* Initialize interface hostname */netif->hostname = "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...) */#if LWIP_IPV4
#if LWIP_ARP || LWIP_ETHERNET
#if LWIP_ARPnetif->output = etharp_output;
#else/* The user should write ist own code in low_level_output_arp_off function */netif->output = low_level_output_arp_off;
#endif /* LWIP_ARP */
#endif /* LWIP_ARP || LWIP_ETHERNET */
#endif /* LWIP_IPV4 */#if LWIP_IPV6netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */netif->linkoutput = low_level_output;/* initialize the hardware */low_level_init(netif);return ERR_OK;
}
  •  由lwip官方下载的示例代码
err_t
ethernetif_init(struct netif *netif)
{struct ethernetif *ethernetif;LWIP_ASSERT("netif != NULL", (netif != NULL));ethernetif = mem_malloc(sizeof(struct ethernetif));if (ethernetif == NULL) {LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));return ERR_MEM;}#if LWIP_NETIF_HOSTNAME/* Initialize interface hostname */netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME *//** Initialize the snmp variables and counters inside the struct netif.* The last argument should be replaced with your link speed, in units* of bits per second.*/MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);netif->state = ethernetif;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...) */
#if LWIP_IPV4netif->output = etharp_output;
#endif /* LWIP_IPV4 */
#if LWIP_IPV6netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */netif->linkoutput = low_level_output;ethernetif->ethaddr = (struct eth_addr *) & (netif->hwaddr[0]);/* initialize the hardware */low_level_init(netif);return ERR_OK;
}

小结:由上面两种程序对比,可以看出大同小异。基本上都是对netif 网卡结构体进行赋值,然后真正的初始化交由low_level_init 来进行。

(3)ethernetif_input 函数相关整理

ethernetif_input  主要是用于接收eth传来的数据包。

  • 在RTOS中,一般需要设置一个优先级很高的线程来一直循环调用该函数(主要是循环调用其真正的接收实现函数low_level_input
  • 在裸机环境中,一般设置在主函数中设置死循环调用该函数。
  • STM32CubeMX生成的lwIP的 ethernetif_input
void ethernetif_input(void const * argument)
{struct pbuf *p;struct netif *netif = (struct netif *) argument;for( ;; ){if (osSemaphoreWait(s_xSemaphore, TIME_WAITING_FOR_INPUT) == osOK){do{   p = low_level_input( netif );if   (p != NULL){if (netif->input( p, netif) != ERR_OK ){pbuf_free(p);}}} while(p!=NULL);}}
}
  • lwIP官网下载的示例代码
static void
ethernetif_input(struct netif *netif)
{struct ethernetif *ethernetif;struct eth_hdr *ethhdr;struct pbuf *p;ethernetif = netif->state;/* move received packet into a new pbuf */p = low_level_input(netif);/* if no packet could be read, silently ignore this */if (p != NULL) {/* pass all packets to ethernet_input, which decides what packets it supports */if (netif->input(p, netif) != ERR_OK) {LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));pbuf_free(p);p = NULL;}}
}

小结:对比上面两种程序,可以发现也是大同小异。真正的接收函数是由low_level_input 来实现。同时在带有RTOS的情况下,是将ethernetif_input 当成一个线程一直进行,通过对信号量资源的检测来判断是否数据传入,然后再将通过low_level_input 来接收数据。而lwip官网的程序,是不带操作系统的例程,所以直接交由low_level_input 来接收数据,如果要一直接收数据,则需要将ethernetif_input 放入死循环中。

(4)low_level_init 相关内容整理

low_level_init 主要任务

  • 主要是对STM32中的ETH外设进行初始化(较为具体的流程可以查看上面的ETH配置流程
  • 然后继续对 netif 网卡结构体 进行初始化赋值操作
  • 创建 二值信号量 Semaphore 用于 ethernetif_input 网卡接收线程使用
  • 然后创建 ethernetif_input  最高优先级的线程,使能ETH
  • (然后看情况配置ETH的寄存器)

注:其实就大致四个部分(带RTOS情况下) STM32的ETH外设初始化netif网卡结构体初始化、ETH数据接收线程(看需求)ETH寄存器进行的设置

  • STM32CubeMX生成的lwIP的 low_level_init
static void low_level_init(struct netif *netif)
{ uint32_t regvalue = 0;HAL_StatusTypeDef hal_eth_init_status;/* Init ETH */uint8_t MACAddr[6] ;heth.Instance = ETH;heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;heth.Init.PhyAddress = LAN8742A_PHY_ADDRESS;MACAddr[0] = 0x00;MACAddr[1] = 0x80;MACAddr[2] = 0xE1;MACAddr[3] = 0x00;MACAddr[4] = 0x00;MACAddr[5] = 0x00;heth.Init.MACAddr = &MACAddr[0];heth.Init.RxMode = ETH_RXINTERRUPT_MODE;heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;/* USER CODE BEGIN MACADDRESS *//* USER CODE END MACADDRESS */hal_eth_init_status = HAL_ETH_Init(&heth);if (hal_eth_init_status == HAL_OK){/* Set netif link flag */  netif->flags |= NETIF_FLAG_LINK_UP;}/* Initialize Tx Descriptors list: Chain Mode */HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);/* Initialize Rx Descriptors list: Chain Mode  */HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);#if LWIP_ARP || LWIP_ETHERNET /* set MAC hardware address length */netif->hwaddr_len = ETH_HWADDR_LEN;/* set MAC hardware address */netif->hwaddr[0] =  heth.Init.MACAddr[0];netif->hwaddr[1] =  heth.Init.MACAddr[1];netif->hwaddr[2] =  heth.Init.MACAddr[2];netif->hwaddr[3] =  heth.Init.MACAddr[3];netif->hwaddr[4] =  heth.Init.MACAddr[4];netif->hwaddr[5] =  heth.Init.MACAddr[5];/* maximum transfer unit */netif->mtu = 1500;/* Accept broadcast address and ARP traffic *//* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */#if LWIP_ARPnetif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;#else netif->flags |= NETIF_FLAG_BROADCAST;#endif /* LWIP_ARP *//* create a binary semaphore used for informing ethernetif of frame reception */osSemaphoreDef(SEM);s_xSemaphore = osSemaphoreCreate(osSemaphore(SEM), 1);/* create the task that handles the ETH_MAC */osThreadDef(EthIf, ethernetif_input, osPriorityRealtime, 0, INTERFACE_THREAD_STACK_SIZE);osThreadCreate (osThread(EthIf), netif);/* Enable MAC and DMA transmission and reception */HAL_ETH_Start(&heth);/* USER CODE BEGIN PHY_PRE_CONFIG */ /* USER CODE END PHY_PRE_CONFIG *//* Read Register Configuration */HAL_ETH_ReadPHYRegister(&heth, PHY_ISFR, &regvalue);regvalue |= (PHY_ISFR_INT4);/* Enable Interrupt on change of link status */ HAL_ETH_WritePHYRegister(&heth, PHY_ISFR , regvalue );/* Read Register Configuration */HAL_ETH_ReadPHYRegister(&heth, PHY_ISFR , &regvalue);/* USER CODE BEGIN PHY_POST_CONFIG */ /* USER CODE END PHY_POST_CONFIG */#endif /* LWIP_ARP || LWIP_ETHERNET *//* USER CODE BEGIN LOW_LEVEL_INIT */ /* USER CODE END LOW_LEVEL_INIT */
}
  • lwIP官网下载的示例代码
low_level_init(struct netif *netif)
{struct ethernetif *ethernetif = netif->state;/* set MAC hardware address length */netif->hwaddr_len = ETHARP_HWADDR_LEN;/* set MAC hardware address */netif->hwaddr[0] = ;...netif->hwaddr[5] = ;/* 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;#if LWIP_IPV6 && LWIP_IPV6_MLD/** For hardware/netifs that implement MAC filtering.* All-nodes link-local is handled by default, so we must let the hardware know* to allow multicast packets in.* Should set mld_mac_filter previously. */if (netif->mld_mac_filter != NULL) {ip6_addr_t ip6_allnodes_ll;ip6_addr_set_allnodes_linklocal(&ip6_allnodes_ll);netif->mld_mac_filter(netif, &ip6_allnodes_ll, NETIF_ADD_MAC_FILTER);}
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD *//* Do whatever else is needed to initialize interface. */
}

小结:对比两种程序,可以看到注释中大致的流程是相同的。基本上是都是 STM32ETH外设初始化、 继续对netif 网卡结构体的初始化、(信号量的创建与ETH接收数据线程的创建、 ETH寄存器部分位修改及使能)

(5)low_level_input 相关内容整理

low_level_input 主要任务

  • 通过STM32 ETH外设的 HAL_ETH_GetReceivedFrame_IT接收从以太网接口传来的数据包
  • 将接收到的数据包封装成pbuf的形式(最终需要返回该pbuf的地址
  • 释放 DMA 接收描述符(为下一次接收做准备)
  • STM32CubeMX生成的lwIP的 low_level_input
static struct pbuf * low_level_input(struct netif *netif)
{struct pbuf *p = NULL;struct pbuf *q = NULL;uint16_t len = 0;uint8_t *buffer;__IO ETH_DMADescTypeDef *dmarxdesc;uint32_t bufferoffset = 0;uint32_t payloadoffset = 0;uint32_t byteslefttocopy = 0;uint32_t i=0;/* get received frame */if (HAL_ETH_GetReceivedFrame_IT(&heth) != HAL_OK)return NULL;/* Obtain the size of the packet and put it into the "len" variable. */len = heth.RxFrameInfos.length;buffer = (uint8_t *)heth.RxFrameInfos.buffer;if (len > 0){/* We allocate a pbuf chain of pbufs from the Lwip buffer pool */p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);}if (p != NULL){dmarxdesc = heth.RxFrameInfos.FSRxDesc;bufferoffset = 0;for(q = p; q != NULL; q = q->next){byteslefttocopy = q->len;payloadoffset = 0;/* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size*/while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE ){/* Copy data to pbuf */memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));/* Point to next descriptor */dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);bufferoffset = 0;}/* Copy remaining data in pbuf */memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);bufferoffset = bufferoffset + byteslefttocopy;}}  /* Release descriptors to DMA *//* Point to first descriptor */dmarxdesc = heth.RxFrameInfos.FSRxDesc;/* Set Own bit in Rx descriptors: gives the buffers back to DMA */for (i=0; i< heth.RxFrameInfos.SegCount; i++){  dmarxdesc->Status |= ETH_DMARXDESC_OWN;dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);}/* Clear Segment_Count */heth.RxFrameInfos.SegCount =0;  /* When Rx Buffer unavailable flag is set: clear it and resume reception */if ((heth.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)  {/* Clear RBUS ETHERNET DMA flag */heth.Instance->DMASR = ETH_DMASR_RBUS;/* Resume DMA reception */heth.Instance->DMARPDR = 0;}return p;
}
  • lwIP官网下载的示例代码
static struct pbuf *
low_level_input(struct netif *netif)
{struct ethernetif *ethernetif = netif->state;struct pbuf *p, *q;u16_t len;/* Obtain the size of the packet and put it into the "len"variable. */len = ;#if ETH_PAD_SIZElen += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif/* We allocate a pbuf chain of pbufs from the pool. */p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);if (p != NULL) {#if ETH_PAD_SIZEpbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endif/* We iterate over the pbuf chain until we have read the entire* packet into the pbuf. */for (q = p; q != NULL; q = q->next) {/* Read enough bytes to fill this pbuf in the chain. The* available data in the pbuf is given by the q->len* variable.* This does not necessarily have to be a memcpy, you can also preallocate* pbufs for a DMA-enabled MAC and after receiving truncate it to the* actually received size. In this case, ensure the tot_len member of the* pbuf is the sum of the chained pbuf len members.*/read data into(q->payload, q->len);}acknowledge that packet has been read();MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len);if (((u8_t *)p->payload)[0] & 1) {/* broadcast or multicast packet*/MIB2_STATS_NETIF_INC(netif, ifinnucastpkts);} else {/* unicast packet*/MIB2_STATS_NETIF_INC(netif, ifinucastpkts);}
#if ETH_PAD_SIZEpbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endifLINK_STATS_INC(link.recv);} else {drop packet();LINK_STATS_INC(link.memerr);LINK_STATS_INC(link.drop);MIB2_STATS_NETIF_INC(netif, ifindiscards);}return p;
}

小结:可以看到两种程序的框架都是差不多的。主要都是使用底层接收函数接收数据,然后将接收到的数据封装成pbuf,以供接下来上层协议的使用。

(6)low_level_output 相关内容整理

low_level_output 主要任务

  • 将要发送的信息装填到 ETH 的 DMA 发送描述符中
  • 将数据从pbuf从拷贝到 ETH 的 DMA Tx buffer 中
  • 最后通过 HAL_ETH_TransmitFrame 将数据从 Tx buffer 中发送出去
  • STM32CubeMX生成的lwIP的 low_level_output
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{err_t errval;struct pbuf *q;uint8_t *buffer = (uint8_t *)(heth.TxDesc->Buffer1Addr);__IO ETH_DMADescTypeDef *DmaTxDesc;uint32_t framelength = 0;uint32_t bufferoffset = 0;uint32_t byteslefttocopy = 0;uint32_t payloadoffset = 0;DmaTxDesc = heth.TxDesc;bufferoffset = 0;/* copy frame from pbufs to driver buffers */for(q = p; q != NULL; q = q->next){/* Is this buffer available? If not, goto error */if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET){errval = ERR_USE;goto error;}/* Get bytes in current lwIP buffer */byteslefttocopy = q->len;payloadoffset = 0;/* Check if the length of data to copy is bigger than Tx buffer size*/while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE ){/* Copy data to Tx buffer*/memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );/* Point to next descriptor */DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);/* Check if the buffer is available */if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET){errval = ERR_USE;goto error;}buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);bufferoffset = 0;}/* Copy the remaining bytes */memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );bufferoffset = bufferoffset + byteslefttocopy;framelength = framelength + byteslefttocopy;}/* Prepare transmit descriptors to give to DMA */ HAL_ETH_TransmitFrame(&heth, framelength);errval = ERR_OK;error:/* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */if ((heth.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET){/* Clear TUS ETHERNET DMA flag */heth.Instance->DMASR = ETH_DMASR_TUS;/* Resume DMA transmission*/heth.Instance->DMATPDR = 0;}return errval;
}
  • lwIP官网下载的示例代码
static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{struct ethernetif *ethernetif = netif->state;struct pbuf *q;initiate transfer();#if ETH_PAD_SIZEpbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endiffor (q = p; q != NULL; q = q->next) {/* Send the data from the pbuf to the interface, one pbuf at atime. The size of the data in each pbuf is kept in the ->lenvariable. */send data from(q->payload, q->len);}signal that packet should be sent();MIB2_STATS_NETIF_ADD(netif, ifoutoctets, p->tot_len);if (((u8_t *)p->payload)[0] & 1) {/* broadcast or multicast packet*/MIB2_STATS_NETIF_INC(netif, ifoutnucastpkts);} else {/* unicast packet */MIB2_STATS_NETIF_INC(netif, ifoutucastpkts);}/* increase ifoutdiscards or ifouterrors on error */#if ETH_PAD_SIZEpbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endifLINK_STATS_INC(link.xmit);return ERR_OK;
}

小结:可以看出大致程序框架是差不多的。主要的流程就是先将数据拷贝到ETH的发送buffer中,然后再通过HAL_ETH_TransmitFrame 来发送数据,最后再清理一下ETH的寄存器标志位。

三、总结

  从上面的相关内容整理可以得知,lwip的提供low_level_init、low_level_input、low_level_output 的接口函数,其本质上都是通过对STM32 eth 外设的初始化、输入、输出的封装并将数据整理成pbuf的形式来进行lwip中上下层中的数据传递。

  所以,在学习lwip的过程中,首先要熟悉STM32 eth 外设的相关内容,还有eth PHY的一些信息,包括寄存器的使用,以及相关标志位的判断!!!