STM32移植lwip之官方源码解析

本篇目标:分析stm32的ETH(MAC控制器)初始化及lwip是如何与stm32底层连接的

材料准备:


ETH初始化

按照程序的思路,从main函数开始看:

int main(void)
{
  /* 配置中断优先级为4位,16个抢占级优先级,基于STM32库函数 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

  /* configure ethernet (GPIOs, clocks, MAC, DMA) */
  /* 配置有关以太网接口的控制器(包括IO口,时钟,MAC,DMA)
  基于STM32_ETH库函数 */
  ETH_BSP_Config();

  /* Initilaize the LwIP stack */
  /* lwip初始化函数,基于lwip协议栈 */
  LwIP_Init();

  /* Infinite loop */
  /* 主循环 */
  while (1)
  {  
    /* check if any packet received */
    /* 判断是否接收到数据,基于STM32_ETH库函数 */
    if (ETH_CheckFrameReceived())
    { 
      /* process received ethernet packet*/
      /* 接受缓存的数据,发送到lwip层进行处理,基于lwip协议栈 */
      LwIP_Pkt_Handle();
    }
    /* handle periodic timers for LwIP*/
    /* 轮询函数,定期询问lwip的一些状态 */
    LwIP_Periodic_Handle(LocalTime);
  } 
}

函数总结:main函数没有太多的注释,主要就是包含了很多内部函数的调用,包括stm32寄存器的初始化,lwip协议栈的初始化,以及循环while(1)中对接受数据的判断等。


进入 ETH_BSP_Config() 函数:

void ETH_BSP_Config(void)
{
  RCC_ClocksTypeDef RCC_Clocks;

  /***************************************************************************
    NOTE: 
         When using Systick to manage the delay in Ethernet driver, the Systick
         must be configured before Ethernet initialization and, the interrupt 
         priority should be the highest one.
    摘要 :
         当使用系统滴答定时器时钟来驱动以太网设备,系统滴答定时器时钟必须在以太网初始化之前配置,而且中断优先级必须是最高的。
      *****************************************************************************/

  /* Configure Systick clock source as HCLK */
  /* 配置滴答定时器时钟源为HCLK,因为之前没有对时钟进行任何配置,所以  
  频率为内部高速时钟HSI为8MHz */
  SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);

  /* SystTick configuration: an interrupt every 10ms */
  /* 读取时钟信息 */
  RCC_GetClocksFreq(&RCC_Clocks);
  /* 计算滴答定时器计数值以配置定时器,计数值/HCLK = 10/1000,即计数
  值 = HCLK/100 ,滴答定时器中断时间为10ms*/
  SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);

  /* Set Systick interrupt priority to 0*/
  /* 配置滴答定时器中断优先级为抢占优先级0 */
  NVIC_SetPriority (SysTick_IRQn, 0);

  /* Configure the GPIO ports for ethernet pins */
  /* 以太网PHY相关引脚配置 */
  ETH_GPIO_Config();

  /* Configure the Ethernet MAC/DMA */
  /* 配置以太网MAC和DMA相关寄存器 */
  ETH_MACDMA_Config();

  /* Get Ethernet link status*/iwang
  /* 获取以太网芯片的连接状态 
   * EthStatus变量要获取两个状态,一个是连接状态 ETH_LINK_FLAG,另
   * 一个是以太网初始化成功状态 ETH_INIT_FLAG ;必须在这两个状态都
   * 完成后,才能使lwip正常工作
   */
  if(ETH_ReadPHYRegister(DP83848_PHY_ADDRESS, PHY_SR) & 1)
  {
    EthStatus |= ETH_LINK_FLAG;
  }

  /* Configure the PHY to generate an interrupt on change of link status */
  /* 配置PHY在改变连接状态时,产生中断 */
  Eth_Link_PHYITConfig(DP83848_PHY_ADDRESS);

  /* Configure the EXTI for Ethernet link status. */
  /* 配置以太网连接的退出 */
  Eth_Link_EXTIConfig(); 
}

上面都是一些对函数基本功能的注释,这里挑出重要的函数来做一个总结:

  1. ETH_GPIO_Config() 函数:连接PHY芯片的相关GPIO口初始化,STM32F4系列GPIO口初始化与STM32F1系列有点区别,需要注意一下,初始化顺序包括
    (1)使能相关外设时钟,包括GPIOA、GPIOB、GPIOC、SYSCFG;
    (2)MCO引脚输出时钟并配置接口为RMII模式;
    (3)复用相关GPIO口;
  2. ETH_MACDMA_Config() 函数:函数中主要就是对结构体变量
    ETH_InitStructure的值进行初始化,其中包括相关MAC和DMA的参数;最主要的还是最后一个函数 ETH_Init() ,这个函数中对PHY芯片进行了所有需要的配置,包括全双工通讯和100M网卡等等。
  3. PHY物理地址确定:在官方代码中PHY的物理地址有一个宏定义为
    #define DP83848_PHY_ADDRESS 0x00
    这个物理地址怎么确定,网上关于lan9303的物理地址讲解的比较少,而dp83848比较多,可以度娘,因为硬件搭建的是lan9303,所以这里就分析一下lan9303的物理地址:
    在lan9303数据手册里面找到关于 PHYADDR_LED5P 引脚的说明,这个引脚确定初始的物理地址;当reset之后,这个引脚为低电平则虚拟PHY物理地址为0,反之则为1,由于硬件搭建的是下拉,所以是低电平,则把物理地址的宏定义改成了0x00。(关于 PHYADDR_LED5P 引脚,数据手册还有很多的介绍,慢慢挖掘吧)

上面也就是简单浅层次提了ETH的初始化以及对PHY芯片的配置过程,现在来到lwip协议栈层,来挖掘lwip是怎么与STM32低层数据进行传递处理的。

lwip的底层连接

就像之前刚刚拿到lwip程序的自己,不能一下子就找到重要的底层函数,那就从main函数的 while 循环开始,循环肯定做了对数据的查询和处理。

 while (1)
  {  
    /* 看到下面这个函数,就有点头绪了,从函数名字分析可以确定,这个函
        数是用来判断是否有接收到数据,进入函数可以发现里面就有很多DMA
        接收结构体的处理 */
    if (ETH_CheckFrameReceived())
    { 
      /* 下面的这个函数肯定包含了对数据的接收 */
      LwIP_Pkt_Handle();
    }
    LwIP_Periodic_Handle(LocalTime);
  } 

进入LwIP_Pkt_Handle()函数:发现只调用了一个函数ethernetif_input();

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

  /* move received packet into a new pbuf */
  /* 将接收的数据存到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 */
  /* LwIP 协议栈入口点,即将接收到的数据放入lwip,lwip会对数据进行一
  系列的处理,所以称为入口点 */
  err = netif->input(p, netif);

  if (err != ERR_OK)
  {
    LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
    pbuf_free(p);
  }
  return err;
}

上面还是没有看到最底层的数据接收,那就再进入low_level_input()函数:

static struct pbuf * low_level_input(struct netif *netif)
{
  struct pbuf *p, *q;
  uint32_t len;
  FrameTypeDef frame;
  u8 *buffer;
  __IO ETH_DMADESCTypeDef *DMARxDesc;
  uint32_t bufferoffset = 0;
  uint32_t payloadoffset = 0;
  uint32_t byteslefttocopy = 0;
  uint32_t i=0;  

  /* get received frame */
  /* 获取接收数据,进入这个函数就可以发现数据是在这个函数里面接收的 */
  frame = ETH_Get_Received_Frame();

  len = frame.length;
  buffer = (u8 *)frame.buffer;

  /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
  /* 分配内存 */
  p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);

  if (p != NULL)
  {
    DMARxDesc = frame.descriptor;
    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*/
      /* 比较接收数据的长度, 若大于buffer的长度,则进行分批依次处理
      byteslefttocopy为剩余数据的长度
      bufferoffset为上一组最后一个比buffer长度短的数据的长度 */
      while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
      {
        /* Copy data to pbuf*/
        /* 拷贝buffer指向的内存地址(代表为接收数据存放的地址)到q指
        向的内存地址 */
        memcpy( (u8_t*)((u8_t*)q->payload + payloadoffset), (u8_t*)((u8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));

        /* Point to next descriptor */
        /* 指向下一个数据 */
        DMARxDesc = (ETH_DMADESCTypeDef *)(DMARxDesc->Buffer2NextDescAddr);
        buffer = (unsigned char *)(DMARxDesc->Buffer1Addr);

        byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
        payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
        bufferoffset = 0;
      }
      /* Copy remaining data in pbuf */
      /* 最后一个小于buffer长度的数据拷贝 */
      memcpy( (u8_t*)((u8_t*)q->payload + payloadoffset), (u8_t*)((u8_t*)buffer + bufferoffset), byteslefttocopy);
      bufferoffset = bufferoffset + byteslefttocopy;
    }
  }

  /* 下面就是清除标志位,重新启动DMA的一些配置,与串口相似 */
  /* Release descriptors to DMA */
  DMARxDesc =frame.descriptor;

  /* Set Own bit in Rx descriptors: gives the buffers back to DMA */
  for (i=0; i<DMA_RX_FRAME_infos->Seg_Count; i++)
  {  
    DMARxDesc->Status = ETH_DMARxDesc_OWN;
    DMARxDesc = (ETH_DMADESCTypeDef *)(DMARxDesc->Buffer2NextDescAddr);
  }

  /* Clear Segment_Count */
  DMA_RX_FRAME_infos->Seg_Count =0;

  /* When Rx Buffer unavailable flag is set: clear it and resume reception */
  if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)  
  {
    /* Clear RBUS ETHERNET DMA flag */
    ETH->DMASR = ETH_DMASR_RBUS;
    /* Resume DMA reception */
    ETH->DMARPDR = 0;
  }
  return p;
}

接收的过程过程已经略知12了,那找找发送的:直接找到 ethernetif.c 文件中的 low_level_output 函数;

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
  /* ···省略一段不重要的程序 */

  /* copy frame from pbufs to driver buffers */
  /* 拷贝数据到发送的buffer */
  for(q = p; q != NULL; q = q->next)
    {
      /* Is this buffer available? If not, goto error */
      /* buffer是非准备好可用,如果没有,那就会报错 */
      if((DmaTxDesc->Status & ETH_DMATxDesc_OWN) != (u32)RESET)
      {
        errval = ERR_BUF;
        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*/
      /* 比较发送数据是否比发送buffer长度大,是则需要进行分批处理 */
      while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
      {
        /* Copy data to Tx buffer*/
        /* 拷贝数据到发送buffer */
        memcpy( (u8_t*)((u8_t*)buffer + bufferoffset), (u8_t*)((u8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );

        /* Point to next descriptor */
        /* 指向下一个数据 */
        DmaTxDesc = (ETH_DMADESCTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);

        /* Check if the buffer is available */
        /* 重新确认buffer可用 */
        if((DmaTxDesc->Status & ETH_DMATxDesc_OWN) != (u32)RESET)
        {
          errval = ERR_USE;
          goto error;
        }

        buffer = (u8 *)(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( (u8_t*)((u8_t*)buffer + bufferoffset), (u8_t*)((u8_t*)q->payload + payloadoffset), byteslefttocopy );
      bufferoffset = bufferoffset + byteslefttocopy;
      framelength = framelength + byteslefttocopy;
    }

  ETH_Prepare_Transmit_Descriptors(framelength);

  errval = ERR_OK;

  /* ···省略一段不重要的程序 */
}

可以看出发送函数 low_level_output() 和接收 low_level_input() 相辅相成,作为与stm32低层连接的两个重要的函数,那剩下还有两个次要的函数,找到 ethernetif.c 中的 ethernetif_init() 与 low_level_init():

err_t ethernetif_init(struct netif *netif)
{
  LWIP_ASSERT("netif != NULL", (netif != NULL));

  netif->name[0] = IFNAME0;
  netif->name[1] = IFNAME1;

  /* 网络结构体netif指向了两个函数,而两个函数最终调用的都是发送函数
  low_level_output() ,所以在lwip协议栈里面处理完数据后,会通过netif
  网络结构体进行调用发送函数*/
  netif->output = etharp_output;
  netif->linkoutput = low_level_output;

  /* initialize the hardware */
  /* 初始化硬件 */
  low_level_init(netif);

  return ERR_OK;
}
static void low_level_init(struct netif *netif)
{
#ifdef CHECKSUM_BY_HARDWARE
  int i; 
#endif
  /* set MAC hardware address length */
  /* 配置MAC物理地址长度 */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* set MAC hardware address */
  /* 配置MAC物理地址 */
  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 */ 
  /* 将MAC地址写入MAC控制器 */
  ETH_MACAddressConfig(ETH_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_FLAG_ETHARP ,这里将器件配置开启广播功能和使用ARP */
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;

  /* Initialize Tx Descriptors list: Chain Mode */
  /* 配置Tx为连续模式 */
  ETH_DMATxDescChainInit(DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
  /* Initialize Rx Descriptors list: Chain Mode  */
  /* 配置Rx为连续模式 */
  ETH_DMARxDescChainInit(DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);

#ifdef CHECKSUM_BY_HARDWARE
  /* Enable the TCP, UDP and ICMP checksum insertion for the Tx frames */
  /* 使能TCP/UDP/ICMP校验 */
  for(i=0; i<ETH_TXBUFNB; i++)
    {
      ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
    }
#endif

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

  /* Enable MAC and DMA transmission and reception */
  /* 开启MAC转换和DMA接收 */
  ETH_Start();

}

小结:上面整理了这么多,看起来也有点累,用一张图来捋一捋

这里写图片描述


ps:代码的解析远远比移植困难的多,花了几天时间看了很多网上大神关于移植lwip和lwip源码的解析,然后就照着自己的理解粗略的描述了一些自己认为比较重要的几个点,当然肯定见解不周,希望自己以后学的更深刻的时候再一点一点修改不当的地方,共勉~

猜你喜欢

转载自blog.csdn.net/q361750389/article/details/52788005