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

这篇具有很好参考价值的文章主要介绍了STM32的以太网外设+PHY(LAN8720)使用详解(6):以太网数据接收及发送。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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描述符是第一个描述符或最后一个描述符或中间描述符:
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
当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状态寄存器
涉及到寄存器如下:
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
相关bit描述:
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
这里检查位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寄存器描述如下:
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY

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);
}

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

stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY

stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
接收中断服务函数如下:

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;

stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
(2.2)设置报文长度:

DMATxDesc->ControlBufferSize = (FrameLength & ETH_DMATxDesc_TBS1);

stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
(2.3)设置发送DMA描述符控制权为DMA

DMATxDesc->Status |= ETH_DMATxDesc_OWN)

(2.4)将跟踪发送DMA描述符指向下一个发送DMA描述符
(3)当Tx Buffer不可用标志被设置时,清除该标志并恢复传输
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
stm32f407 lan8720,以太网,单片机开发,RTOS,stm32,MAC,ETH,LAN8720,PHY
这里的DMA就相当于头指针,而CPU则相当于尾指针。

2 总结

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

到了这里,关于STM32的以太网外设+PHY(LAN8720)使用详解(6):以太网数据接收及发送的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【STM32】HAL库-以太网外设-LAN8720A-LWIP-无操作系统

    KEIL:MDK_ARM_5.27 MCU:STM32F429IGT6 PHY_IC:LAN8720A LWIP:LWIP2.1.2 STM32CUBEMX:6.6.1 HAL:V1.27.1 LAN8720A使用RMII接口与STM32的ETH外设进行数据通信 STM32使用SMI接口读/写LAN8720A的寄存器 LAN8720A由外部25MHz晶振提供时钟,LED2/NINTSEL引脚配置为下拉,故PHY(LAN8720A)提供50MHz时钟给RMII的NINT/REFCLKO(此时引脚

    2024年02月08日
    浏览(37)
  • ESP32以太网(ETH)环境和参数配置,基于内部mac和外部PHY(LAN8720A)

    ESP32在网上的资料很多,但问题也各式各样。由于ESP32主要做wifi功能,因此对于以太网的资料也很少。相对应的开发板也很少。 本人使用的是淘宝上购买的雨甄机电的带网口的开发板(如下图) 安信可ESP-32S 参考资料主要以热心博主《兴趣使然_ 》的 (5条消息) ESP32 单片机学习

    2024年02月15日
    浏览(34)
  • 【ETH】以太网----PHY芯片LAN8720A----电路原理图

    LAN8720A 是低功耗的 10/100M 以太网 PHY 层芯片,I/0 引脚电压符合EEE802.3-2005 标准,支持通过 RMI 接口与以太网 MAC 层通信,内置 10-BASE-T/100BASE-TX 全双工传输模块,支持 10Mbps 和 100Mbps。 LAN8720A 可以通过自协商的方式与目的主机最佳的连接方式(速度和双工模式),支持 HPAuto-MDIX 自动

    2024年02月06日
    浏览(75)
  • STM32CubeMX+STM32F407+FreeRTos+LAN8720 以太网通信实现数据收发功能

    目录 前言 一、STM32CubeMX配置 二、修改代码 三、硬件测试 总结 该工程应用的以太网芯片是LAN8720,代码是基于STM32CUbeMx6.2.1配置生成的,在CubeMx中配置了ETH和LWIP,还有串口1和FREERTOS,最后通过创建任务函数实现udp的以太网数据收发功能。在测试中,可以在电脑的DOS窗口ping通在

    2024年02月08日
    浏览(48)
  • 关于STM32F4和GD32F4以太网,LAN8720+lwip+freemodbus,实现modbus tcp

    关于STM32F4和GD32F4以太网,LAN8720+lwip+freemodbus 这里使用了大佬 小灰灰搞电子 的代码,文章看 STM32F407+LAN8720移植Lwip和freeModbus实现MODBUS TCP 代码看 STM32F407+LAN8720+LWIP移植freemodbus TCP.zip 他的代码是基于正点原子F407的板子开发的,如果是别的板子,需要修改引脚 小灰灰的代码里,没

    2024年02月14日
    浏览(26)
  • GD32F450以太网(2-2): PHY芯片IP101GR介绍

    接上文 《GD32F450以太网(1):ETH 外设接口简介》介绍了嵌入式以太网接口。 《GD32F450以太网(2-1):PHY芯片LAN8720A介绍》介绍了LAN8720A 本文介绍另外一款PHY芯片IP101GR,支持MII接口或RMII接口,可代替市场上LAN8710A/LAN8720A/KSZ8041等芯片。 IP101是台湾省九阳电子公司生产制造的,符合IEEE

    2023年04月08日
    浏览(101)
  • STM32以太网接口的配置和使用方法详解

    STM32 微控制器提供了多种系列和型号,不同型号的芯片可能有不同的以太网接口,包括MAC(媒体访问控制器)和PHY(物理层接口)等组件。 在这里,我们以STM32F4系列为例来详细介绍以太网接口的配置和使用方法。 ✅作者简介:热爱科研的嵌入式开发者,修心和技术同步精进

    2024年04月26日
    浏览(32)
  • 手把手教-gd32f450基于rt-thread发布的bsp包手动添加以太网外设

    一、开发环境 rt-thread发布版本4.1.0; bsp包选用的是gd32目录下的gd32450z-eval 开发板gd32f450z系列开发板。 二、手动添加以太网ETH外设 先看下初始结构 可以看到,配置中什么也没有。 手动添加步骤如下: ①复制相应的驱动到gd32_drivers文件夹中,同时修改该文件夹中的SConscript文件

    2024年02月15日
    浏览(32)
  • 以太网MAC与PHY(二)

    目录 一、概述 二、MAC控制器 三、PHY 四、SMI协议         以太网硬件主要包括OSI的最下面两层,物理层和数据链路层 物理层:定义了数据传送与接收所需要的电与光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层提供标准接口。物理层的芯片为PHY 数据

    2024年02月09日
    浏览(40)
  • 【以太网通信】PHY 芯片回环测试

    PHY 芯片通常带有回环(Loopback)功能,用于 PHY 通信链路的测试。本文主要讨论三种常用 PHY 芯片的回环功能,并使用 Broadcom 的 B50612D 芯片进行 PHY 回环测试。 目录 1 常见 PHY 的回环功能 1.1 KSZ9031 1.2 RTL8211 1.3 B50610/B50612 2 PHY 回环测试         KSZ9031 芯片支持以下两种回环模

    2024年02月02日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包