一、以太网协议简介
以太网是一种产生较早,使用相当广泛的局域网技术。目前以太网根据速度等级分类大概分为:标准以太网(10Mbit/s),快速以太网(100Mbit/s),千兆以太网(1000Mbit/s),以及更快的万兆以太网(10Gbit/s)。
以太网常用于大型数据传输(如:视频数据),以太网也叫以太网协议,就是一种传输规则,发收双方必须遵守这种规则才能正确地进行数据传输和接收。以太网通信是以数据包的形式传输, 其单包数据量达到几十, 甚至成百上千个字节。
下面图片是以太网通信的一帧数据格式,首先当用户发送数据时(应用层),将数据向下交给传输层,传输层会在数据前面加上传输层首部,然后向下交给网络层。 同样地,网络层会在数据前面加上网络层首部(IP首部), 然后将数据向下交给链路层,链路层会对数据进行最后一次封装, 即在数据前面加上链路层首部(此处使用以太网接口为例),然后将数据交给网卡。 最后,网卡将数据转换成物理链路上的电平信号,数据就这样被发送到了网络中。 当网络中的设备的网卡接收到某个数据包后,它会将其放置在网卡的接收缓存中,并告知TCP/IP内核。然后TCP/IP内核就开始工作了,它会将数据包从接收缓存中取出,并逐层解析数据包中的协议首部信息,并最终将数据交给某个应用程序。
(注意:TCP/IP是一个协议族,包含众多的协议。但对于网络应用开发人员,可能听到更多的是其中的应用层协议,比如HTTP、FTP、MQTT、TCP、UDP、IP等。)
IP地址的组成
IP地址 = 网络地址 + 主机地址
192.168.1.168(IP地址 ) = 192.168.1.0 (网络地址 ) + 0.0.0.168(主机地址)
它的作用是屏蔽(遮住)IP地址的一部分以划分成网络地址和主机地址两部分,并说明该IP地址是在局域网上,还是在远程网上。A类地址来说,默认的子网掩码是255.0.0.0;对于B类地址来说默认的子网掩码是255.255.0.0;对于C类地址来说默认的子网掩码是255.255.255.0。
A,B,C三类中既有公网地址,也有私网地址:在A类地址中,10.0.0.0-10.255.255.255是私有地址.在B类地址中,172.16.0.0-172.31.255.255是私有地址。在C类地址中,192.168.0.0-192.168.255.255是私有地址。
连接两个不同的网络的设备都可以叫网关设备;网关的作用就是实现两个网络之间进行通讯与控制。网关设备可以是 交互机(三层及以上才能跨网络)、路由器、启用了路由协议的服务器、代理服务器、防火墙等。
要实现这两个网络之间的通信,则必须通过网关。
如果网络A中的主机发现数据包的目的主机不在本地网络中,就把数据包转发给它自己的网关,再由网关转发给网络B的网关,网络B的网关再转发给网络B的某个主机(如附图所示)。网络A向网络B转发数据包的过程。
二、LWIP简介
Light weight IP(LWIP)轻量化的 TCP/IP协议, LwIP 的设计初衷是:用少量的资源消耗(RAM)实现一个较为完整的 TCP/IP 协议栈, 在保持TCP协议主要功能的基础上减少对 RAM 的占用。
2.1 LWIP提供三种API:
(1)RAW API:
把协议栈和应用程序放到一个进程里边,该接口基于函数回调技术,为了接收数据,应用程序会向协议栈注册一个回调函数。该回调函数与特定的连接相关联,当该关联的连接到达一个信息包,该回调函数就会被协议栈调用。
RAW/Callback API 的缺点:
基于回调函数开发应用程序时的思维过程比较复杂。在操作系统环境中, 应用程序代码与内核代码处于同一个线程,虽然能够节省任务间通信和切换任务的开销,但是相应地,应用程序的执行会制约内核程序的执行,不同的应用程序之间也会互相制约。
(2)NETCONN API:
基于操作系统的 IPC 机制(即信号量和邮箱机制) 实现的, 它的设计将 LwIP 内核代码和网络应用程序分离成了独立的线程。如此一来, LwIP 内核线程就只负责数据包的 TCP/IP 封装和拆封,而不用进行数据的应用层处理,大大提高了系统对网络数据包的处理效率。
NETCONN API 使用了操作系统的 IPC 机制, 对网络连接进行了抽象,用户可以像操作文件一样操作网络连接(打开/关闭、读/写数据)。 但是 NETCONN API 并不如操作文件的 API 那样简单易用。举个例子,调用 f_read 函数读文件时,读到的数据会被放在一个用户指定的数组中,用户操作起来很方便,而 NETCONN API 的读数据 API,就没有那么人性化了。 用户获得的不是一个数组,而是一个特殊的数据结构 netbuf,用户如果想使用好它,就需要对内核的 pbuf 和 netbuf 结构体有所了解。 NETCONN API 之所以采取这种不人性的设计,是为了避免数据包在内核程序和应用程序之间发生拷贝,从而降低程序运行效率。当然, 用户如果不在意数据递交时的效率问题, 也可以把 netbuf 中的数据取出来拷贝到一个数组中,然后去处理这个数组。
简单来说, NETCONN API 的优缺点是:相较于 RAW/Callback API, NETCONN API 简化了编程工作,使用户可以按照操作文件的方式来操作网络连接。 但相较于 Socket API, NETCONN API 避免了内核程序和网络应用程序之间的数据拷贝,提高了数据递交的效率。
(3)SOCKET API:
Socket,即套接字,它对网络连接进行了高级的抽象,使得用户可以像操作文件一样操作网络连接。它十分易用, 许多网络开发人员最早接触的就是 Socket 编程, Socket 已经成为了网络编程的标准。在不同的系统中,运行着不同的 TCP/IP 协议,但是只要它实现了Socket 的接口,那么用 Socket 编写的网络应用程序就能在其中运行。可见用 Socket 编写的网络应用程序具有很好的可移植性。
它的最大特点是使应用程序移植到其它系统时比较容易,但内核程序和应用程序之间通信存在数据的拷贝,这会降低数据递交的效率。 在嵌入式设备中一般不会使用。
2.2 MII与RMII接口
2.2.1 MII接口
独立于介质的接口(MII)用于MAC与外接的PHY互联,支持10Mbit/s和100Mbit/s数据传输模式。MII的信号线如下图所示:
MII_TX_CLK:发送数据使用的时钟信号,对于10M位/s的数据传输,此时钟为2.5MHz,对于100M位/s的数据传输,此时钟为25MHz。
MII_RX_CLK:接收数据使用的时钟信号,对于10M位/s的数据传输,此时钟为2.5MHz,对于100M位/s的数据传输,此时钟为25MHz。
MII_TX_EN:传输使能信号,此信号必需与数据前导符的起始位同步出现,并在传输完毕前一直保持。
MII_TXD[3:0]:发送数据线,每次传输4位数据,数据在MII_TX_EN信号有效时有效。MII_TXD[0]是数据的最低位,MII_TXD[3]是最高位。当MII_TX_EN信号无效时,PHY忽略传输的数据。
MII_CRS:载波侦听信号,仅工作在半双工模式下,由PHY控制,当发送或接收的介质非空闲时,使能此信号。 PHY必需保证MII_CRS信号在发生冲突的整个时间段内都保持有效,不需要此信号与发送/接收的时钟同步。
MII_COL:冲突检测信号,仅工作在半双工模式下,由PHY控制,当检测到介质发生冲突时,使能此信号,并且在整个冲突的持续时间内,保持此信号有效。此信号不需要和发送/接收的时钟同步。
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_RX_DV:接收数据使能信号,由PHY控制,当PHY准备好数据供MAC接收时,使能该信号。此信号必需和帧数据的首位同步出现,并保持有效直到数据传输完成。在传送最后4位数据后的第一个时钟之前,此信号必需变为无效状态。为了正确的接收一个帧,有效电平不能滞后于数据线上的SFD位出现。
MII_RX_ER:接收出错信号,保持一个或多个时钟周期(MII_RX_CLK)的有效状态,表明MAC在接收过程中检测到错误。具体错误原因需配合MII_RX_DV的状态及MII_RXD[3:0]的数据值。
2.2.2 RMII接口
精简的独立于介质接口(RMII)规范减少了以太网通信所需要的引脚数。根据IEEE802.3标准,MII接口需要16个数据和控制信号引脚,而RMII标准则将引脚数减少到了7个。RMII具有以下特性:
时钟信号需要提高到50MHz。
MAC和外部的以太网PHY需要使用同样的时钟源
使用2位宽度的数据收发
2.2.3 RMII和MII的区别
MII上的发送/接收的4位数据,在RMII上以2个2位数据的形式发送/接收。方式为:先发送/接收低2位,再发送/接收高2位。
MII时钟源 :为了产生TX_CLK和RX_CLK时钟信号,外接的PHY模块必需有来自外部的25MHz时钟驱动。该时钟不需要与MAC时钟相同。可以使用外部的25MHz晶体或者微控制器的MCO引脚提供这一时钟。注意:STM32的PA.8引脚具有复用功能------时钟输出(MCO), 该功能能将STM32内部的时钟通过PA.8输出.GD32类似于STM32
操作流程:
1)、设置PA.8为复用Push-Pull模式。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
2)、选择输出时钟源。
时钟的选择由时钟配置寄存器(RCC_CFGR)中的MCO[2:0]位控制。
RCC_MCOConfig(RCC_MCO);
参数RCC_MCO为要输出的内部时钟:
RCC_MCO_NoClock --- 无时钟输出
RCC_MCO_SYSCLK --- 输出系统时钟(SysCLK)
RCC_MCO_HSI --- 输出内部高速8MHz的RC振荡器的时钟(HSI)
RCC_MCO_HSE --- 输出高速外部时钟信号(HSE)
RCC_MCO_PLLCLK_Div2 --- 输出PLL倍频后的二分频时钟(PLLCLK/2)
RMII时钟源 :通过将相同的时钟源接到MAC和以太网PHY的REF_CLK引脚保证两者时钟源的同步。可以通过外部的50MHZ信号或者GD32F107xx微控制器的MCO引脚提供这一时钟。当时钟来源MCO引脚时需配置合适的PLL,保证MCO引脚输出的时钟为50MHZ。
三、LWIP和以太网协议区别
以太网协议
数据链路层:以太网协议位于OSI模型的第二层(数据链路层)。它负责点对点的物理和逻辑连接,定义了MAC地址、帧格式、冲突检测(CSMA/CD)等。以太网用于在局域网内传递数据。
功能:以太网协议用于在局域网中传输数据包,它将网络层的数据包(如IP数据包)封装到以太网帧中并通过物理介质发送。这意味着以太网负责在本地网络设备之间传输数据。
lwIP(轻量级IP)
网络层及以上:lwIP是一个开源的TCP/IP协议栈,主要用于嵌入式系统。它实现了TCP/IP协议族,允许设备通过IP协议进行网络通信,操作在OSI模型的网络层及以上。
功能:lwIP提供了一组完整的网络通信功能,包括TCP、UDP、IP、ICMP等。它能处理通过以太网、Wi-Fi等不同介质传输的数据包。
TCP/IP协议有五层,应用层,传输层,网络层,数据链路层和物理层,其中
- 前三层由LWIP组成
- 数据链路层由芯片自带的10/100(Mbit/s)的以太网MAC内核组成
- 物理层由PHY芯片(LAN8720A或者YT8512C等,用于接收电、光这类模拟信号)控制
四、LWIP移植
4.1 lwip移植大体过程
LwIP实现了网络互联层与传输层的主要协议,同时也提供了部分主要的应用层协议。而底层的网络接口层,最常见的是以太网,其分为MAC与PHY两个部分,一般由网卡负责实现,我们移植过程中的主要工作,就是将网卡驱动提供的接口包装成LwIP要求的样子,然后提供给LwIP的网络接口层使用。
第一步:初始化网口的GPIO端口。gd32f4xx_enet_eval.c 用来初始化GPIO引脚,时钟,NVIC分组配置。
第二步:配置以太网的收发工作(底层驱动),ethernetif.c文件,用来硬件的初始化low_level_init,数据的接收low_level_input和发送low_level_output。
第三步:LwIP时钟安装。sys_now函数,此函数用于获取当前系统的嘀嗒计时时间,用于LwIP处理超时事件,同时TCP协议中的一些超时判断机制也会用到它,返回毫秒单位。
第四步:初始化整个协议栈,并且以轮询或中断的方式定期将收到的数据包上传给LwIP处理netconf.c文件。
4.2 lwip常用函数讲解
- lwip_stack_init函数:初始化lwip;加入网卡的ip,子网掩码和网关;将网卡注册进LWIP,设置其为默认网络接口,使能网卡;
- lwip_periodic_handle函数:周期性函数,用来处理接收的数据和处理LwIP的超时事件。
- sys_check_timeouts函数:先获取当前时间,通过当前时间与上一次定时器超时时间的差值得知距离上一次定时事件触发的时间差,根据该时间差检查定时链表上有哪些定时事件超时了,对于超时的定时器结构调用并执行其注册的回调函数,然后将超时的定时器从链表上移除并释放其占用的内存资源,如果链表上有多个定时器超时则按上述处理所有超时的定时事件。
- tcp_tmr函数:ARP层内核会以5秒为周期去调用etharp_tmr,它会将每个ARP缓存表项的ctime字段值加1,当相应表项的生存时间计数值ctime大于系统规定的某个值时,系统将删除对应的表项。 etharp_tmr函数保证了在ARP缓存表大小有限的情况下,尽量提高其使用效率,及时删除那些旧的、不用的配对信息
- tcp_tmr函数:
cpp
void tcp_tmr(void)
{
/* Call tcp_fasttmr() every 250 ms */
tcp_fasttmr();//每250ms处理延时发送的ack报文和fin报文,同时通知上层应用处理数据
if (++tcp_timer & 1) {
/* Call tcp_slowtmr() every 500 ms, i.e., every other timer
tcp_tmr() is called. */
tcp_slowtmr();//每500ms调用,该函数完成了超时重传,tcp保活功能,并会遍历active和timewait链表的PCB,删除那些超时或者出错的PCB,同时将PCB中unsent队列中的数据发送出去。一般使用tcp_write();写入数据后,数据不会马上发送,而是在定时任务中发送。
//保活keepalive:服务端需要检查客户端是否还能通信,若两小时内无通信,客户端发送探查报文,若客户端ack,则更新保活计时器,否则,每隔75s发送一个探查报文,若发送超过9个报文,则认为客户端已挂掉
}
}
void tcp_fasttmr(void)
{
struct tcp_pcb *pcb;
++tcp_timer_ctr;
tcp_fasttmr_start:
pcb = tcp_active_pcbs; //在active中遍历
while (pcb != NULL) {
if (pcb->last_timer != tcp_timer_ctr) {
struct tcp_pcb *next;
pcb->last_timer = tcp_timer_ctr;
//发送延时的ack
if (pcb->flags & TF_ACK_DELAY) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: delayed ACK\n"));
tcp_ack_now(pcb);
tcp_output(pcb);
pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
}
//发送延时的fin
if (pcb->flags & TF_CLOSEPEND) {
LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: pending FIN\n"));
pcb->flags &= ~(TF_CLOSEPEND);
tcp_close_shutdown_fin(pcb);
}
next = pcb->next;
//若当前tcp有未被上层应用接收的数据
if (pcb->refused_data != NULL) {
tcp_active_pcbs_changed = 0;
tcp_process_refused_data(pcb); //通过回调函数使上层处理数据
if (tcp_active_pcbs_changed) {
goto tcp_fasttmr_start;
}
}
pcb = next; //下一个
} else {
pcb = pcb->next;
}
}
}
4.3 GD32F470ZGT6移植LWIP协议
第一步,官网下载例程代码
GD32F4xx_Demo_Suites_V2.6.4\GD32F4xx_Demo_Suites_V2.6.4\GD32470Z_EVAL_Demo_Suites\Projects\26_ENET\Projects\Raw_tcpudp\MDK-ARM
第二步,编译无错误无警告
第三步,修改网卡的GPIO初始化,gd32f4xx_enet_eval文件中的enet_gpio_config函数
(1)网卡时钟问题,首先确定网卡是哪种接口,其次是否由MCO提供时钟,例程中使用PA8给ENET_RMII_REF_CLK提供50MHZ时钟,而针对自己的原理图来说,有外部晶振提供时钟源
(2)根据自己原理图上面的引脚一一对应修改
第四步,修改LWIP初始化,lwip_stack_init函数,修改IP,子网掩码和网关
五、常见协议作用
5.1 DHIP协议
动态分配IP地址,过程自动化,终端无需一一手工配置,配置信息统一管理(DNS,网关),IP地址有限、需要大量配置IP地址、经常移动终端。
分配IP地址方式:
(1)手工配置方式:通过网络管理员手工配置某台客户端特定的IP地址,当客户端请求分配时,DHCP服务器就将手动配置的IP地址分配给客户端。。
(2)自动配置方式:当DHCP客户端第一次想服务端租用到第一个IP地址后,就将这个IP地址永久分配给客户端使用。
(3)动态配置方式:服务器暂时分配一个IP地址给客户端,根据租约到期或者续约租期的方式来管理分配的IP地址。