文章目录
LWIP 简介
LwIP 全名:Light weight IP,意思是轻量化的 TCP/IP 协议。LwIP 的设计初衷是:用少量的资源消耗实现一个较为完整的 TCP/IP 协议栈,其中“完整”主要指的是 TCP 协议的完整性,实现的重点是在保持 TCP 协议主要功能的基础上减少对 RAM 的占用。此外 LwIP 既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。
LWIP 文件说明
最关心的是src文件中的内容
api :文件装的是NETCONN API 和SOCKET API 源文件,只有在操作系统中才会编译
apps:文件夹里面装的是应用程序的源文件,包括常见的应用程序,如 httpd、mqtt、tftp、sntp、snmp等
core:文件夹里面是 LwIP 的内核源文件
include:文件夹里面是 LwIP 所有模块对应的头文件。
netif:文件夹里面是与网卡移植有关的文件,这些文件为我们移植网卡提供了模板,我们可以直接使用。
core内核文件简介
altcp.c、altcp_alloc.c、altcp_tcp.c 等文件是应用程序分层 TCP 连接 API,从 TCPIP 线程使用,是一个抽象层,可以模拟应用程序的 tcp 回调 API,同时防止直接链接,没有使用。
def.c:文件定义了一些基础类函数,比如主机序和网络序的转换、字符串的查找和比较、整数转换成字符串等,这些函数会被 LwIP 内核的很多模块所调用。在 include 目录里面的 def.h 文件对外声明了 def.c 所实现的函数,同时定义了许多宏,能实现一些基础操作,比如取最大值、取最小值、计算数组长度等,这些宏同样也被内核的许多模块所调用。
我们经常可以看到某个内核的源文件在开始的地方 #include “def.h”
dns:文件实现了域名解析的功能
inet_chksum.c:文件提供了 LwIP 所需的校验和功能
init.c:文件对 LwIP 的用户宏配置进行了检查,会将配置错误和不合理的地方,通过编译器的 #error和 #warning 功能表示出来。另外,init.c 定义了lwip_init 初始化函数,这个函数会依次对 LwIP 的各个模块进行初始化。
ip.c:文件实现了 IP 协议相关的函数
mem.c:文件实现了内存堆(heap)管理机制
memp.c:文件实现了动态内存池(Dynamic)管理机制
netif.c文件实现了网卡的操作,比如注册/删除网卡、使能/禁能网卡、设置网卡 IP 地址等等
pbuf.c:文件实现了 LwIP 对网络数据包的各种操作
raw.c:文件实现了一个传输层协议的框架
stat.c:文件实现了 LwIP 内核的统计功能,使用户可以实时地查看 LwIP 内核对网络数据包的处理情况
sys.c:它提供了与临界区相关的操作
tcp.c、tcp_in.c 和 tcp_out.c:文件实现了 TCP 协议,包括对 TCP 连接的操作、对 TCP 数据包的输入输出操作和 TCP 定时器
timeouts.c:定义了 LwIP 内核的超时处理机制
udp.c:文件实现了 UDP 协议,包括对 UDP 连接的操作和 UDP 数据包的操作。
LWIP 三种编程接口
RAW
RAW/Callback API 是指内核回调型的 API,这在许多通信协议的 C 语言实现中都有所应用
NETCONN
可以使用 NETCONN API 或者 Socket API 进行网络应用程序的开发。NETCONN API 是基于操作系统的 IPC 机制(即信号量和邮箱机制)实现。在操作系统环境中,LwIP 内核会被实现为一个独立的线程,名为tcpip_thread
函数的调用 lwip_system_init->tcpip_init(tcpip_init_done_callback, (void *)&done_sem);
void tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
lwip_init();
tcpip_init_done = initfunc;
tcpip_init_done_arg = arg;
if (sys_mbox_new(&tcpip_mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
}
#if LWIP_TCPIP_CORE_LOCKING
if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
LWIP_ASSERT("failed to create lock_tcpip_core", 0);
}
#endif /* LWIP_TCPIP_CORE_LOCKING */
sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}
SOCKET API
Socket,即套接字,它对网络连接进行了高级的抽象,使得用户可以像操作文件一样操作网络连接
ETH(以太网)
介绍
TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准,PHY 层芯片 LAN8720 相当于物理层,STM32F407 自带的MAC 层相当于数据链路层,而 LWIP 提供的就是网络层、传输层的功能,应用层是需要用户自己根据自己想要的功能去实现的。
STM32F407自带有10/100Mbit/s的以太网MAC(这个是内核,介子控制器)内核,这个以太网MAC内核有 如下特性:
- 支持外部PHY(物理层这个是纯模拟电路)接口实现10/100Mbit/s数据传输速率。
- 通过符合IEEE802.3的(MII接口)介子独立接口和(RMII接口)简化介子独立接口与外接快速以太网PHY进行通信
- 支持全双工和半双工操作
- 报头和帧起始数据(SFD)在发送路径中插入、在接收路径中删除
- 可逐帧控制CRC和pad自动生成
- 可编程帧长度,支持高大16KB的巨型帧
- 可编程帧间隔
- SMI接口是站管理接口,通过这个接口来访问PHY寄存器,数据线MDIO和时钟线MDC,可以访问32个PHY设备
SMI接口
站管理接口(SMI) 允许应用程序通过 2 条线:时钟(MDC)和数据线(MDIO)访问任意 PHY 寄存器。该接口支持访问多达 32 个PHY。应用程序可以从 32 个PHY 中选择一个PHY,然后从任意PHY 包含的 32 个寄存器中选择一个寄存器,发送控制数据或接收状态信息。
MII接口
MII接口:介质独立接口 (MII) 定义了 10 Mbit/s 和 100 Mbit/s 的数据传输速率下 MAC 子层与 PHY 之间的互连。
TX_CLK和RX_CLK为发送和接收连续时钟,当速率为10Mbit/s时为2.5MHZ,速率为100Mbit/s时为25MHZ,外部晶振提供25MHZ的时钟。
RMII接口
介质独立接口 (RMII) 规范降低了 10/100 Mbit/s 下微控制器以太网外设与外部 PHY 间的 引脚数。根据 IEEE 802.3u 标准, MII 包括 16 个数据和控制信号的引脚。 RMII 规范将引脚 数减少为 7个(引脚数减少 62.5%)。不过RMII接口的参考时钟必须是50MHZ!
REF_CLK:参考时钟
TX_EN: 发送使能
TXD[1:0]:发送数据
RXD[1:0]:接收数据
以太网DMA描述符
F407有一个以太网专用的DAM,DMA可以在CPU完全不干预的情况下,通过描述符有效地将数据从源传送到目标,接收缓冲区和发送缓冲区的数据都可以通过以太网DMA来传送。429可以直接从HAL库中把32f4xx_hal_eth.c添加进来。
typedef struct
{
__IO uint32_t Status; /*!< Status */
uint32_t ControlBufferSize; /*!< Control and Buffer1, Buffer2 lengths */
uint32_t Buffer1Addr; /*!< Buffer1 address pointer 这个是缓冲区的地址 */
uint32_t Buffer2NextDescAddr; /*!< Buffer2 or next descriptor address pointer
下一个描述符的地址 */
/*!< Enhanced ETHERNET DMA PTP Descriptors */
uint32_t ExtendedStatus; /*!< 增强型描述符状态 */
uint32_t Reserved1; /*!< Reserved */
uint32_t TimeStampLow; /*!< Time Stamp 时间戳 Low value for transmit and receive */
uint32_t TimeStampHigh; /*!< Time Stamp 时间错High value for transmit and receive */
} ETH_DMADescTypeDef;
//DMA描述符数组
ETH_DMADescTypeDef DMARxDscrTab[ETH_RXBUFNB] //DMA接收描述符
ETH_DMADescTypeDef DMATxDscrTab[ETH_TXBUFNB] //DMA发送描述符
//数据缓冲区
uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]
uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]
以太网驱动库 stm32f4xx_hal_eth.c
中我们采用的是 DMA 链接结构,但是DMARxDscrTab
和DMATxDscrTab
是两个指针(或数组),那么我们必然要将这两个指针(或数组)改为链接结构。在 stm32f4xx_hal_eth.c
文件中有两个函数 HAL_ETH_DMATxDescListInit()
和HAL_ETH_DMARxDescListInit ()
,通过这两个函数我们就可以将上面两个指针(或数组)变为链接结构。
//在应用层定义指针,用于发送和接收数据的缓冲区
uint8_t *Rx_Buff; //以太网底层驱动接收 buffers 指针
uint8_t *Tx_Buff; //以太网底层驱动发送 buffers 指针
//这两个值,在stm32f4xx_hal_conf.h 定义
uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE];
uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE];
LWIP数据包和网络接口管理
netif 结构体
如何使硬件和软件无缝的连接起来。LWIP使用一个数据结构体netif来描述网卡。
单个网卡中,netif 结构体只有一个,多个网卡的时候,LwIP 会将每个用 netif 描述的网卡连接成一个链表(单向链表),该链表就记录每个网卡的 netif。
struct netif {
#if !LWIP_SINGLE_NETIF
/** pointer to next in linked list */
/** 指向netif 链表的下一个 */
struct netif *next;
#endif
#if LWIP_IPV4
/** IP address configuration in network byte order */
/** IP 地址 子网掩码 默认网关 */
ip_addr_t ip_addr;
ip_addr_t netmask;
ip_addr_t gw;
#endif /* LWIP_IPV4 */
/** This function is called by the network device driver
* to pass a packet up the TCP/IP stack.
* 此函数由网络设备驱动程序调用,将数据包传递到 TCP/IP 协议栈。
* 对于以太网物理层,这通常是 ethernet_input() */
netif_input_fn input;
#if LWIP_IPV4
/** This function is called by the IP module when it wants
* to send a packet on the interface. This function typically
* first resolves the hardware address, then sends the packet.
* For ethernet physical layer, this is usually etharp_output()
* 此函数由 IP 层调用,在接口上发送数据包。通常这个功能,
* 首先解析硬件地址,然后发送数据包。
* 对于以太网物理层,这通常是 etharp_output()/*
* */
netif_output_fn output;
#endif /* LWIP_IPV4 */
/** This function is called by ethernet_output() when it wants
* to send a packet on the interface. This function outputs
* the pbuf as-is on the link medium.
* 这个函数被ethernet_output()调用,当需要在网卡上发送一个数据包的时候,
* 底层硬件输出数据,
*/
netif_linkoutput_fn linkoutput;
#if LWIP_NETIF_STATUS_CALLBACK
/** This function is called when the netif state is set to up or down
状态
*/
netif_status_callback_fn status_callback;
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
/** This function is called when the netif link is set to up or down
网络连接
*/
netif_status_callback_fn link_callback;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_NETIF_REMOVE_CALLBACK
/** This function is called when the netif has been removed
网卡将要删除的时候
*/
netif_status_callback_fn remove_callback;
#endif /* LWIP_NETIF_REMOVE_CALLBACK */
/** This field can be set by the device driver and could point
* to state information for the device. */
void *state;
#ifdef netif_get_client_data
void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX + LWIP_NUM_NETIF_CLIENT_DATA];
#endif
#if LWIP_NETIF_HOSTNAME
/* the hostname for this netif, NULL is a valid value
netif 的主机名 NULL也是一个有效值
*/
const char* hostname;
#endif /* LWIP_NETIF_HOSTNAME */
#if LWIP_CHECKSUM_CTRL_PER_NETIF
u16_t chksum_flags;
#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
/** maximum transfer unit (in bytes) */
u16_t mtu;
#if LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES
/** maximum transfer unit (in bytes), updated by RA */
u16_t mtu6;
#endif /* LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES */
/** link level hardware address of this interface */
u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
/** number of bytes used in hwaddr */
u8_t hwaddr_len;
/** flags (@see @ref netif_flags)
网卡状态信息标志位,是很重要的控制字段,
* 它包括网卡功能使能、广播使能、 ARP 使能等等重要控制位。 */
* */
u8_t flags;
/** descriptive abbreviation */
char name[2];
/** number of this interface. Used for @ref if_api and @ref netifapi_netif,
* as well as for IPv6 zones */
u8_t num;
#if LWIP_IPV6_AUTOCONFIG
/** is this netif enabled for IPv6 autoconfiguration */
u8_t ip6_autoconfig_enabled;
#endif /* LWIP_IPV6_AUTOCONFIG */
#if LWIP_IPV6_SEND_ROUTER_SOLICIT
/** Number of Router Solicitation messages that remain to be sent. */
u8_t rs_count;
#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
#if MIB2_STATS
/** link type (from "snmp_ifType" enum from snmp_mib2.h) */
u8_t link_type;
/** (estimate) link speed */
u32_t link_speed;
/** timestamp at last change made (up/down) */
u32_t ts;
/** counters */
struct stats_mib2_netif_ctrs mib2_counters;
#endif /* MIB2_STATS */
#if LWIP_IPV4 && LWIP_IGMP
/** This function could be called to add or delete an entry in the multicast
filter table of the ethernet MAC.*/
netif_igmp_mac_filter_fn igmp_mac_filter;
#endif /* LWIP_IPV4 && LWIP_IGMP */
#if LWIP_IPV6 && LWIP_IPV6_MLD
/** This function could be called to add or delete an entry in the IPv6 multicast
filter table of the ethernet MAC. */
netif_mld_mac_filter_fn mld_mac_filter;
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
#if LWIP_NETIF_USE_HINTS
struct netif_hint *hints;
#endif /* LWIP_NETIF_USE_HINTS */
#if ENABLE_LOOPBACK
/* List of packets to be queued for ourselves. */
struct pbuf *loop_first;
struct pbuf *loop_last;
#if LWIP_LOOPBACK_MAX_PBUFS
u16_t loop_cnt_current;
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
#endif /* ENABLE_LOOPBACK */
};
netif 使用
将网卡定义一个 netif结构体变量 struct netif gnetif,然后要把网卡挂载到 netif_list链表上才能够使用。
netif_add()函数,把网卡进行挂载。
struct netif *netif_add(struct netif *netif,
#if LWIP_IPV4
const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
#endif /* LWIP_IPV4 */
void *state, netif_init_fn init, netif_input_fn input)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("netif_add: invalid netif", netif != NULL, return NULL);
LWIP_ERROR("netif_add: No init function given", init != NULL, return NULL);
#if LWIP_IPV4
if (ipaddr == NULL) {
ipaddr = ip_2_ip4(IP4_ADDR_ANY);
}
if (netmask == NULL) {
netmask = ip_2_ip4(IP4_ADDR_ANY);
}
if (gw == NULL) {
gw = ip_2_ip4(IP4_ADDR_ANY);
}
/* reset new interface configuration state */ 第一步 地址清零
ip_addr_set_zero_ip4(&netif->ip_addr);
ip_addr_set_zero_ip4(&netif->netmask);
ip_addr_set_zero_ip4(&netif->gw);
netif->output = netif_null_output_ip4;
#endif /* LWIP_IPV4 */
NETIF_SET_CHECKSUM_CTRL(netif, NETIF_CHECKSUM_ENABLE_ALL);
netif->mtu = 0;
netif->flags = 0;
#ifdef netif_get_client_data
memset(netif->client_data, 0, sizeof(netif->client_data));
#endif /* LWIP_NUM_NETIF_CLIENT_DATA */
#if LWIP_NETIF_STATUS_CALLBACK
netif->status_callback = NULL;
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
netif->link_callback = NULL;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_IGMP
netif->igmp_mac_filter = NULL;
#endif /* LWIP_IGMP */
#if ENABLE_LOOPBACK
netif->loop_first = NULL;
netif->loop_last = NULL;
#endif /* ENABLE_LOOPBACK */
/* remember netif specific state information data */
netif->state = state; 第二步根据传递进来的参数填写网卡 state、input 等字段的相关信息
netif->num = netif_num;
netif->input = input;
NETIF_RESET_HINTS(netif);
#if ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS
netif->loop_cnt_current = 0;
#endif /* ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS */
#if LWIP_IPV4
netif_set_addr(netif, ipaddr, netmask, gw); 第三步调用网卡设置函数 netif_set_addr() 设置网卡 IP 地址、子网掩码、网关等信息
#endif /* LWIP_IPV4 */
/* call user specified initialization function for netif */
if (init(netif) != ERR_OK) { 第四步通过传递进来的回调函数 init() 进行网卡真正的初始化操作,所以该函数是由用户实现的
return NULL;
}
{
struct netif *netif2;
int num_netifs;
do {
if (netif->num == 255) {
netif->num = 0;
}
num_netifs = 0;
for (netif2 = netif_list; netif2 != NULL; netif2 = netif2->next) {
LWIP_ASSERT("netif already added", netif2 != netif);
num_netifs++;
LWIP_ASSERT("too many netifs, max. supported number is 255", num_netifs <= 255);
if (netif2->num == netif->num) {
netif->num++;
break;
}
}
} while (netif2 != NULL);
}
if (netif->num == 254) {
netif_num = 0;
} else {
netif_num = (u8_t)(netif->num + 1); 第5步初始化网卡成功,则遍历当前设备拥有多少个网卡,并为当前网卡分配维一标识 num
}
/* add this netif to the list */
netif->next = netif_list; 第6步将当前网卡插入 netif_list 链表中。
netif_list = netif;
#endif /* "LWIP_SINGLE_NETIF */
mib2_netif_added(netif);
#if LWIP_IGMP
/* start IGMP processing */
if (netif->flags & NETIF_FLAG_IGMP) {
igmp_start(netif);
}
#endif /* LWIP_IGMP */
LWIP_DEBUGF(NETIF_DEBUG, ("netif: added interface %c%c IP",
netif->name[0], netif->name[1]));
#if LWIP_IPV4
LWIP_DEBUGF(NETIF_DEBUG, (" addr "));
ip4_addr_debug_print(NETIF_DEBUG, ipaddr);
LWIP_DEBUGF(NETIF_DEBUG, (" netmask "));
ip4_addr_debug_print(NETIF_DEBUG, netmask);
LWIP_DEBUGF(NETIF_DEBUG, (" gw "));
ip4_addr_debug_print(NETIF_DEBUG, gw);
#endif /* LWIP_IPV4 */
LWIP_DEBUGF(NETIF_DEBUG, ("\n"));
netif_invoke_ext_callback(netif, LWIP_NSC_NETIF_ADDED, NULL);
return netif;
}
与netif相关的底层函数
在ethernetifi.c中常用的几个底层函数
err_t ethernetif_init(struct netif *netif);
ethernetif_init()
函数是在上层管理网卡 netif 的到时候会被调用的函数,如使用 netif_add()
添加网卡的时候,就会调用 ethernetif_init()
函数对网卡进行初始化,其实该函数的最终调用的初始化函数就是low_level_init()
函数
static void low_level_init(struct netif *netif);
网卡初始化话函数,它主要完成网卡的复位及参数初始化,根据实际的网卡属性
进行配置 netif 中与网卡相关的字段,例如网卡的 MAC 地址、长度,最大发送单元等。调用我们自己实现的以太网驱动初始化函数 Bsp_Eth_Init()
,这是根据网卡的驱动所编写的函数,不同的网卡是不一样的,由用户实现,
初始化完成就需要启动网卡,才能进行数据的收发操作。
static err_t low_level_output(struct netif *netif, struct pbuf *p);
函数为网卡的发送函数,它主要将内核的数据包发送出去,数据包采用 pbuf 数据结构进行描述
static struct pbuf * low_level_input(struct netif *netif);
函数为网卡的数据接收函数,该函数会接收一个数据包,为了内核易于对数据包的管理,该函数必须将接收的数据封装成 pbuf 的形式。
LWIP 数据包
利用pbuf
来描述数据包,这个结构体在pbuf.h
中。
struct pbuf {
struct pbuf *next; //指向下一个 pbuf 结构体,可以构成链表
void *payload; //指向该 pbuf 真正的数据区
u16_t tot_len; //当前 pbuf 和链表中后面所有 pbuf 的数据长度,它们属于一个数据包
u16_t len; //当前 pbuf 的数据长度
u8_t type; //当前 pbuf 的类型
u8_t flags; //状态为,保留
u16_t ref; //该 pbuf 被引用的次数
};
next:指向下一个 pbuf
结构体,每个 pbuf 能存放的数据有限,如果应用有大量的数据的话,我们就需要多个 pbuf 来存放,我们将同一个数据包的 pbuf 连接在一起形成一个链表,那么 next字段就是实现这个链表的关键。
payload:指向该 pbuf 的数据存储区的首地址,STM32F407 内部网络模块接收到数据,并将数据提交给 LWIP 的时候,就是将数据存储在 payload 指定的存储区中。同样在发送数据的时候将 payload 所指向的存储区数据转给 STM32F407 的网络模块去发送。
tot_len:我们在接收或发送数据的时候数据会存放在 pbuf 链表中,tot_len 字段就表示当前pbuf 和链表中以后所有 pbuf 总的数据长度。
len:当前 pbuf 总数据的长度
type:当前 pbuf 类型,一共有四种:PBUF_RAM
、PBUF_ROM
、PBUF_REF
和 PBUF_POOL
。
flag:保留位
ref:该 pbuf 被引用的次数,当还有其他指针指向这个 pbuf 的时候 ref 字段就加一。
LWIP网络接口管理
在 LWIP 中对于网络接口的描述是通过一个 netif 结构体完成的,netif 结构体在netif.h 文件中有定义。
struct netif {
struct netif *next; //指向下过一个netif 结构体
ip_addr_t ip_addr; //网络接口IP 地址
ip_addr_t netmask; //子网掩码
ip_addr_t gw; //默认网关
netif_input_fn input; //IP 层接收数据函数
netif_output_fn output; //IP 层发送数据包调用
netif_linkoutput_fn linkoutput; //底层数据包发送
void *state; //设备的状态信息
u16_t mtu; //该网络接口最大允许传输的数据长度
u8_t hwaddr_len; //物理地址长度
u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //该网络接口的物理地址
u8_t flags; //该网络接口的状态和属性
char name[2]; //该网络接口的名字
u8_t num; //该网络接口的编号
}
添加中间文件
LWIP 文件夹可以发现有一个 arch 文件夹,在 arch文件中有 5 个文件 cc.h、cpu.h、perf.h、sys_arch.h
和 sys_arch.c
cc.h :主要完成协议内部使用的数据类型的定义
cpu.h :用来定义CPU 的大小端模式,因为STM32 是小端模式,因此这里定义BYTE_ORDER=LITTLE_ENDLAN
//小端模式
perf.h :是和系统测量与统计相关的文件,我们不使用任何的测量和统计
sys_arch.h :是在使用操作系统的时候才使用到的文件,获取时间的函数 sys_now(),为LWIP提供时钟。
sys_arch.c :用来定义CPU 的大小端模式,因为STM32 是小端模式,因此这里定义BYTE_ORDER=LITTLE_ENDLAN
//小端模式
开始移植
硬件 STM32F407VET6 PHY是DP83848(外接50Mhz)用RMII模式
REF_CLK: 参考时钟
TX_EN:发送使能
TXD[1:0]:发送数据
RXD[1:0]:接收数据
CRS_DV:载波监听,接收数据有效
RX_ED:接收错误(可选)
MDC:周期性时钟,提供以最大频率 2.5 MHz 传输数据时的参考时序,在空闲 状态下, SMI 管理接口将 MDC 时钟信号驱动为低电平。
MDIO:数据输入/输出比特流,用于通过 MDC 时钟信号向/从 PHY 设备同步传输状态 信息。
ETH_INT:这个引脚自己写的
在Cubemx中选用ETH