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

2023-12-23 03:20

本文主要是介绍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描述符是第一个描述符或最后一个描述符或中间描述符:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当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标志并恢复发送

这篇关于STM32的以太网外设+PHY(LAN8720)使用详解(6):以太网数据接收及发送的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/526522

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件