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

news2025/2/28 15:33:04

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标志并恢复发送

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1330222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(十七)Flask之大型项目目录结构示例【二扣蓝图】

大型项目目录结构&#xff1a; 问题引入&#xff1a; 在上篇文章讲蓝图的时候我给了一个demo项目&#xff0c;其中templates和static都各自只有一个&#xff0c;这就意味着所有app的模板和静态文件都放在了一起&#xff0c;如果项目比较大的话&#xff0c;这就非常乱&#xf…

力扣面试经典题之二叉树

104. 二叉树的最大深度 简单 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xf…

vp与vs联合开发-网口通信(socket)

Socket通信是一种在网络中进行进程间通信的机制。它使用了一种称为套接字&#xff08;Socket&#xff09;的编程接口&#xff0c;通过该接口可以创建、连接、发送和接收数据等操作。 Socket通信中&#xff0c;有两个主要的角色&#xff1a;服务器和客户端。服务器负责监听指定…

【单调栈】LeetCode:2818操作使得分最大

作者推荐 map|动态规划|单调栈|LeetCode975:奇偶跳 涉及知识点 单调栈 题目 给你一个长度为 n 的正整数数组 nums 和一个整数 k 。 一开始&#xff0c;你的分数为 1 。你可以进行以下操作至多 k 次&#xff0c;目标是使你的分数最大&#xff1a; 选择一个之前没有选过的 非…

基于Springboot的酒店管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的酒店管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

阅读笔记-A Cluster Separation Measure

A Cluster Separation Measure&#xff08;一种聚类分离测度&#xff09; 1.这篇论文要解决什么问题&#xff1f;要验证一个什么科学假设&#xff1f; 问题是确定数据中聚类的适当数量&#xff0c;解决这种问题的两种方法都取决于确定指数中相对较大的变化&#xff0c;而不是…

免费PHP完美运营的最新短视频打赏系统学习版

免费PHP完美运营的最新短视频打赏系统学习版 一、介绍 免费PHP完美运营的最新短视频打赏系统学习版&#xff0c;是一款基于PHP开发的打赏系统&#xff0c;具有强大的功能和稳定的性能。相比于市面上的其他打赏系统&#xff0c;它更加完善&#xff0c;几乎无bug&#xff0c;能…

Socket.D 基于消息的响应式应用层网络协议

首先根据 Socket.D 官网 的副标题&#xff0c;Socket.D 的自我定义是&#xff1a; 基于事件和语义消息流的网络应用协议。官网定义的特点是&#xff1a; 基于事件&#xff0c;每个消息都可事件路由所谓语义&#xff0c;通过元信息进行语义描述流关联性&#xff0c;有相关的消…

《数字图像处理-OpenCV/Python》连载:图像的阈值处理

《数字图像处理-OpenCV/Python》连载&#xff1a;图像的阈值处理 本书京东 优惠购书链接 https://item.jd.com/14098452.html 本书CSDN 独家连载专栏 https://blog.csdn.net/youcans/category_12418787.html 第 9 章 图像的阈值处理 图像的阈值处理简单、直观&#xff0c;计算…

ES-mapping

类似数据库中的表结构定义&#xff0c;主要作用如下 定义Index下的字段名( Field Name) 定义字段的类型&#xff0c;比如数值型、字符串型、布尔型等定义倒排索引相关的配置&#xff0c;比如是否索引、记录 position 等 index_options 用于控制倒排索记录的内容&#xff0c;有如…

【PWN学习】cannary绕过方式汇总

背景 利用cannary解题在现在的CTF比赛中似乎已经过时了&#xff0c;只是为了学习了解一下。 绕过的4种方式 目前我了解到的方式主要有以下4种 forksspstack_chk_failTLS canary attack fork 每次进程重启后的Canary是不同的&#xff0c;但是同一个进程中的Canary都是一样…

探秘JDK 10:崭新特性悉数解析

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 探秘JDK 10&#xff1a;崭新特性悉数解析 前言局部变量类型推断(var关键字)线程本地握手背景和用途&#xff1a;如何使用&#xff1a;优势&#xff1a; 改进的option集合工厂方法List.of() 方法&#…

化繁为简,Python快速入门,从基础到实践的学习。

文章目录 前言一、安装与运行命令行运行 python 文件 二、变量和简单数据类型2.1 变量命名规则2.2 字符串2.2.1 字符串的简单运算title()upper()、lower() 2.2.2 合并&#xff08;拼接&#xff09;字符串2.2.3 使用制表符或换行符来添加空白2.2.4 删除空白2.2.5 Python 2 中的 …

Arduino开发实例-APDS-9930环境光和趋近传感器驱动

APDS-9930环境光和趋近传感器驱动 文章目录 APDS-9930环境光和趋近传感器驱动1、APDS-9930介绍2、硬件准备及接线3、驱动实现1、APDS-9930介绍 APDS-9930模块由环境光、红外线和接近传感器组成。 检测距离可达 100 毫米。 APDS-9930 传感器测量环境光。 该传感器还可以在黑暗中…

trino-435版本windows下源码编译

一、源码下载地址 https://github.com/trinodb/trino/tags 二、编译环境及工具准备 1、maven &#xff08;1&#xff09;版本&#xff1a;3.6.3 &#xff08;2&#xff09;settings.xml配置 <?xml version"1.0" encoding"UTF-8"?> <settin…

融资项目——vue之事件监听

vue通过v-on进行事件监听&#xff0c;在标签中使用v-on:xxx&#xff08;事件名称&#xff09;进行监听&#xff0c;事件触发的相应方法定义在Vue对象中的methods中。如下图所示&#xff1a; 上述代码对按钮进行监听&#xff0c;点击按钮后就会触发solve函数。

2016年第五届数学建模国际赛小美赛C题对超级细菌的战争解题全过程文档及程序

2016年第五届数学建模国际赛小美赛 C题 对超级细菌的战争 原题再现&#xff1a; 最近有很多关于我们抗生素耐药性危机的讨论。进化出的能够抵抗抗生素的细菌每年杀死70万人&#xff0c;越来越强大的细菌正在世界各地传播。研究人员担心&#xff0c;我们将进入一个后抗生素时代…

分子动力学模拟

天玑算科研服务_模拟计算_超算租用_实验测试天玑算科研服务致力于营造良好科研生态环境&#xff0c;加速科研成果转换&#xff0c;500余位TOP高校博士计算工程师团队&#xff0c;涉及第一性原理、分子动力学模拟、有限元等领域&#xff0c;为电池、能源、化工、生物医药等领域提…

尚硅谷图解Java设计模式

尚硅谷图解Java设计模式 第 1 章 内容介绍和授课方式Java 设计模式内容介绍先看几个经典的面试题设计模式的重要性 课程亮点和授课方式 第 2 章 设计模式七大原则设计模式的目的设计模式七大原则单一职责原则基本介绍应用实例单一职责原则注意事项和细节 接口隔离原则(Interfac…

HarmonyOS构建第一个ArkTS应用(FA模型)

构建第一个ArkTS应用&#xff08;FA模型&#xff09; 创建ArkTS工程 若首次打开DevEco Studio&#xff0c;请点击Create Project创建工程。如果已经打开了一个工程&#xff0c;请在菜单栏选择File > New > Create Project来创建一个新工程。 选择Application应用开发&a…