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

相关文章

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

Django开发时如何避免频繁发送短信验证码(python图文代码)

《Django开发时如何避免频繁发送短信验证码(python图文代码)》Django开发时,为防止频繁发送验证码,后端需用Redis限制请求频率,结合管道技术提升效率,通过生产者消费者模式解耦业务逻辑... 目录避免频繁发送 验证码1. www.chinasem.cn避免频繁发送 验证码逻辑分析2. 避免频繁

批量导入txt数据到的redis过程

《批量导入txt数据到的redis过程》用户通过将Redis命令逐行写入txt文件,利用管道模式运行客户端,成功执行批量删除以Product*匹配的Key操作,提高了数据清理效率... 目录批量导入txt数据到Redisjs把redis命令按一条 一行写到txt中管道命令运行redis客户端成功了批量删除k

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do