• LWIP移植文件介绍


    在介绍文件之前首先介绍一下DMA描述符

    stm32以太网模块接收/发送FIFO和内存之间的以太网传输是通过以太网DMA使用DMA描述符完成的,一共有两个描述符列表:一个用于接收,一个用于发送, 两个列表的基址分别写入ETH_DMARDLAR 寄存器和 ETH_DMATDLAR 寄存器中。 

     1 typedef struct {
     2 __IO uint32_t Status; //状态
     3 uint32_t ControlBufferSize; //控制和 buffer1, buffer2 的长度
     4 uint32_t Buffer1Addr; //buffer1 地址
     5 uint32_t Buffer2NextDescAddr; //buffer2 地址或下一个描述符地址
     6 uint32_t ExtendedStatus; //增强描述符状态
     7 uint32_t Reserved1; //保留
     8 uint32_t TimeStampLow; //时间戳低位
     9 uint32_t TimeStampHigh; //时间戳高位
    10 } ETH_DMADESCTypeDef;

    根据DMA描述符的内容可以组成两种结构,而stm32以太网库提供的就是链接结构。即Buffer1Addr存放数据,Buffer2NextDescAddr指向下一个描述符的首地址

    uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE];      
    uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]
    上面就是在初始化DMA描述符链表使能用到,相当于二维数组,第一个代表描述符的数量,此时定义的是5,第二个代表描述符 
    Buffer1Addr 的分配空间的大小,此时定义1500

     

    1.lwipopts.h 

    用户自己定义使用的配置文件 可以对宏定义的开关选择打开或者关闭某些功能。系统提供的配置文件opt.h有大量的条件编译,所以还是不要动系统的默认配置。

    配置信息有:LWIP_UDP/LWIP_TCP 、系统、lwip的接口编程方式,接收缓冲区的大小,是否使用socket,是否需要打印debug消息等等。

    2.网卡驱动的编写:                                         LWIP协议栈只能通过pbuf和网卡通讯

    lwip的网卡驱动的移植其实就是对以下几个函数函数的编写与封装。lwip内核已经写好了框架在ethnetif.c中,该文件默认是没有编译进去的(指的是LWIP官网下载的安装包),我们需要自己填充这个文件

    1 static void low_level_init(struct netif *netif)
    2 static err_t low_level_output(struct netif *netif, struct pbuf *p)
    3 static struct pbuf * low_level_input(struct netif *netif)
    4 void ethernetif_input(struct netif *netif)
    5 err_t ethernetif_init(struct netif *netif)

    (1).网卡初始化函数,它主要包括mcu的片内外设

       ① eth的初始化(自协商模式是否开启,PHY的地址,网卡的MAC地址,接收模式是中断还是轮训,stm32官网开发文档写着中断模式只能用于系统,不带系统只能使用轮训模式,但是正点原子使用的demo是中断模式,我此次使用的是poll,是否开启硬件校验和PHY的通讯模式(MII/RMII))

               ② 将DMA发送和接收描述符初始化成链表结构 ,设置协议栈网络接口管理netif中国与网卡属性相关的特性,如设置MAC硬件地址,mac地址长度,最大传输单元,使能DMA发送和接收

    (2)网卡数据包发送函数 将内核数据结构体pbuf描述的数据包发送出去

    (3)网卡数据包接收函数,为了让内核更好的处理接收过来的数据,我们需要将接收到的数据封装成pbuf的形式

    (4)主要是调用low_level_input()实现从网卡读取一个数据包,及解析数据包的类型(ARP,IP)并提交给应用层,这个函数可直接被应用程序调用

    (5)在管理网络接口netif会调用,只要是对netif某些字段进行初始化,并调用low_level_init完成相关的初始化。

    下面分别讲解一下这几个函数:.

     void low_level_init(struct netif *netif)

    初始化了netif的hwaddr_len、hwaddr、mtu、flags,其他的上面已经很详细的介绍,这里不再赘叙

     1 static void low_level_init(struct netif *netif)
     2 { 
     3   uint32_t regvalue = 0;
     4   HAL_StatusTypeDef hal_eth_init_status;
     5   
     6 /* Init ETH */
     7 
     8    uint8_t MACAddr[6] ;
     9   heth.Instance = ETH;
    10   heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
    11   heth.Init.PhyAddress = LAN8742A_PHY_ADDRESS;
    12   MACAddr[0] = 0x02;
    13   MACAddr[1] = 0x00;
    14   MACAddr[2] = 0x00;
    15   MACAddr[3] = 0x00;
    16   MACAddr[4] = 0x00;
    17   MACAddr[5] = 0x00;
    18   heth.Init.MACAddr = &MACAddr[0];
    19   heth.Init.RxMode = ETH_RXPOLLING_MODE;
    20   heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
    21   heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
    22 
    23   /* USER CODE BEGIN MACADDRESS */
    24     
    25   /* USER CODE END MACADDRESS */
    26 
    27   hal_eth_init_status = HAL_ETH_Init(&heth);
    28 
    29   if (hal_eth_init_status == HAL_OK)
    30   {
    31     /* Set netif link flag */  
    32     netif->flags |= NETIF_FLAG_LINK_UP;
    33   }
    34   /* Initialize Tx Descriptors list: Chain Mode */
    35   HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
    36      
    37   /* Initialize Rx Descriptors list: Chain Mode  */
    38   HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);
    39  
    40 #if LWIP_ARP || LWIP_ETHERNET 
    41 
    42   /* set MAC hardware address length */
    43   netif->hwaddr_len = ETH_HWADDR_LEN;
    44   
    45   /* set MAC hardware address */
    46   netif->hwaddr[0] =  heth.Init.MACAddr[0];
    47   netif->hwaddr[1] =  heth.Init.MACAddr[1];
    48   netif->hwaddr[2] =  heth.Init.MACAddr[2];
    49   netif->hwaddr[3] =  heth.Init.MACAddr[3];
    50   netif->hwaddr[4] =  heth.Init.MACAddr[4];
    51   netif->hwaddr[5] =  heth.Init.MACAddr[5];
    52   
    53   /* maximum transfer unit */
    54   netif->mtu = 1500;
    55   
    56   /* Accept broadcast address and ARP traffic */
    57   /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
    58   #if LWIP_ARP
    59     netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
    60   #else 
    61     netif->flags |= NETIF_FLAG_BROADCAST;
    62   #endif /* LWIP_ARP */
    63   
    64   /* Enable MAC and DMA transmission and reception */
    65   HAL_ETH_Start(&heth);
    66 
    67 /* USER CODE BEGIN PHY_PRE_CONFIG */ 
    68     
    69 /* USER CODE END PHY_PRE_CONFIG */
    70   
    71 
    72   /* Read Register Configuration */
    73   HAL_ETH_ReadPHYRegister(&heth, PHY_ISFR, &regvalue);
    74   regvalue |= (PHY_ISFR_INT4);
    75 
    76   /* Enable Interrupt on change of link status */ 
    77   HAL_ETH_WritePHYRegister(&heth, PHY_ISFR , regvalue );
    78   
    79   /* Read Register Configuration */
    80   HAL_ETH_ReadPHYRegister(&heth, PHY_ISFR , &regvalue);
    81 
    82 
    83 #endif /* LWIP_ARP || LWIP_ETHERNET */
    84 
    85 
    86 }
    low_level_init

    low_level_output(struct netif *netif, struct pbuf *p)

    首先介绍一下pubuf结构体
    struct pbuf {
    struct pbuf *next; //指向下一个 pbuf 结构体,可以构成链表
    void *payload; //指向该 pbuf 真正的数据区
    u16_t tot_len; //当前 pbuf 和链表中后面所有 pbuf 的数据长度, 它们属于一个数据包
    u16_t len; //当前 pbuf 的数据长度
    u8_t type; //当前 pbuf 的类型   pbuf_ram / pbuf_pool
    u8_t flags; //状态为,保留
    u16_t ref; //该 pbuf 被引用的次数
    };

    PS:offset的值因为当前的pbuf处于不同的协议层而不同,如raw时 offset 为0 开启TCP时offset 为54

    本质:

    一个pbuf可能无法将所有的数据都发出去,所以一般将pbuf组成一个链表,这样数据就会在pbuf链表的payload区域,遍历pbuf链表将数据全部memcpy到eth的DMA发送描述符链表中,要注意三个地方,一个是offset区域,第二个是发送时要确保DMA发送描述符被ETH所有,不是被LWIP所有,第三个是当要发送数据大于DMA描述符链表所能发送的最大字节.

    具体代码实现如下:

    static err_t low_level_output(struct netif *netif, struct pbuf *p)
    {
    err_t errval;
    struct pbuf *q;
    uint8_t *buffer=(uint8_t *)(ETH_Handler.TxDesc->Buffer1Addr);
    __IO ETH_DMADescTypeDef *DmaTxDesc;
    uint32_t framelength = 0;
    uint32_t bufferoffset = 0;
    uint32_t byteslefttocopy = 0;
    uint32_t payloadoffset = 0;
    DmaTxDesc = ETH_Handler.TxDesc;
    bufferoffset = 0;
    //从 pbuf 中拷贝要发送的数据
    for(q=p;q!=NULL;q=q->next)
    {
    //判断此发送描述符是否有效,即判断此发送描述符是否归以太网 DMA 所有
    if((DmaTxDesc->Status&ETH_DMATXDESC_OWN)!=(uint32_t)RESET)
    {
    errval=ERR_USE;
    goto error; //发送描述符无效,不可用
    }
    byteslefttocopy=q->len; //要发送的数据长度
    payloadoffset=0;
    //将 pbuf 中要发送的数据写入到以太网发送描述符中,有时候我们要发送的
    //数据可能大于一个以太网描述符的 Tx Buffer,因此我们需要分多次将数据
    //拷贝到多个发送描述符中
    while((byteslefttocopy+bufferoffset)>ETH_TX_BUF_SIZE )
    {
    //将数据拷贝到以太网发送描述符的 Tx Buffer 中
    memcpy((uint8_t*)((uint8_t*)buffer+bufferoffset),(uint8_t*)((uint8_t*)q->payload
    +payloadoffset),(ETH_TX_BUF_SIZE-bufferoffset));
    //DmaTxDsc 指向下一个发送描述符
    DmaTxDesc=(ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
    //检查新的发送描述符是否有效
    if((DmaTxDesc->Status&ETH_DMATXDESC_OWN)!=(uint32_t)RESET)
    {
    errval = ERR_USE;
    goto error; //发送描述符无效,不可用
    }
    buffer=(uint8_t *)(DmaTxDesc->Buffer1Addr); //更新 buffer 地址,指向新
    //的发送描述符的 Tx Buffer
    byteslefttocopy=byteslefttocopy-(ETH_TX_BUF_SIZE-bufferoffset);
    payloadoffset=payloadoffset+(ETH_TX_BUF_SIZE-bufferoffset);
    framelength=framelength+(ETH_TX_BUF_SIZE-bufferoffset);
    bufferoffset=0;
    }
    //拷贝剩余的数据
    memcpy( (uint8_t*)((uint8_t*)buffer+bufferoffset),(uint8_t*)((uint8_t*)q->payload
    +payloadoffset),byteslefttocopy );
    bufferoffset=bufferoffset+byteslefttocopy;
    framelength=framelength+byteslefttocopy;
    }
    HAL_ETH_TransmitFrame(&ETH_Handler,framelength);
    errval = ERR_OK;
    error:
    //发送缓冲区发生下溢,一旦发送缓冲区发生下溢 TxDMA 会进入挂起状态
    if((ETH_Handler.Instance->DMASR&ETH_DMASR_TUS)!=(uint32_t)RESET)
    {
    //清除下溢标志
    ETH_Handler.Instance->DMASR = ETH_DMASR_TUS;
    //当发送帧中出现下溢错误的时候 TxDMA 会挂起,这时候需要向 DMATPDR 寄存器
    //随便写入一个值来将其唤醒,此处我们写 0
    ETH_Handler.Instance->DMATPDR=0;
    }
    return errval;
    }
    low_level_output_code
    struct pbuf * low_level_input(struct netif *netif)
    本质:
     将网卡eth的DMA描述符中的内容复制到pbuf链表中,供LWIP使用,过程是判断DMA描述符中是否有数据,获取数据长度len,为pbuf分配(offset+len)长度的空间,然后遍历DMA描述符链表进行复制,
    最后复制完成将DMA描述符链表清空,最后将DMA描述符还给网卡,标记后即可接受新的数据。
    具体代码实现如下:
     1 static struct pbuf * low_level_input(struct netif *netif)
     2 {
     3 struct pbuf *p = NULL;
     4 struct pbuf *q;
     5 uint16_t len;
     6 uint8_t *buffer;
     7 __IO ETH_DMADescTypeDef *dmarxdesc;
     8 uint32_t bufferoffset=0;
     9 uint32_t payloadoffset=0;
    10 uint32_t byteslefttocopy=0;
    11 uint32_t i=0;
    12 if(HAL_ETH_GetReceivedFrame(&ETH_Handler)!=HAL_OK) //判断是否接收到数据
    13 return NULL;
    14 len=ETH_Handler.RxFrameInfos.length; //获取接收到的以太网帧长度
    15 buffer=(uint8_t *)ETH_Handler.RxFrameInfos.buffer; //获取接收到的以太网帧的数据 buffer
    16 if(len>0) p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL); //申请 pbuf
    17 if(p!=NULL) //pbuf 申请成功
    18 {
    19 dmarxdesc=ETH_Handler.RxFrameInfos.FSRxDesc; //获取接收描述符链表中
    20 //的第一个描述符
    21 bufferoffset = 0;
    22 for(q=p;q!=NULL;q=q->next)
    23 {
    24 byteslefttocopy=q->len;
    25 payloadoffset=0;
    26 //将接收描述符中 Rx Buffer 的数据拷贝到 pbuf 中
    27 while((byteslefttocopy+bufferoffset)>ETH_RX_BUF_SIZE )
    28 {
    29 //将数据拷贝到 pbuf 中
    30 memcpy((uint8_t*)((uint8_t*)q->payload+payloadoffset),
    31 (uint8_t*)((uint8_t*)buffer+bufferoffset),(ETH_RX_BUF_SIZE-bufferoffset));
    32 //dmarxdesc 指向下一个接收描述符
    33 dmarxdesc=(ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
    34 //更新 buffer 地址,指向新的接收描述符的 Rx Buffer
    35 buffer=(uint8_t *)(dmarxdesc->Buffer1Addr);
    36 byteslefttocopy=byteslefttocopy-(ETH_RX_BUF_SIZE-bufferoffset);
    37 payloadoffset=payloadoffset+(ETH_RX_BUF_SIZE-bufferoffset);
    38 bufferoffset=0;
    39 }
    40 //拷贝剩余的数据
    41 memcpy((uint8_t*)((uint8_t*)q->payload+payloadoffset),
    42 (uint8_t*)((uint8_t*)buffer+bufferoffset),byteslefttocopy);
    43 bufferoffset=bufferoffset+byteslefttocopy;
    44 }
    45 }
    46 //释放 DMA 描述符
    47 dmarxdesc=ETH_Handler.RxFrameInfos.FSRxDesc;
    48 for(i=0;i<ETH_Handler.RxFrameInfos.SegCount; i++)
    49 {
    50 dmarxdesc->Status|=ETH_DMARXDESC_OWN; //标记描述符归 DMA 所有
    51 dmarxdesc=(ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
    52 }
    53 ETH_Handler.RxFrameInfos.SegCount =0; //清除段计数器
    54 //接收缓冲区不可用
    55 if((ETH_Handler.Instance->DMASR&ETH_DMASR_RBUS)!=(uint32_t)RESET)
    56 {
    57 //清除接收缓冲区不可用标志
    58 ETH_Handler.Instance->DMASR = ETH_DMASR_RBUS;
    59 //当接收缓冲区不可用的时候 RxDMA 会进去挂起状态,通过向 DMARPDR
    60 //写入任意一个值来唤醒 Rx DMA
    61 ETH_Handler.Instance->DMARPDR=0;
    62 }
    63 return p;
    64 }
    low_level_input_code
    
    
    void ethernetif_input(struct netif *netif)
    主要是对low_level_inpu()的封装,然后传入指定的网卡结构中
     1 err_t ethernetif_input(struct netif *netif)
     2 {
     3 err_t err;
     4 struct pbuf *p;
     5 p=low_level_input(netif); //调用 low_level_input 函数接收数据
     6 if(p==NULL) return ERR_MEM;
     7 err=netif->input(p, netif); //调用 netif 结构体中的 input 字段(一个函数)来处理数据包
     8 if(err!=ERR_OK)
     9 {
    10 LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error
    "));
    11 pbuf_free(p);
    12 p = NULL;
    13 }
    14 return err;
    15 }
    ethernetif_input
    err_t ethernetif_init(struct netif *netif)
    主要是对low_level_init()的封装,初始化了netif的相关字段,注册IP层发送函数
     1 err_t ethernetif_init(struct netif *netif)
     2 {
     3 LWIP_ASSERT("netif!=NULL",(netif!=NULL));
     4 #if LWIP_NETIF_HOSTNAME //LWIP_NETIF_HOSTNAME
     5 netif->hostname="lwip"; //初始化名称
     6 #endif
     7 netif->name[0]=IFNAME0; //初始化变量 netif 的 name 字段
     8 netif->name[1]=IFNAME1; //在文件外定义这里不用关心具体值
     9 netif->output=etharp_output;//IP 层发送数据包函数
    10 netif->linkoutput=low_level_output;//ARP 模块发送数据包函数
    11 low_level_init(netif); //底层硬件初始化函数
    12 return ERR_OK;
    13 }
    ethernetif_init

    3.系统时钟

     系统通过调用函数sys_check_timerouts来处理内核的各种定时时间比如ARP、TCP等,该函数要求移植者实现一个函数sys_now来返回当前的系统时间,通过差值判断是否时间到达,从而调用相关的函数处理定时事件。在本次移植中,用的hal库自带的tick(1ms)来实现,也可以用全局变量和定时器来实现

    1 u32_t sys_now(void)
    2 {
    3   return HAL_GetTick();
    4 }
     

    4.LWIP初始化过程

           lwip_init()      初始化LWIP内核
                ↓
         IP4_ADDR()         将ip/netmask/gw数值整合成一个ip4_addr_t变量
                ↓
        netif_add()         此函数主要设置ip/netmask/gw 和调用ethernetif_init函数,当有消息来的时候调用ethernet_input。这两个函数会在后面文章介绍,一种网卡设备添加一次
                ↓
    netif_set_default()     将此网卡设置为默认网口
                ↓
        netif_set_up()      开启网口,在添加网口设备后就会将NETIF_IS_LINK_UP_FLAG 置1,后面会详细讲解 netif_set_link_up和netif_set_up的区别

     因为此次是用的是poll模式,所以要在主循环调用ethnetif_input()和sys_sys_check_timerouts(); 确保内核是工作的。完成这一步,板子就能够ping通了

    ************************************************************************************************

    参考文章:

    正点原子的《STM32F7 LWIP开发手册》

    《嵌入式网络那些事:LwIP协议栈深度剖析与演练》

  • 相关阅读:
    Exercice_3.8
    Exercice_3.13.1_练习使用vector2
    Exercice_3.13_练习使用vetor
    Exercice_3.10_去掉string对象中的标点符号
    Exercice_3.7_判断两个字符串的大小和长度
    1-日期时间视图 2-长按事件
    View(视图)2
    View(视图)
    计算器(UI事件)给按钮添加监听器
    Activity(活动)
  • 原文地址:https://www.cnblogs.com/st-home/p/10899214.html
Copyright © 2020-2023  润新知