STM32的以太网外设+PHY(LAN8720)使用详解(6):以太网数据接收及发送

19 篇文章 1 订阅
17 篇文章 47 订阅

0 工具准备

1.野火 stm32f407霸天虎开发板
2.LAN8720数据手册
3.STM32F4xx中文参考手册

1 以太网数据接收及发送

1.1 以太网数据接收(轮询)

1.1.1 检查是否接收到一帧完整报文

使用轮询的方式接收以太网数据是一种简单但是效率低下的方法,为了保证及时处理以太网数据我们需要在主循环内高频轮询是否接收到了以太网数据。轮询的函数为ETH_CheckFrameReceived,内容如下:

uint32_t ETH_CheckFrameReceived(void)
{
  /* check if last segment */
  if(((DMARxDescToGet->Status & ETH_DMARxDesc_OWN) == (uint32_t)RESET) &&
     ((DMARxDescToGet->Status & ETH_DMARxDesc_LS) != (uint32_t)RESET)) 
  {
    DMA_RX_FRAME_infos->Seg_Count++;
    if (DMA_RX_FRAME_infos->Seg_Count == 1)
    {
      DMA_RX_FRAME_infos->FS_Rx_Desc = DMARxDescToGet;
    }
    DMA_RX_FRAME_infos->LS_Rx_Desc = DMARxDescToGet;
    return 1;
  }

  /* check if first segment */
  else if(((DMARxDescToGet->Status & ETH_DMARxDesc_OWN) == (uint32_t)RESET) &&
          ((DMARxDescToGet->Status & ETH_DMARxDesc_FS) != (uint32_t)RESET)&&
            ((DMARxDescToGet->Status & ETH_DMARxDesc_LS) == (uint32_t)RESET))
  {
    DMA_RX_FRAME_infos->FS_Rx_Desc = DMARxDescToGet;
    DMA_RX_FRAME_infos->LS_Rx_Desc = NULL;
    DMA_RX_FRAME_infos->Seg_Count = 1;   
    DMARxDescToGet = (ETH_DMADESCTypeDef*) (DMARxDescToGet->Buffer2NextDescAddr);
  }

  /* check if intermediate segment */ 
  else if(((DMARxDescToGet->Status & ETH_DMARxDesc_OWN) == (uint32_t)RESET) &&
          ((DMARxDescToGet->Status & ETH_DMARxDesc_FS) == (uint32_t)RESET)&&
            ((DMARxDescToGet->Status & ETH_DMARxDesc_LS) == (uint32_t)RESET))
  {
    (DMA_RX_FRAME_infos->Seg_Count) ++;
    DMARxDescToGet = (ETH_DMADESCTypeDef*) (DMARxDescToGet->Buffer2NextDescAddr);
  } 
  return 0;
}

当以太网帧大于我们设置的DMA描述符buffer大小时,以太网帧将会被分成若干段被存储在不同的DMA描述符中,DMA描述符使用接收描述符字0来表示当前DMA描述符是第一个描述符或最后一个描述符或中间描述符:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当DMA描述符是首个描述符时将段计数置为1,保存首个描述符到FS_Rx_Desc同时将Rx描述符指向下一个DMA描述符;当DMA描述符是中间描述符时将段计数+1,同时将Rx描述符指向下一个DMA描述符;当DMA描述符是最后一个描述符时将段计数+1,保存最后一个描述符到LS_Rx_Desc(如果段计数为1也就是一个完整的以太网帧被保存在一个DMA描述符内,保存最后一个描述符到FS_Rx_Desc)同时返回1表明接收到了一帧完整以太网数据。

1.1.2 读取一帧完整报文

在我们检查到接收了一帧完整报文后,就可以调用low_level_input函数读取该帧报文。

FrameTypeDef low_level_input(void)
{
    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();

    /* Obtain the size of the packet and put it into the "len" variable. 获取数据包大小 */
    len = frame.length;
    buffer = (u8 *)frame.buffer;

    /* Release descriptors to DMA 将描述符释放到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 恢复DMA接收 */
        ETH->DMARPDR = 0;
    }
    return frame;
}

该函数操作流程如下:
(1)获取报文长度
调用ETH_Get_Received_Frame函数会返回以太网帧最后一个描述符存储的报文长度和buffer地址。我们可以将DMA描述符buffer数据拷贝到协议栈buffer中。
(2)释放DMA控制权给DMA
在我们拷贝完了DMA描述符的buffer数据后需要释放DMA控制权,相关语句如下:

for (i = 0; i < DMA_RX_FRAME_infos->Seg_Count; i++)
    {
        DMARxDesc->Status = ETH_DMARxDesc_OWN;
        DMARxDesc = (ETH_DMADESCTypeDef *)(DMARxDesc->Buffer2NextDescAddr);
    }
    DMA_RX_FRAME_infos->Seg_Count = 0;

上述语句将首个DMA描述符到最后一个DMA描述符的控制权交给DMA,最后清空段计数。
(3)检查DMA状态寄存器
涉及到寄存器如下:
在这里插入图片描述
相关bit描述:
在这里插入图片描述
这里检查位7是否为1,如果为1则设置DMASR寄存器的值为0x00000080,然后恢复DMA接收。相关语句如下:

if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)
    {
        /* Clear RBUS ETHERNET DMA flag */
        ETH->DMASR = ETH_DMASR_RBUS;
        /* Resume DMA reception 恢复DMA接收 */
        ETH->DMARPDR = 0;
    }

DMARPDR寄存器描述如下:
在这里插入图片描述

1.2 以太网数据接收(中断)

在主循环或者线程内使用轮询的方式判断是否接收到以太网报文效率比较低下,而且容易出现未及时处理接收报文导致溢出的问题。因此,建议使能ETH接收中断,在中断内释放信号量然后处理以太网数据。
打开ETH接收中断语句如下:

NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn;  // 以太网中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0X00; // 中断寄存器组2最高优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0X00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
if (EthStatus == ETH_SUCCESS)
{
    /* 使能接收中断 */
    ETH_DMAITConfig(ETH_DMA_IT_NIS | ETH_DMA_IT_R, ENABLE);
}

使能接收中断涉及的寄存器如下:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
接收中断服务函数如下:

void ETH_IRQHandler(void)
{
    int i;
    FrameTypeDef frame;
    while(ETH_CheckFrameReceived() != 0) // 检测是否收到数据包
    {
        frame = low_level_input();
        printf("Len : %d\r\n", frame.length);
        for (i = 0; i < frame.length; i++)
        {
            printf("%02X ", ((u8 *)frame.buffer)[i]);
        }
        printf("\r\n");
    }
    ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
    ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
} 

(1)特别要注意我们这里使用的是while而不是if,因为每次触发接收中断可能接收到了多个报文,我们应该尽快将报文全部取出,避免DMA描述符占用标志一直是CPU。
(2)上面的中断服务函数只是用于演示,我们直接在中断内打印接收到的报文。正常操作是释放信号量或者将接收标志置位,通知RTOS的接收线程或者裸机下的主循环内的回调函数处理。

1.3 以太网数据发送

uint8_t low_level_output(uint8_t *sendBuffer, uint16_t len)
{
    uint8_t errval;
    struct pbuf *q;
    u8 *buffer = (u8 *)(DMATxDescToSet->Buffer1Addr);
    __IO ETH_DMADESCTypeDef *DmaTxDesc;
    uint16_t framelength = 0;
    uint32_t bufferoffset = 0;
    uint32_t byteslefttocopy = 0;
    uint32_t payloadoffset = 0;

    DmaTxDesc = DMATxDescToSet;
    bufferoffset = 0;

    memcpy((u8_t *)buffer, (u8_t *)sendBuffer, len);

    /* Prepare transmit descriptors to give to DMA 准备发送描述符给DMA使用 */
    ETH_Prepare_Transmit_Descriptors(len);

    errval = 0;

error:
    errval = -1;
    /* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
    if ((ETH->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
    {
        /* Clear TUS ETHERNET DMA flag */
        ETH->DMASR = ETH_DMASR_TUS;

        /* Resume DMA transmission*/
        ETH->DMATPDR = 0;
    }
    return errval;
}

相比起接收,以太网数据发送则显得比较简单,因为DMA描述符的主动操作方在CPU这一侧。上述函数的操作如下:
(1)将待发送数据拷贝到当前跟踪的发送DMA描述符
(2)将跟踪的发送DMA描述符控制权交给DMA,设置DMA描述符相关状态:

uint32_t ETH_Prepare_Transmit_Descriptors(u16 FrameLength)
{   
  uint32_t buf_count =0, size=0,i=0;
  __IO ETH_DMADESCTypeDef *DMATxDesc;

  /* Check if the descriptor is owned by the ETHERNET DMA (when set) or CPU (when reset) */
  if((DMATxDescToSet->Status & ETH_DMATxDesc_OWN) != (u32)RESET)
  {  
    /* Return ERROR: OWN bit set */
    return ETH_ERROR;
  }

  DMATxDesc = DMATxDescToSet;
  
  if (FrameLength > ETH_TX_BUF_SIZE)
  {
    buf_count = FrameLength/ETH_TX_BUF_SIZE;
    if (FrameLength%ETH_TX_BUF_SIZE) buf_count++;
  }
  else buf_count =1;

  if (buf_count ==1)
  {
    /*set LAST and FIRST segment */
    DMATxDesc->Status |=ETH_DMATxDesc_FS|ETH_DMATxDesc_LS;
    /* Set frame size */
    DMATxDesc->ControlBufferSize = (FrameLength & ETH_DMATxDesc_TBS1);
    /* Set Own bit of the Tx descriptor Status: gives the buffer back to ETHERNET DMA */
    DMATxDesc->Status |= ETH_DMATxDesc_OWN;
    DMATxDesc= (ETH_DMADESCTypeDef *)(DMATxDesc->Buffer2NextDescAddr);
  }
  else
  {
    for (i=0; i< buf_count; i++)
    {
      /* Clear FIRST and LAST segment bits */
      DMATxDesc->Status &= ~(ETH_DMATxDesc_FS | ETH_DMATxDesc_LS);
      
      if (i==0) 
      {
        /* Setting the first segment bit */
        DMATxDesc->Status |= ETH_DMATxDesc_FS;  
      }

      /* Program size */
      DMATxDesc->ControlBufferSize = (ETH_TX_BUF_SIZE & ETH_DMATxDesc_TBS1);
      
      if (i== (buf_count-1))
      {
        /* Setting the last segment bit */
        DMATxDesc->Status |= ETH_DMATxDesc_LS;
        size = FrameLength - (buf_count-1)*ETH_TX_BUF_SIZE;
        DMATxDesc->ControlBufferSize = (size & ETH_DMATxDesc_TBS1);
      }

      /* Set Own bit of the Tx descriptor Status: gives the buffer back to ETHERNET DMA */
      DMATxDesc->Status |= ETH_DMATxDesc_OWN;

      DMATxDesc = (ETH_DMADESCTypeDef *)(DMATxDesc->Buffer2NextDescAddr);
    }
  }
  
  DMATxDescToSet = DMATxDesc;

  /* When Tx Buffer unavailable flag is set: clear it and resume transmission */
  if ((ETH->DMASR & ETH_DMASR_TBUS) != (u32)RESET)
  {
    /* Clear TBUS ETHERNET DMA flag */
    ETH->DMASR = ETH_DMASR_TBUS;
    /* Resume DMA transmission*/
    ETH->DMATPDR = 0;
  }

  /* Return SUCCESS */
  return ETH_SUCCESS;   
}

这个函数篇幅有点长,这里举例说一下当我们发送的以太网报文可以被一个发送DMA描述符容纳时的操作:
(2.1)设置发送DMA描述符的LS和FS位为1,也就是一个发送DMA描述符对应一个以太网报文:

DMATxDesc->Status |=ETH_DMATxDesc_FS|ETH_DMATxDesc_LS;

在这里插入图片描述
在这里插入图片描述
(2.2)设置报文长度:

DMATxDesc->ControlBufferSize = (FrameLength & ETH_DMATxDesc_TBS1);

在这里插入图片描述
(2.3)设置发送DMA描述符控制权为DMA

DMATxDesc->Status |= ETH_DMATxDesc_OWN)

(2.4)将跟踪发送DMA描述符指向下一个发送DMA描述符
(3)当Tx Buffer不可用标志被设置时,清除该标志并恢复传输
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里的DMA就相当于头指针,而CPU则相当于尾指针。

2 总结

(1)以太网数据接收可以使用轮询和中断2种方式,建议使用中断方式在中断内释放信号量通知以太网报文接收线程进行处理
(2)发送DMA描述符运作方式类似于环形buffer,CPU是尾指针,DMA是头指针;接收DMA描述符运作方式类似于环形buffer,CPU是头指针,DMA是尾指针
(3)在接收以太网数据时一定要及时取出DMA描述符中的数据,将控制权交还给DMA,避免报文阻塞
(4)在发送以太网数据时一定要及时清除Tx Buffer标志并恢复发送

使用STM32CubeMX、LAN8720、LwIP和FreeRTOS实现网络通信,需要注意以下几点。 首先,STM32CubeMX是一个图形化配置工具,用于为STM32微控制器生成初始化的代码框架。我们可以通过选择所需的外设(如以太网控制器)和配置参数来生成代码。 其次,LAN8720是一个用于实现以太网通信的PHY(物理层)芯片,负责将数据从媒介访问控制层(MAC层)转换为物理传输信号。 接下来,LwIP(Lightweight IP)是一个轻量级的网络协议栈,用于实现TCP/IP协议。我们需要将LwIP集成到项目中,并配置好网络参数,如IP地址、子网掩码和网关。 最后,FreeRTOS是一个流行的实时操作系统,用于管理任务调度和资源管理。我们可以将网络通信任务添加到FreeRTOS的任务列表中,并通过队列和信号量等机制进行任务间通信和同步。 总体实现步骤如下: 1. 使用STM32CubeMX选择并配置以太网控制器和PHY,并生成初始化代码。 2. 配置LwIP的网络参数,如IP地址、子网掩码和网关。 3. 将LwIP集成到项目中,包括源代码和相应的头文件。 4. 添加网络通信任务到FreeRTOS的任务列表中。 5. 在网络任务中,使用LwIP提供的API进行网络初始化、连接设置以及数据收发等操作。 6. 通过使用队列或信号量等机制,实现不同任务间的数据共享和同步。 7. 在主函数中初始化FreeRTOS,并启动任务调度器。 通过以上步骤,我们可以利用STM32CubeMX、LAN8720、LwIP和FreeRTOS实现网络通信功能。这样我们就可以在STM32微控制器上实现网络连接、数据传输等网络应用。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NW嵌入式开发

感谢您的支持,让我们一起进步!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值