在介绍文件之前首先介绍一下DMA描述符

stm32以太网模块接收/发送FIFO和内存之间的以太网传输是通过以太网DMA使用DMA描述符完成的,一共有两个描述符列表:一个用于接收,一个用于发送, 两个列表的基址分别写入ETH_DMARDLAR 寄存器和 ETH_DMATDLAR 寄存器中。

 typedef struct {
__IO uint32_t Status; //状态
uint32_t ControlBufferSize; //控制和 buffer1, buffer2 的长度
uint32_t Buffer1Addr; //buffer1 地址
uint32_t Buffer2NextDescAddr; //buffer2 地址或下一个描述符地址
uint32_t ExtendedStatus; //增强描述符状态
uint32_t Reserved1; //保留
uint32_t TimeStampLow; //时间戳低位
uint32_t TimeStampHigh; //时间戳高位
} ETH_DMADESCTypeDef;

根据DMA描述符的内容可以组成两种结构,而stm32以太网库提供的就是链接结构。即Buffer1Addr存放数据,Buffer2NextDescAddr指向下一个描述符的首地址

uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE];      
uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE];

上面就是在初始化DMA描述符链表使能用到,相当于二维数组,第一个代表描述符的数量,此时定义的是5,第二个代表描述符 Buffer1Addr 的分配空间的大小,此时定义1500

 

1.lwipopts.h

用户自己定义使用的配置文件 可以对宏定义的开关选择打开或者关闭某些功能。系统提供的配置文件opt.h有大量的条件编译,所以还是不要动系统的默认配置。

配置信息有:LWIP_UDP/LWIP_TCP 、系统、lwip的接口编程方式,接收缓冲区的大小,是否使用socket,是否需要打印debug消息等等。

2.网卡驱动的编写:                                         LWIP协议栈只能通过pbuf和网卡通讯

lwip的网卡驱动的移植其实就是对以下几个函数函数的编写与封装。lwip内核已经写好了框架在ethnetif.c中,该文件默认是没有编译进去的(指的是LWIP官网下载的安装包),我们需要自己填充这个文件

 static void low_level_init(struct netif *netif)
static err_t low_level_output(struct netif *netif, struct pbuf *p)
static struct pbuf * low_level_input(struct netif *netif)
void ethernetif_input(struct netif *netif)
err_t ethernetif_init(struct netif *netif)

(1).网卡初始化函数,它主要包括mcu的片内外设

   ① eth的初始化(自协商模式是否开启,PHY的地址,网卡的MAC地址,接收模式是中断还是轮训,stm32官网开发文档写着中断模式只能用于系统,不带系统只能使用轮训模式,但是正点原子使用的demo是中断模式,我此次使用的是poll,是否开启硬件校验和PHY的通讯模式(MII/RMII))

② 将DMA发送和接收描述符初始化成链表结构 ,设置协议栈网络接口管理netif中国与网卡属性相关的特性,如设置MAC硬件地址,mac地址长度,最大传输单元,使能DMA发送和接收

(2)网卡数据包发送函数 将内核数据结构体pbuf描述的数据包发送出去

(3)网卡数据包接收函数,为了让内核更好的处理接收过来的数据,我们需要将接收到的数据封装成pbuf的形式

(4)主要是调用low_level_input()实现从网卡读取一个数据包,及解析数据包的类型(ARP,IP)并提交给应用层,这个函数可直接被应用程序调用

(5)在管理网络接口netif会调用,只要是对netif某些字段进行初始化,并调用low_level_init完成相关的初始化。

下面分别讲解一下这几个函数:.

void low_level_init(struct netif *netif)

初始化了netif的hwaddr_len、hwaddr、mtu、flags,其他的上面已经很详细的介绍,这里不再赘叙

 static void low_level_init(struct netif *netif)
{
uint32_t regvalue = ;
HAL_StatusTypeDef hal_eth_init_status; /* Init ETH */ uint8_t MACAddr[] ;
heth.Instance = ETH;
heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
heth.Init.PhyAddress = LAN8742A_PHY_ADDRESS;
MACAddr[] = 0x02;
MACAddr[] = 0x00;
MACAddr[] = 0x00;
MACAddr[] = 0x00;
MACAddr[] = 0x00;
MACAddr[] = 0x00;
heth.Init.MACAddr = &MACAddr[];
heth.Init.RxMode = ETH_RXPOLLING_MODE;
heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; /* USER CODE BEGIN MACADDRESS */ /* USER CODE END MACADDRESS */ hal_eth_init_status = HAL_ETH_Init(&heth); if (hal_eth_init_status == HAL_OK)
{
/* Set netif link flag */
netif->flags |= NETIF_FLAG_LINK_UP;
}
/* Initialize Tx Descriptors list: Chain Mode */
HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[][], ETH_TXBUFNB); /* Initialize Rx Descriptors list: Chain Mode */
HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[][], ETH_RXBUFNB); #if LWIP_ARP || LWIP_ETHERNET /* set MAC hardware address length */
netif->hwaddr_len = ETH_HWADDR_LEN; /* set MAC hardware address */
netif->hwaddr[] = heth.Init.MACAddr[];
netif->hwaddr[] = heth.Init.MACAddr[];
netif->hwaddr[] = heth.Init.MACAddr[];
netif->hwaddr[] = heth.Init.MACAddr[];
netif->hwaddr[] = heth.Init.MACAddr[];
netif->hwaddr[] = heth.Init.MACAddr[]; /* maximum transfer unit */
netif->mtu = ; /* Accept broadcast address and ARP traffic */
/* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
#if LWIP_ARP
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
#else
netif->flags |= NETIF_FLAG_BROADCAST;
#endif /* LWIP_ARP */ /* Enable MAC and DMA transmission and reception */
HAL_ETH_Start(&heth); /* USER CODE BEGIN PHY_PRE_CONFIG */ /* USER CODE END PHY_PRE_CONFIG */ /* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_ISFR, &regvalue);
regvalue |= (PHY_ISFR_INT4); /* Enable Interrupt on change of link status */
HAL_ETH_WritePHYRegister(&heth, PHY_ISFR , regvalue ); /* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_ISFR , &regvalue); #endif /* LWIP_ARP || LWIP_ETHERNET */ }

low_level_init

low_level_output(struct netif *netif, struct pbuf *p)

首先介绍一下pubuf结构体
struct pbuf {
struct pbuf *next; //指向下一个 pbuf 结构体,可以构成链表
void *payload; //指向该 pbuf 真正的数据区
u16_t tot_len; //当前 pbuf 和链表中后面所有 pbuf 的数据长度, 它们属于一个数据包
u16_t len; //当前 pbuf 的数据长度
u8_t type; //当前 pbuf 的类型 pbuf_ram / pbuf_pool
u8_t flags; //状态为,保留
u16_t ref; //该 pbuf 被引用的次数
};

PS:offset的值因为当前的pbuf处于不同的协议层而不同,如raw时 offset 为0 开启TCP时offset 为54

本质:

一个pbuf可能无法将所有的数据都发出去,所以一般将pbuf组成一个链表,这样数据就会在pbuf链表的payload区域,遍历pbuf链表将数据全部memcpy到eth的DMA发送描述符链表中,要注意三个地方,一个是offset区域,第二个是发送时要确保DMA发送描述符被ETH所有,不是被LWIP所有,第三个是当要发送数据大于DMA描述符链表所能发送的最大字节.

具体代码实现如下:

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
err_t errval;
struct pbuf *q;
uint8_t *buffer=(uint8_t *)(ETH_Handler.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = ;
uint32_t bufferoffset = ;
uint32_t byteslefttocopy = ;
uint32_t payloadoffset = ;
DmaTxDesc = ETH_Handler.TxDesc;
bufferoffset = ;
//从 pbuf 中拷贝要发送的数据
for(q=p;q!=NULL;q=q->next)
{
//判断此发送描述符是否有效,即判断此发送描述符是否归以太网 DMA 所有
if((DmaTxDesc->Status&ETH_DMATXDESC_OWN)!=(uint32_t)RESET)
{
errval=ERR_USE;
goto error; //发送描述符无效,不可用
}
byteslefttocopy=q->len; //要发送的数据长度
payloadoffset=;
//将 pbuf 中要发送的数据写入到以太网发送描述符中,有时候我们要发送的
//数据可能大于一个以太网描述符的 Tx Buffer,因此我们需要分多次将数据
//拷贝到多个发送描述符中
while((byteslefttocopy+bufferoffset)>ETH_TX_BUF_SIZE )
{
//将数据拷贝到以太网发送描述符的 Tx Buffer 中
memcpy((uint8_t*)((uint8_t*)buffer+bufferoffset),(uint8_t*)((uint8_t*)q->payload\
+payloadoffset),(ETH_TX_BUF_SIZE-bufferoffset));
//DmaTxDsc 指向下一个发送描述符
DmaTxDesc=(ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
//检查新的发送描述符是否有效
if((DmaTxDesc->Status&ETH_DMATXDESC_OWN)!=(uint32_t)RESET)
{
errval = ERR_USE;
goto error; //发送描述符无效,不可用
}
buffer=(uint8_t *)(DmaTxDesc->Buffer1Addr); //更新 buffer 地址,指向新
//的发送描述符的 Tx Buffer
byteslefttocopy=byteslefttocopy-(ETH_TX_BUF_SIZE-bufferoffset);
payloadoffset=payloadoffset+(ETH_TX_BUF_SIZE-bufferoffset);
framelength=framelength+(ETH_TX_BUF_SIZE-bufferoffset);
bufferoffset=;
}
//拷贝剩余的数据
memcpy( (uint8_t*)((uint8_t*)buffer+bufferoffset),(uint8_t*)((uint8_t*)q->payload\
+payloadoffset),byteslefttocopy );
bufferoffset=bufferoffset+byteslefttocopy;
framelength=framelength+byteslefttocopy;
}
HAL_ETH_TransmitFrame(&ETH_Handler,framelength);
errval = ERR_OK;
error:
//发送缓冲区发生下溢,一旦发送缓冲区发生下溢 TxDMA 会进入挂起状态
if((ETH_Handler.Instance->DMASR&ETH_DMASR_TUS)!=(uint32_t)RESET)
{
//清除下溢标志
ETH_Handler.Instance->DMASR = ETH_DMASR_TUS;
//当发送帧中出现下溢错误的时候 TxDMA 会挂起,这时候需要向 DMATPDR 寄存器
//随便写入一个值来将其唤醒,此处我们写 0
ETH_Handler.Instance->DMATPDR=;
}
return errval;
}

low_level_output_code

struct pbuf * low_level_input(struct netif *netif)
本质:
 将网卡eth的DMA描述符中的内容复制到pbuf链表中,供LWIP使用,过程是判断DMA描述符中是否有数据,获取数据长度len,为pbuf分配(offset+len)长度的空间,然后遍历DMA描述符链表进行复制,
最后复制完成将DMA描述符链表清空,最后将DMA描述符还给网卡,标记后即可接受新的数据。
具体代码实现如下:
 static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p = NULL;
struct pbuf *q;
uint16_t len;
uint8_t *buffer;
__IO ETH_DMADescTypeDef *dmarxdesc;
uint32_t bufferoffset=;
uint32_t payloadoffset=;
uint32_t byteslefttocopy=;
uint32_t i=;
if(HAL_ETH_GetReceivedFrame(&ETH_Handler)!=HAL_OK) //判断是否接收到数据
return NULL;
len=ETH_Handler.RxFrameInfos.length; //获取接收到的以太网帧长度
buffer=(uint8_t *)ETH_Handler.RxFrameInfos.buffer; //获取接收到的以太网帧的数据 buffer
if(len>) p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL); //申请 pbuf
if(p!=NULL) //pbuf 申请成功
{
dmarxdesc=ETH_Handler.RxFrameInfos.FSRxDesc; //获取接收描述符链表中
//的第一个描述符
bufferoffset = ;
for(q=p;q!=NULL;q=q->next)
{
byteslefttocopy=q->len;
payloadoffset=;
//将接收描述符中 Rx Buffer 的数据拷贝到 pbuf 中
while((byteslefttocopy+bufferoffset)>ETH_RX_BUF_SIZE )
{
//将数据拷贝到 pbuf 中
memcpy((uint8_t*)((uint8_t*)q->payload+payloadoffset),\
(uint8_t*)((uint8_t*)buffer+bufferoffset),(ETH_RX_BUF_SIZE-bufferoffset));
//dmarxdesc 指向下一个接收描述符
dmarxdesc=(ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
//更新 buffer 地址,指向新的接收描述符的 Rx Buffer
buffer=(uint8_t *)(dmarxdesc->Buffer1Addr);
byteslefttocopy=byteslefttocopy-(ETH_RX_BUF_SIZE-bufferoffset);
payloadoffset=payloadoffset+(ETH_RX_BUF_SIZE-bufferoffset);
bufferoffset=;
}
//拷贝剩余的数据
memcpy((uint8_t*)((uint8_t*)q->payload+payloadoffset),\
(uint8_t*)((uint8_t*)buffer+bufferoffset),byteslefttocopy);
bufferoffset=bufferoffset+byteslefttocopy;
}
}
//释放 DMA 描述符
dmarxdesc=ETH_Handler.RxFrameInfos.FSRxDesc;
for(i=;i<ETH_Handler.RxFrameInfos.SegCount; i++)
{
dmarxdesc->Status|=ETH_DMARXDESC_OWN; //标记描述符归 DMA 所有
dmarxdesc=(ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
}
ETH_Handler.RxFrameInfos.SegCount =; //清除段计数器
//接收缓冲区不可用
if((ETH_Handler.Instance->DMASR&ETH_DMASR_RBUS)!=(uint32_t)RESET)
{
//清除接收缓冲区不可用标志
ETH_Handler.Instance->DMASR = ETH_DMASR_RBUS;
//当接收缓冲区不可用的时候 RxDMA 会进去挂起状态,通过向 DMARPDR
//写入任意一个值来唤醒 Rx DMA
ETH_Handler.Instance->DMARPDR=;
}
return p;
}

low_level_input_code


void ethernetif_input(struct netif *netif)
主要是对low_level_inpu()的封装,然后传入指定的网卡结构中
 err_t ethernetif_input(struct netif *netif)
{
err_t err;
struct pbuf *p;
p=low_level_input(netif); //调用 low_level_input 函数接收数据
if(p==NULL) return ERR_MEM;
err=netif->input(p, netif); //调用 netif 结构体中的 input 字段(一个函数)来处理数据包
if(err!=ERR_OK)
{
LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));
pbuf_free(p);
p = NULL;
}
return err;
}

ethernetif_input

err_t ethernetif_init(struct netif *netif)
主要是对low_level_init()的封装,初始化了netif的相关字段,注册IP层发送函数
 err_t ethernetif_init(struct netif *netif)
{
LWIP_ASSERT("netif!=NULL",(netif!=NULL));
#if LWIP_NETIF_HOSTNAME //LWIP_NETIF_HOSTNAME
netif->hostname="lwip"; //初始化名称
#endif
netif->name[]=IFNAME0; //初始化变量 netif 的 name 字段
netif->name[]=IFNAME1; //在文件外定义这里不用关心具体值
netif->output=etharp_output;//IP 层发送数据包函数
netif->linkoutput=low_level_output;//ARP 模块发送数据包函数
low_level_init(netif); //底层硬件初始化函数
return ERR_OK;
}

ethernetif_init

3.系统时钟

系统通过调用函数sys_check_timerouts来处理内核的各种定时时间比如ARP、TCP等,该函数要求移植者实现一个函数sys_now来返回当前的系统时间,通过差值判断是否时间到达,从而调用相关的函数处理定时事件。在本次移植中,用的hal库自带的tick(1ms)来实现,也可以用全局变量和定时器来实现

 u32_t sys_now(void)
{
return HAL_GetTick();
}
 

4.LWIP初始化过程

       lwip_init()      初始化LWIP内核

IP4_ADDR() 将ip/netmask/gw数值整合成一个ip4_addr_t变量

netif_add() 此函数主要设置ip/netmask/gw 和调用ethernetif_init函数,当有消息来的时候调用ethernet_input。这两个函数会在后面文章介绍,一种网卡设备添加一次

netif_set_default() 将此网卡设置为默认网口

netif_set_up() 开启网口,在添加网口设备后就会将NETIF_IS_LINK_UP_FLAG 置1,后面会详细讲解 netif_set_link_up和netif_set_up的区别

因为此次是用的是poll模式,所以要在主循环调用ethnetif_input()和sys_sys_check_timerouts(); 确保内核是工作的。完成这一步,板子就能够ping通了

************************************************************************************************

参考文章:

正点原子的《STM32F7 LWIP开发手册》

《嵌入式网络那些事:LwIP协议栈深度剖析与演练》

LWIP移植文件介绍的更多相关文章

  1. LwIP移植和使用

    LwIP移植和使用 本手册基于lwip-1.4.x编写,本人没有移植过1.4.0之前的版本,更早的版本或许有差别.如果看官发现问题欢迎联系<QQ: 937431539  email: 93743 ...

  2. 关于lwip移植到ucsos-ii平台的遇到的问题(一)

    移植的步骤参照<Day_Day_Up笔记之uCOS-II_LwIP_在_STM32F107_上移植>,<uCOS平台下的LwIP移植笔记>,<嵌入式网络那些事>. ...

  3. Linux core 文件介绍

    Linux core 文件介绍 http://www.cnblogs.com/dongzhiquan/archive/2012/01/20/2328355.html 1. core文件的简单介绍在一个 ...

  4. linux设备驱动程序该添加哪些头文件以及驱动常用头文件介绍(转)

    原文链接:http://blog.chinaunix.net/uid-22609852-id-3506475.html 驱动常用头文件介绍 #include <linux/***.h> 是 ...

  5. Android下HelloWorld项目的R.java文件介绍

    R.java文件介绍 HelloWorld工程中的R.java文件 package com.android.hellworld; public final class R {     public s ...

  6. APK扩展文件介绍、功能及用法

    APK扩展文件介绍 Android Market (Google Play Store)中每一个APK文件的最大限制是50MB.假设您的程序中包括大量的数据文件,曾经您仅仅能把这些数据文件放到自己的s ...

  7. NSIS文字及字符串函数与头文件介绍

    原文 NSIS文字及字符串函数与头文件介绍 文字函数,顾名思义就是处理字符串的函数.使用这些字符串函数前,必须先包含头文件WordFunc.nsh.该头文件目前包含如下一些函数:WordFind.Wo ...

  8. opensslBIO系列之2---BIO结构和BIO相关文件介绍

    BIO结构和BIO相关文件介绍     (作者:DragonKing Mail:wzhah@263.net 公布于:http://gdwzh.126.com openssl专业论坛)          ...

  9. 微信小程序-01-项目组成文件介绍(入门篇)

    自古开篇先说两句,写这些笔记不是学习用的,主要是后续分享一些遇到的坑,碰到过什么样的问题,怎么去解决,如果你不是一个很耐心无看文章的人,建议去 网易云课堂找一些课程,跟着别人的脚步或许会更有动力,我的 ...

随机推荐

  1. yolo进化史之yolov3

    yolov3的论文写的比较简略,不看yolov1,yolov2很难直接看懂. 建议先看v1,v2论文. yolov3主要做了几点改进 改进了特征提取部分的网络结构 多尺度预测 分类由softmax改为 ...

  2. Mysql中有符号数和无符号数的区别

    1原文地址:https://blog.csdn.net/s78365126/article/details/85048882 2可以手写sql验证一下 3mysql无符号和有符号的区别无符号unsig ...

  3. spring中ehcache的配置和使用方法

    继续上篇,这篇介绍服务层缓存,ehcache一般的配置和用法 一.添加jar包引用 修改pom.xml文件,加入: <dependency> <groupId>org.spri ...

  4. [Pandas] 05 - Parallel processing

    相关资源 [Python] 09 - Multi-processing [Pandas] 01 - A guy based on NumPy [AI] 深度数学 - Bayes 这章非常有意思,但一定 ...

  5. DataFrame 转换为Dataset

    写在前面: A DataFrame is a Dataset organized into named columns. A Dataset is a distributed collection o ...

  6. opencv目标检测之canny算法

    canny canny的目标有3个 低错误率 检测出的边缘都是真正的边缘 定位良好 边缘上的像素点与真正的边缘上的像素点距离应该最小 最小响应 边缘只能标识一次,噪声不应该标注为边缘 canny分几步 ...

  7. 阿里云服务器CentOS6.9安装Mysql

    上篇讲了CentOS6.9安装tomcat,这篇来讲mysql的安装 1.查看CentOS是否安装了MySQL yum list installed | grep mysql //查看CentOS是否 ...

  8. springboot 集成druid

    1.druid简介 Druid首先是一个数据库连接池.Druid是目前最好的数据库连接池,在功能.性能.扩展性方面,都超过其他数据库连接池,包括DBCP.C3P0.BoneCP.Proxool.JBo ...

  9. Linux 删除命令rm

    Linux rm命令用于删除一个文件或者目录. 语法:rm [options] name... 参数: 1.-i 删除前逐一询问确认. 2.-f 即使原档案属性设为唯读,亦直接删除,无需逐一确认. 3 ...

  10. [ProblemSolving][Ubuntu][LyX] The selected document class ... requires external files that are not available...

    I installed LyX in my Ubuntu(version LTS 18.04), but I just can't make it work. Every time I open an ...