打印
[应用相关]

以太网配合FreeRTOS实现socket通信!实战STM32F4以太网DP83848配...

[复制链接]
2986|32
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

以太网配合FreeRTOS实现socket通信!实战STM32F4以太网DP83848配合LWIP


一. 以太网行业标准MII/RMII
1 以太网接口MII,RMII
MII即“媒体独立接口”,也叫“独立于介质的接口”。它是IEEE-802.3定义的以太网行业标准。它包括一个数据接口,以及一个MAC和PHY之间的管理接口。

RMII全称为“简化的媒体独立接口”,是IEEE-802.3u标准中除MII接口之外的另一种实现。

1.1. 独立于介质的接口(MII)
独立于介质的接口(MII)用于MAC与外接的PHY互联,支持10Mbit/s和100Mbit/s数据传输模式。MII的信号线如下图所示:




MII_TX_EN:传输使能信号,此信号必需与数据前导符的起始位同步出现,并在传输完毕前一直保持。      
MII_TX_CLK:发送数据使用的时钟信号,对于10M位/s的数据传输,此时钟为2.5MHz,对于100M位/s的数据传输,此时钟为25MHz。
MII_TXD[3:0]:发送数据线,每次传输4位数据,数据在MII_TX_EN信号有效时有效。MII_TXD[0]是数据的最低位,MII_TXD[3]是最高位。当MII_TX_EN信号无效时,PHY忽略传输的数据。
MII_RX_ER:接收出错信号,保持一个或多个时钟周期(MII_RX_CLK)的有效状态,表明MAC在接收过程中检测到错误。具体错误原因需配合MII_RX_DV的状态及MII_RXD[3:0]的数据值。  
MII_RX_DV:接收数据使能信号,由PHY控制,当PHY准备好数据供MAC接收时,使能该信号。此信号必需和帧数据的首位同步出现,并保持有效直到数据传输完成。在传送最后4位数据后的第一个时钟之前,此信号必需变为无效状态。为了正确的接收一个帧,有效电平不能滞后于数据线上的SFD位出现。
MII_RX_CLK:接收数据使用的时钟信号,对于10M位/s的数据传输,此时钟为2.5MHz,对于100M位/s的数据传输,此时钟为25MHz。   
MII_RXD[3:0]:接收数据线,每次接收4位数据,数据在MII_RX_DV信号有效时有效。MII_RXD[0]是数据的最低位,MII_RXD[3]是最高位。当MII_RX_EN无效,而MII_RX_ER有效时,MII_RXD[3:0]数据值代表特定的信息(请参考表194)。                          
MII_CRS:载波侦听信号,仅工作在半双工模式下,由PHY控制,当发送或接收的介质非空闲时,使能此信号。 PHY必需保证MII_CRS信号在发生冲突的整个时间段内都保持有效,不需要此信号与发送/接收的时钟同步。
MII_COL:冲突检测信号,仅工作在半双工模式下,由PHY控制,当检测到介质发生冲突时,使能此信号,并且在整个冲突的持续时间内,保持此信号有效。此信号不需要和发送/接收的时钟同步。


1.2 精简的独立于介质的接口(RMII)
精简的独立于介质接口(RMII)规范减少了以太网通信所需要的引脚数。根据IEEE802.3标准,MII接口需要16个数据和控制信号引脚,而RMII标准则将引脚数减少到了7个。RMII具有以下特性:



时钟信号需要提高到50MHz.MAC和外部的以太网PHY需要使用同样的时钟源 ,使用2位宽度的数据收发   

RMII的信号线如下图所示:



1.3 时钟源
1)MII时钟源

为了产生TX_CLK和RX_CLK时钟信号,外接的PHY模块必需有来自外部的25MHz时钟驱动。该时钟不需要与MAC时钟相同。可以使用外部的25MHz晶体。当时钟来源MCO引脚时需配置合适的PLL,保证MCO引脚输出的时钟为25MHZ。

2)RMII时钟源

通过将相同的时钟源接到MAC和以太网PHY的REF_CLK引脚保证两者时钟源的同步。可以通过外部的50MHZ信号或者GD32F107xx微控制器的MCO引脚提供这一时钟。当时钟来源MCO引脚时需配置合适的PLL,保证MCO引脚输出的时钟为50MHZ。   

3)总结

采用MII接口,PYH的时钟频率要求25M,不需要与MAC层时钟一致。

采用RMII接口,PYH的时钟频率要求50M,需与MAC层时钟一致,通常从MAC层获取该时钟源。


使用特权

评论回复
沙发
keaibukelian|  楼主 | 2021-8-5 10:07 | 只看该作者
二. 以太网芯片(DP83848介绍)
1. DP83848芯片概述

DP83848是TI出的一款以太网物理层IC,有以下feature

一般工规芯片选择DP83848I就行了


使用特权

评论回复
板凳
keaibukelian|  楼主 | 2021-8-5 10:08 | 只看该作者
2. DP83848引脚介绍
2.1 Serial Management Interface

2.2 MAC Data Interface

2.3 Clock Interface

2.4 LED Interface

2.5 Reset and Power Down



使用特权

评论回复
地板
keaibukelian|  楼主 | 2021-8-5 10:11 | 只看该作者
3. DP83848中断寄存器

这个在后面移植的low_level_init会用到



使用特权

评论回复
5
keaibukelian|  楼主 | 2021-8-5 10:12 | 只看该作者
三. CubeMx配置以太网芯片DP838481. DP83848 GPIO配置

HR91105A是这个


使用特权

评论回复
6
keaibukelian|  楼主 | 2021-8-5 10:13 | 只看该作者

CubeMx GPIO配置如下(通过原理图可以看到是RMII接口):


使用特权

评论回复
7
keaibukelian|  楼主 | 2021-8-5 10:14 | 只看该作者
2. Cubemx Advanced Paramter配置
这里会牵扯到三个配置:

2.1 External PHY Configuration


此部分一共牵扯到以下部分:

PHY: 外部PHY芯片,我们开发板是选择的这个DP83848,所以默认可以直接勾选上

PHY Address Value: 外部PHY芯片的地址,可以看到DP83848芯片是根据map来的,默认PHYAD0是默认上拉的,PHYAD[4:1]是默认下来的,所以默认地址是0x01



其他默认配置就行了!


使用特权

评论回复
8
keaibukelian|  楼主 | 2021-8-5 10:15 | 只看该作者
2.2 Common:External PHY Configuration

这个我们选择默认就好

2.3 Extended:External PHY Configuration

这个我们选择默认就好


使用特权

评论回复
9
keaibukelian|  楼主 | 2021-8-5 10:16 | 只看该作者
四. lwip介绍
LwIP 全名:Light weight IP,意思是轻量化的 TCP/IP 协议,是瑞典计算机科学院 (SICS) 的 Adam Dunkels 开发的一个小型开源的 TCP/IP 协议栈。LwIP 的设计初衷是:用少量的资源消耗实现一个较为完整的TCP/IP 协议栈,其中“完整”主要指的是 TCP 协议的完整性,实现的重点是在保持 TCP 协议主要功能的基础上减少对 RAM 的占用。此外 LwIP 既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。

LwIP 具有主要特性:

  • 支持 ARP 协议(以太网地址解析协议)。
  • 支持 ICMP 协议(控制报文协议),用于网络的调试与维护。
  • 支持 IGMP 协议(互联网组管理协议),可以实现多播数据的接收。
  • 支持 UDP 协议 (用户数据报协议)。
  • 支持 TCP 协议 (传输控制协议),包括阻塞控制、RTT 估算、快速恢复和快速转发。
  • 支持 PPP 协议(点对点通信协议),支持 PPPoE。
  • 支持 DNS(域名解析)。
  • 支持 DHCP 协议,动态分配 IP 地址。
  • 支持 IP 协议,包括 IPv4、IPv6 协议,支持 IP 分片与重装功能,多网络接口下的数据包转发。
  • 支持 SNMP 协议(简单网络管理协议)。
  • 支持 AUTOIP,自动 IP 地址配置。
  • 提供专门的内部回调接口 (Raw API),用于提高应用程序性能。
  • 提供可选择的 Socket API、NETCONN API (在多线程情况下使用) 。

LwIP 在嵌入式中使用有以下优点:

  • 资源开销低,即轻量化。LwIP 内核有自己的内存管理策略和数据包管理策略,使得内核处理数据包的效率很高。另外,LwIP 高度可剪裁,一切不需要的功能都可以通过宏编译选项去掉。LwIP 的流畅运行需要40KB 的代码 ROM 和几十 KB 的 RAM,这让它非常适合用在内存资源受限的嵌入式设备中。
  • 支持的协议较为完整。几乎支持 TCP/IP 中所有常见的协议,这在嵌入式设备中早已够用。
  • 实现了一些常见的应用程序:DHCP 客户端、DNS 客户端、HTTP 服务器、MQTT 客户端、TFTP 服务器、SNTP 客户端等等。
  • 同时提供了三种编程接口:RAW API、NETCONN API (注:NETCONN API 即为 SequentialAPI,为了统一,下文均采用 NETCONN API)和 Socket API。这三种 API 的执行效率、易用性、可移植性以及时空间的开销各不相同,用户可以根据实际需要,平衡利弊,选择合适的 API 进行网络应用程序的开发。
  • 高度可移植。其源代码全部用 C 实现,用户可以很方便地实现跨处理器、跨编译器的移植。另外,它对内核中会使用到操作系统功能的地方进行了抽象,使用了一套自定义的 API,用户可以通过自己实现这些 API,从而实现跨操作系统的移植工作。
  • 开源、免费,用户可以不用承担任何商业风险地使用它。
  • 相比于嵌入式领域其它的 TCP/IP 协议栈,比如 uC-TCP/IP、FreeRTOS-TCP 等,LwIP 的发展历史要更悠久一些,得到了更多的验证和测试。LwIP 被广泛用在嵌入式网络设备中,国内一些物联网公司推出的物联网操作系统,其 TCP/IP 核心就是 LwIP;物联网知名的 WiFi模块 ESP8266,其 TCP/IP 固件,使用的就是 LwIP。



LwIP 尽管有如此多的优点,但它毕竟是为嵌入式而生,所以并没有很完整地实现 TCP/IP 协议栈。相比于 Linux 和 Windows 系统自带的 TCP/IP 协议栈,LwIP 的功能不算完整和强大。但对于大多数物联网领域的网络应用程序,LwIP 已经足够了。


使用特权

评论回复
10
keaibukelian|  楼主 | 2021-8-5 10:17 | 只看该作者
1.文件列表
可以通过这个链接去下载lwip的代码:http://download.savannah.nongnu.org/releases/lwip/

打开后就是这个模样:



文件列表大体分为几类:

1)drivers ,主要是提供一款32位的MCU(MCF5223X)部分驱动以及一款Eth的LAN芯片(CS8900A)

2)older_versions,主要是提供一些旧(1.4.0以前的版本)的lwip版本以及Contrib source code

3)contrib-xxxx,主要提供一些辅助代码(示例/移植等)

4)lwip-xxxxx,lwip对应版本的代码


使用特权

评论回复
11
keaibukelian|  楼主 | 2021-8-5 10:21 | 只看该作者
2.LwIP 的三种编程接口
LwIP 提供了三种编程接口,分别为 RAW/Callback API、NETCONN API、SOCKET API。用户可以根据实际情况,平衡利弊,选择合适的 API 进行网络应用程序的开发。以下内容将分别介绍这三种 API。

2.1 RAW/Callback API
RAW/Callback API 是指内核回调型的 API,这在许多通信协议的 C 语言实现中都有所应用。

RAW/Callback API 是 LwIP 的一大特色,在没有操作系统支持的裸机环境中,只能使用这种 API进行开发,同时这种 API 也可以用在操作系统环境中。这里先简要说明一下“回调”的概念。新建了一个 TCP 或者UDP 的连接,你想等它接收到数据以后去处理它们,这时你需要把处理该数据的操作封装成一个函数,然后将这个函数的指针注册到 LwIP 内核中。 LwIP 内核会在需要的时候去检测该连接是否收到数据,如果收到了数据,内核会在第一时间调用注册的函数,这个过程被称为“回调”,这个注册函数被称为“回调函数”。这个回调函数中装着你想要的业务逻辑,在这个函数中,你可以自由地处理接收到的数据,也可以发送任何数据,也就是说,这个回调函数就是你的应用程序。到这里,我们可以发现,在回调编程中,LwIP 内核把数据交给应用程序的过程就只是一次简单的函数调用,这是非常节省时间和空间资源的。每一个回调函数实际上只是一个普通的 C 函数,这个函数在 TCP/IP 内核中被调用。每一个回调函数都作为一个参数传递给当前 TCP 或 UDP 连接。而且,为了能够保存程序的特定状态,可以向回调函数传递一个指定

的状态,并且这个指定的状态是独立于 TCP/IP 协议栈的。。在有操作系统的环境中,如果使用 RAW/Callback API,用户的应用程序就以回调函数的形式成为了内核代码的一部分,用户应用程序和内核程序会处于同一个线程之中,这就省去了任务间通信和切换任务的开销了。

简单来说,RAW/Callback API 的优点有两个:

(1)可以在没有操作系统的环境中使用。

(2)在有操作系统的环境中使用它,对比另外两种 API,可以提高应用程序的效率、节省内存开销。

RAW/Callback API 的优点是显著的,但缺点也是显著的:

(1)基于回调函数开发应用程序时的思维过程比较复杂。在后面与 RAW/Callback API 相关的章节中可以看到,利用回调函数去实现复杂的业务逻辑时,会很麻烦,而且代码的可读性较差。

(2)在操作系统环境中,应用程序代码与内核代码处于同一个线程,虽然能够节省任务间通信和切换任务的开销,但是相应地,应用程序的执行会制约内核程序的执行,不同的应用程序之间也会互相制约。在应用程序执行的过程中,内核程序将不可能得到运行,这会影响网络数据包的处理效率。如果应用程序占用的时间过长,而且碰巧这时又有大量的数据包到达,由于内核代码长期得不到执行,网卡接收缓存里的数据包就持续积累,到最后很可能因为满载而丢弃一些数据包,从而造成丢包的现象。

2.2 NETCONN API
在操作系统环境中,可以使用 NETCONN API 或者 Socket API 进行网络应用程序的开发。NETCONN API 是基于操作系统的 IPC 机制(即信号量和邮箱机制)实现的,它的设计将 LwIP 内核代码和网络应用程序分离成了独立的线程。如此一来,LwIP 内核线程就只负责数据包的 TCP/IP封装和拆封,而不用进行数据的应用层处理,大大提高了系统对网络数据包的处理效率。前面提到,使用 RAW/Callback API 会造成内核程序和网络应用程序、不同网络应用程序之间的相互制约,如果使用 NETCONN API 或者 Socket API,这种制约将不复存在。

在操作系统环境中,LwIP 内核会被实现为一个独立的线程,名为 tcpip_thread,使用 NETCONN API 或者 Socket API 的应用程序处在不同的线程中,我们可以根据任务的重要性,分配不同的优先级给这些线程,从而保证重要任务的时效性,分配优先级的原则具体见表格 。表格 线程优先级分配原则



NETCONN API 使用了操作系统的 IPC 机制,对网络连接进行了抽象,用户可以像操作文件一样操作网络连接(打开/关闭、读/写数据)。但是 NETCONN API 并不如操作文件的 API 那样简单易用。举个例子,调用 f_read 函数读文件时,读到的数据会被放在一个用户指定的数组中,用户操作起来很方便,而 NETCONN API 的读数据 API,就没有那么人性化了。用户获得的不是一个数组,而是一个特殊的数据结构netbuf,用户如果想使用好它,就需要对内核的 pbuf 和 netbuf 结构体有所了解,我们会在后续的章节中对它们进行讲解。NETCONN API 之所以采取这种不人性的设计,是为了避免数据包在内核程序和应用程序之间发生拷贝,从而降低程序运行效率。当然,用户如果不在意数据递交时的效率问题,也可以把 netbuf 中的数据取出来拷贝到一个数组中,然后去处理这个数组。

简单来说,NETCONN API 的优缺点是:

(1)相较于 RAW/Callback API,NETCONN API 简化了编程工作,使用户可以按照操作文件的方式来操作网络连接。但是,内核程序和网络应用程序之间的数据包传递,需要依靠操作系统的信号量和邮箱机制完成,这需要耗费更多的时间和内存,另外还要加上任务切换的时间开销,效率较低。

(2)相较于 Socket API,NETCONN API 避免了内核程序和网络应用程序之间的数据拷贝,提高了数据递交的效率。但是,NETCONN API 的易用性不如 Socket API 好,它需要用户对 LwIP 内核所使用数据结构有一定的了解。

2.3 SOCKET API
Socket,即套接字,它对网络连接进行了高级的抽象,使得用户可以像操作文件一样操作网络连接。它十分易用,许多网络开发人员最早接触的就是 Socket 编程,Socket 已经成为了网络编程的标准。在不同的系统中,运行着不同的 TCP/IP 协议,但是只要它实现了 Socket 的接口,那么用Socket 编写的网络应用程序就能在其中运行。可见用 Socket 编写的网络应用程序具有很好的可移植性。不同的系统有自己的一套Socket 接口。Windows 系统中支持的是 WinSock,UNIX/Linux 系统中支持的是 BSD Socket,它们虽然风格不一致,但大同小异。LwIP 中的 Socket API 是 BSD Socket。但是 LwIP 并没有也没办法实现全部的 BSD Socket,如果开发人员想要移植 UNIX/Linux 系统中的网络应用程序到使用 LwIP 的系统中,就要注意这一点。

相较于 NETCONN API, Socket API 具有更好的易用性。使用 Socket API 编写的程序可读性好,便于维护,也便于移植到其它的系统中。Socket API 在内核程序和应用程序之间存在数据的拷贝,这会降低数据递交的效率。另外,LwIP 的 Socket API 是基于 NETCONN API 实现的,所以效率上相较前者要打个折扣。




使用特权

评论回复
12
keaibukelian|  楼主 | 2021-8-5 10:22 | 只看该作者
五. STM32F407在没有操作系统下移植lwip
1.Cubemx配置过程
1.1 RCC配置(选择外部晶振)


1.2 SYS配置(时基选择Systick)

1.3 Eth配置

我们以太网配置,我们已经在前面(CubeMx配置以太网芯片DP83848)做了说明,所以不再重复!

1.4 UART1配置(主要用来debug)


在这个基础上需要自己添加fputc函数(勾选micro lib)


int fputc(int ch, FILE *fp)

{

    if (fp == stdout)

    {

        /*If you want to output a line feed, you can automatically add a carriage

        return to prevent the horizontal position of some terminal terminals

        from changing after line feed.*/

        if (ch == '\n')

        {

            while ((USART1->SR & USART_SR_TXE) == 0);

            USART1->DR = '\r';

        }

        /* Output characters to USART1 */

        while ((USART1->SR & USART_SR_TXE) == 0);

        USART1->DR = ch;

    }

    return ch;

}



使用特权

评论回复
13
keaibukelian|  楼主 | 2021-8-5 10:23 | 只看该作者
1.5 lwip配置

此部分我们暂时除了DHCP部分,我们其他都用默认的!

这里有一个注意的点:IP地址的第三个域要根据路由器来决定,比如我的路由器IP地址是192.168.1.1,那么我可以随意配置成192.168.1.x(其中x在这个局域网中不能有重复地址)


使用特权

评论回复
14
keaibukelian|  楼主 | 2021-8-5 10:24 | 只看该作者
1.6 stm32f407 clock


有几个注意的点:

  • ①需要根据外部晶振来填写HSE的value,我的开发板外部晶振是25M
  • ②选择HSE
  • ③选择PLLCLK倍频
  • ④选择Max 168M,让cubemx自己配置就行了

使用特权

评论回复
15
keaibukelian|  楼主 | 2021-8-5 10:25 | 只看该作者
1.6 在main函数的while loop中加上lwip的处理MX_LWIP_Process
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* USER CODE BEGIN Init */
  /* USER CODE END Init */
  /* Configure the system clock */
  SystemClock_Config();
  /* USER CODE BEGIN SysInit */
  /* USER CODE END SysInit */
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_LWIP_Init();
  /* USER CODE BEGIN 2 */
        printf("STM32F407 ETH LWIP RUNING...\n");
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
        MX_LWIP_Process();  /* 嗨,我在这里哦,添加在这里,别找不到我! */
  }
  /* USER CODE END 3 */
}
丰收的时候到了哇!!!我们来ping下试试看




使用特权

评论回复
16
keaibukelian|  楼主 | 2021-8-5 10:26 | 只看该作者
2.自己移植lwip过程
2.1 配置lwip的宏文件lwipopts.h(可以直接把这个copy到你的noos的lwip中)
#ifndef __LWIPOPTS__H__
#define __LWIPOPTS__H__
#include "main.h"
/*-----------------------------------------------------------------------------*/
/* Current version of LwIP supported by CubeMx: 2.1.2 -*/
/*-----------------------------------------------------------------------------*/
/* Within 'USER CODE' section, code will be kept by default at each generation */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#ifdef __cplusplus
extern "C" {
#endif
/* STM32CubeMX Specific Parameters (not defined in opt.h) ---------------------*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- WITH_RTOS disabled (Since FREERTOS is not set) -----*/
#define WITH_RTOS 0
/*----- CHECKSUM_BY_HARDWARE enabled -----*/
#define CHECKSUM_BY_HARDWARE 1
/*-----------------------------------------------------------------------------*/
/* LwIP Stack Parameters (modified compared to initialization value in opt.h) -*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- Value in opt.h for NO_SYS: 0 -----*/
#define NO_SYS 1 /* NO_SYS 表示无操作系统模拟层,这个宏非常重要,因为无操作系统与有操作系统的移植和编写是完全不一样的,我们现在是无操作系统移植,所以将这个宏定义为 1。*/
/*----- Value in opt.h for SYS_LIGHTWEIGHT_PROT: 1 -----*/
#define SYS_LIGHTWEIGHT_PROT 0
/*----- Value in opt.h for MEM_ALIGNMENT: 1 -----*/
#define MEM_ALIGNMENT 4 /* 内存对齐,按照 4 字节对齐 */
/*----- Value in opt.h for LWIP_ETHERNET: LWIP_ARP || PPPOE_SUPPORT -*/
#define LWIP_ETHERNET 1
/*----- Value in opt.h for LWIP_DNS_SECURE: (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT) -*/
#define LWIP_DNS_SECURE 7
/*----- Value in opt.h for TCP_SND_QUEUELEN: (4*TCP_SND_BUF + (TCP_MSS - 1))/TCP_MSS -----*/
#define TCP_SND_QUEUELEN 9
/*----- Value in opt.h for TCP_SNDLOWAT: LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) -*/
#define TCP_SNDLOWAT 1071
/*----- Value in opt.h for TCP_SNDQUEUELOWAT: LWIP_MAX(TCP_SND_QUEUELEN)/2, 5) -*/
#define TCP_SNDQUEUELOWAT 5
/*----- Value in opt.h for TCP_WND_UPDATE_THRESHOLD: LWIP_MIN(TCP_WND/4, TCP_MSS*4) -----*/
#define TCP_WND_UPDATE_THRESHOLD 536
/*----- Value in opt.h for LWIP_NETIF_LINK_CALLBACK: 0 -----*/
#define LWIP_NETIF_LINK_CALLBACK 1
/*----- Value in opt.h for LWIP_NETCONN: 1 -----*/
#define LWIP_NETCONN 0
/*----- Value in opt.h for LWIP_SOCKET: 1 -----*/
#define LWIP_SOCKET 0
/*----- Value in opt.h for RECV_BUFSIZE_DEFAULT: INT_MAX -----*/
#define RECV_BUFSIZE_DEFAULT 2000000000
/*----- Value in opt.h for LWIP_STATS: 1 -----*/
#define LWIP_STATS 0
/*----- Value in opt.h for CHECKSUM_GEN_IP: 1 -----*/
#define CHECKSUM_GEN_IP 0
/*----- Value in opt.h for CHECKSUM_GEN_UDP: 1 -----*/
#define CHECKSUM_GEN_UDP 0
/*----- Value in opt.h for CHECKSUM_GEN_TCP: 1 -----*/
#define CHECKSUM_GEN_TCP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP: 1 -----*/
#define CHECKSUM_GEN_ICMP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP6: 1 -----*/
#define CHECKSUM_GEN_ICMP6 0
/*----- Value in opt.h for CHECKSUM_CHECK_IP: 1 -----*/
#define CHECKSUM_CHECK_IP 0
/*----- Value in opt.h for CHECKSUM_CHECK_UDP: 1 -----*/
#define CHECKSUM_CHECK_UDP 0
/*----- Value in opt.h for CHECKSUM_CHECK_TCP: 1 -----*/
#define CHECKSUM_CHECK_TCP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP: 1 -----*/
#define CHECKSUM_CHECK_ICMP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP6: 1 -----*/
#define CHECKSUM_CHECK_ICMP6 0
/*-----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
#ifdef __cplusplus
}
#endif
#endif /*__LWIPOPTS__H__ */


使用特权

评论回复
17
keaibukelian|  楼主 | 2021-8-5 10:27 | 只看该作者
2.2 配置字节对齐等宏cc.h
#ifndef __CC_H__
#define __CC_H__
#include "cpu.h"
#include <stdlib.h>
#include <stdio.h>
typedef int sys_prot_t;
#define LWIP_PROVIDE_ERRNO
#if defined (__GNUC__) & !defined (__CC_ARM)
#define LWIP_TIMEVAL_PRIVATE 0
#include <sys/time.h>
#endif
/* define compiler specific symbols */
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT  
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
#define LWIP_PLATFORM_ASSERT(x) do {printf("Assertion \"%s\" failed at line %d in %s\n", \
                                     x, __LINE__, __FILE__); } while(0)
/* Define random number generator function */
#define LWIP_RAND() ((u32_t)rand())
#endif /* __CC_H__ */


使用特权

评论回复
18
keaibukelian|  楼主 | 2021-8-5 10:28 | 只看该作者
2.3 把eth对接到lwip init/output/input函数中
主要牵扯到的函数(这三个函数都在ethernetif.c中)是:

1)low_level_init

2)low_level_output

3)low_level_input

low_level_init 就是底层初始化,代码如下:

static void low_level_init(struct netif *netif)
{
  uint32_t regvalue = 0;
  HAL_StatusTypeDef hal_eth_init_status;
/* Init ETH */
   uint8_t MACAddr[6] ;
  heth.Instance = ETH;
  heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE; /* 设置自动识别速度(10M/100M) */
  heth.Init.Speed = ETH_SPEED_100M;
  heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX;  /* 设置全双工 */
  heth.Init.PhyAddress = DP83848_PHY_ADDRESS;  /* 设置物理地址 */
  MACAddr[0] = 0x00;
  MACAddr[1] = 0x80;
  MACAddr[2] = 0xE1;
  MACAddr[3] = 0x00;
  MACAddr[4] = 0x00;
  MACAddr[5] = 0x00;  /* 设置MAC地址 */
  heth.Init.MACAddr = &MACAddr[0];
  heth.Init.RxMode = ETH_RXPOLLING_MODE; /* 设置模式为轮询 */
  heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; /* 设置以太网硬件校验 */
  heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;  /* 接口为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[0][0], ETH_TXBUFNB); /* 设置ETH TX DMA链表 */
  /* Initialize Rx Descriptors list: Chain Mode  */
  HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB); /* 设置ETH RX DMA链表 */
#if LWIP_ARP || LWIP_ETHERNET
  /* set MAC hardware address length */
  netif->hwaddr_len = ETH_HWADDR_LEN;
  /* set MAC hardware address */
  netif->hwaddr[0] =  heth.Init.MACAddr[0];
  netif->hwaddr[1] =  heth.Init.MACAddr[1];
  netif->hwaddr[2] =  heth.Init.MACAddr[2];
  netif->hwaddr[3] =  heth.Init.MACAddr[3];
  netif->hwaddr[4] =  heth.Init.MACAddr[4];
  netif->hwaddr[5] =  heth.Init.MACAddr[5];
  /* maximum transfer unit */
  netif->mtu = 1500;
  /* 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);  /* 开启ETH网 */
/* USER CODE BEGIN PHY_PRE_CONFIG */
/* USER CODE END PHY_PRE_CONFIG */
  /**** Configure PHY to generate an interrupt when Eth Link state changes ****/
  /* Read Register Configuration */
  HAL_ETH_ReadPHYRegister(&heth, PHY_MICR, &regvalue);
  regvalue |= (PHY_MICR_INT_EN | PHY_MICR_INT_OE);
  /* Enable Interrupts */
  HAL_ETH_WritePHYRegister(&heth, PHY_MICR, regvalue );
  /* Read Register Configuration */
  HAL_ETH_ReadPHYRegister(&heth, PHY_MISR, &regvalue);
  regvalue |= PHY_MISR_LINK_INT_EN;
  /* Enable Interrupt on change of link status */
  HAL_ETH_WritePHYRegister(&heth, PHY_MISR, regvalue);  /* 以上为配置eth寄存器,至于寄存器是干嘛的,可以看下上面DP83848小节 */
/* USER CODE BEGIN PHY_POST_CONFIG */
/* USER CODE END PHY_POST_CONFIG */
#endif /* LWIP_ARP || LWIP_ETHERNET */
/* USER CODE BEGIN LOW_LEVEL_INIT */
/* USER CODE END LOW_LEVEL_INIT */
}


使用特权

评论回复
19
keaibukelian|  楼主 | 2021-8-5 10:29 | 只看该作者
low_level_output是 lwip 往网卡驱动发送函数,代码如下:

此函数就是把lwip的pbuf数据copy到eth的tx dma中,然后发送

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
  err_t errval;
  struct pbuf *q;
  uint8_t *buffer = (uint8_t *)(heth.TxDesc->Buffer1Addr);
  __IO ETH_DMADescTypeDef *DmaTxDesc;
  uint32_t framelength = 0;
  uint32_t bufferoffset = 0;
  uint32_t byteslefttocopy = 0;
  uint32_t payloadoffset = 0;
  DmaTxDesc = heth.TxDesc;
  bufferoffset = 0;
  /* copy frame from pbufs to driver buffers */
  for(q = p; q != NULL; q = q->next)
    {
      /* Is this buffer available? If not, goto error */
      if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
      {
        errval = ERR_USE;
        goto error;
      }
      /* Get bytes in current lwIP buffer */
      byteslefttocopy = q->len;
      payloadoffset = 0;
      /* Check if the length of data to copy is bigger than Tx buffer size*/
      while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
      {
        /* Copy data to Tx buffer*/
        memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );
        /* Point to next descriptor */
        DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
        /* Check if the buffer is available */
        if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
        {
          errval = ERR_USE;
          goto error;
        }
        buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);
        byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
        payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
        framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
        bufferoffset = 0;
      }
      /* Copy the remaining bytes */
      memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
      bufferoffset = bufferoffset + byteslefttocopy;
      framelength = framelength + byteslefttocopy;
    }
  /* Prepare transmit descriptors to give to DMA */
  HAL_ETH_TransmitFrame(&heth, framelength);
  errval = ERR_OK;
error:
  /* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
  if ((heth.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
  {
    /* Clear TUS ETHERNET DMA flag */
    heth.Instance->DMASR = ETH_DMASR_TUS;
    /* Resume DMA transmission*/
    heth.Instance->DMATPDR = 0;
  }
  return errval;
}


使用特权

评论回复
20
keaibukelian|  楼主 | 2021-8-5 10:30 | 只看该作者
low_level_input是lwip接受input数据,代码如下:

此函数就是把eth的rx dma中数据copy到pbuf中,然后交给lwip处理

static struct pbuf * low_level_input(struct netif *netif)
{
  struct pbuf *p = NULL;
  struct pbuf *q = NULL;
  uint16_t len = 0;
  uint8_t *buffer;
  __IO ETH_DMADescTypeDef *dmarxdesc;
  uint32_t bufferoffset = 0;
  uint32_t payloadoffset = 0;
  uint32_t byteslefttocopy = 0;
  uint32_t i=0;
  /* get received frame */
  if (HAL_ETH_GetReceivedFrame(&heth) != HAL_OK)
    return NULL;
  /* Obtain the size of the packet and put it into the "len" variable. */
  len = heth.RxFrameInfos.length;
  buffer = (uint8_t *)heth.RxFrameInfos.buffer;
  if (len > 0)
  {
    /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
  }
  if (p != NULL)
  {
    dmarxdesc = heth.RxFrameInfos.FSRxDesc;
    bufferoffset = 0;
    for(q = p; q != NULL; q = q->next)
    {
      byteslefttocopy = q->len;
      payloadoffset = 0;
      /* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size*/
      while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
      {
        /* Copy data to pbuf */
        memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));
        /* Point to next descriptor */
        dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
        buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
        byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
        payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
        bufferoffset = 0;
      }
      /* Copy remaining data in pbuf */
      memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);
      bufferoffset = bufferoffset + byteslefttocopy;
    }
  }
    /* Release descriptors to DMA */
    /* Point to first descriptor */
    dmarxdesc = heth.RxFrameInfos.FSRxDesc;
    /* Set Own bit in Rx descriptors: gives the buffers back to DMA */
    for (i=0; i< heth.RxFrameInfos.SegCount; i++)
    {
      dmarxdesc->Status |= ETH_DMARXDESC_OWN;
      dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
    }
    /* Clear Segment_Count */
    heth.RxFrameInfos.SegCount =0;
  /* When Rx Buffer unavailable flag is set: clear it and resume reception */
  if ((heth.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)
  {
    /* Clear RBUS ETHERNET DMA flag */
    heth.Instance->DMASR = ETH_DMASR_RBUS;
    /* Resume DMA reception */
    heth.Instance->DMARPDR = 0;
  }
  return p;
}


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

63

主题

3875

帖子

5

粉丝