系列文章目录
文章目录
前言
本文主要介绍了网卡底层驱动和TCP/IP协议的理论和应用。(本文较长,大约需要30分钟读完)
一、前期准备知识
TCP/IP 协议
互联网协议族(Internet Protocol Suite)是一个网络通信模型,以及一整个网络传输协议家族,为互联网的基础通信架构。它常被通称为TCP/IP协议族(TCP/IP Protocol Suite,或TCP/IP Protocols),简称TCP/IP。
TCP/IP提供点对点的连接机制,将数据应该如何封装、定址、传输、路由以及在目的地如何接收,都加以标准化。它将软件通信过程抽象化为四个抽象层,采取协议堆栈的方式,分别实现不同通信协议。四个抽象层分别为:应用层,传输层,网络层和链路层。
当用户发送数据时,将数据向下交给传输层,这是处于应用层的操作,应用层可以通过调用传输层的接口来编写特定的应用程序。而TCP/IP 协议一般也会包含一些简单的应用程序如Telnet 远程登录、FTP 文件传输、SMTP 邮件传输协议等。传输层会在数据前面加上传输层首部(此处以TCP 协议为例,下图中的传输层首部为TCP 首部,也可以是UDP 首部),然后向下交给网络层。同样地,网络层会在数据前面加上网络层首部(IP 首部),然后将数据向下交给链路层,链路层会对数据进行最后一次封装,即在数据前面加上链路层首部(此处使用以太网接口为例),然后将数据交给网卡。最后,网卡将数据转换成物理链路上的电平信号,数据就这样被发送到了网络中。数据的发送过程,可以概括为TCP/IP 的各层协议对数据进行封装的过程,如下图所示。
当设备的网卡接收到某个数据包后,它会将其放置在网卡的接收缓存中,并告知TCP/IP 内核。然后TCP/IP 内核就开始工作了,它会将数据包从接收缓存中取出,并逐层解析数据包中的协议首部信息,并最终将数据交给某个应用程序。数据的接收过程与发送过程正好相反,可以概括为TCP/IP 的各层协议对数据进行解析的过程。
OSI 模型
开放系统互连参考模型 (Open System Interconnection Reference Model,简称OSI)是国际标准化组织ISO和国际电报电话咨询委员会CCITT联合制定的开放系统互连参考模型,为开放式互连信息系统提供了一种功能结构的框架。它从低到高分别是:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。下表显示了TCP/IP和其它的协议在OSI模型中的大致位置。
OSI模型 | 功能描述 |
---|---|
应用层 | 应用层服务,比如:HTTP、SMTP、SNMP、FTP、Telnet、SIP、SSH、NFS、RTSP、XMPP、Whois、ENRP |
表示层 | 数据的表示形式,加密和解密,把机器相关的数据转换成独立于机器的数据。规定数据的格式化表示,数据格式的转换等,比如:XDR、ASN.1、SMB、AFP、NCP |
会话层 | 为建立会话式通信,控制数据正确地接收和发送,比如:ASAP、SSH、ISO 8327 / CCITT X.225、RPC、NetBIOS、ASP、IGMP、Winsock、BSD sockets |
传输层 | 控制数据传输的顺序、传送错误的恢复等,保证通信的品质,比如:TCP、UDP、TLS、RTP、SCTP、SPX、ATP、IL |
网络层 | 进行数据传送的路由选择或中继,比如:IP、ICMP、IPX、BGP、OSPF、RIP、IGRP、EIGRP、ARP、RARP、X.25 |
数据链路层 | 将物理层收到的信号(位序列)组成有意义的数据,提供传输错误控制等数据传输控制流程,比如:以太网、令牌环、HDLC、帧中继、ISDN、ATM、IEEE 802.11、FDDI、PPP |
物理层 | 规定了通信时使用的电缆、连接器等的电气规格,以实现设备间的信号传送,比如:线路、无线电、光纤 |
以太网和 IEEE 802.3
以太网Ethernet是一种计算机局域网技术,由Xerox公司创建并由Xerox、Intel和DEC公司联合开发的基带局域网规范。以太网与IEEE802.3系列标准相类似,IEEE组织的IEEE 802.3标准制定了以太网的技术标准,它规定了包括物理层的连线、电子信号和介质访问层协议的内容。以太网是目前应用最普遍的局域网技术,取代了其他局域网标准。
根据传输速率的不同,主要分为标准以太网(10M)、快速以太网(100M)和千兆以太网。
在IEEE 802.3标准中,为不同的传输介质制定了不同的物理层标准,在这些标准中前面的数字表示传输速度,单位是“Mbps”,最后的一个数字表示单段网线长度(基准单位是100米),Base表示“基带”的意思,Broad代表“宽带”。比如10BASE-5表示传输速度为10Mbit/s,最大传输距离为500米。
二、网卡芯片
常见的网卡芯片示意图如下图所示,MAC一般是集成在MCU里面,通过总线和CPU通信,比如SPI。单独的MAC无法进行网络通信,还需要外接一个PHY芯片,MAC与PHY的连接接口一般为MII或RMII。MII 包括 16 个数据和控制信号的引脚。 而RMII 规范将引脚数减少为 7 个。(关于MII和RMII在这里就不过多介绍,感兴趣的小伙伴可以自己去了解一下)
1.MAC
媒体访问控制(MAC,Media Access Control),又称作介质访问控制,简称MAC,是局域网中数据链路层的下层部分,提供地址及媒体访问的控制方式,使得不同设备或网络上的节点可以在多点的网络上通信,而不会互相冲突,上述的特性在局域网中格外重要。早期网络发展时以MAC判别各个网络接口的位置,但后来互联网发展后,才有IP的制定与使用。若只是两台设备之间全双工的通信,因为两台设备可以同时发送及接收数据,不会冲突,因此不需要用到MAC协议。
媒体访问控制MAC子层负责解决与媒体接入有关的问题,在物理层的基础上进行无差错的通信。 MAC子层是网络与设备的接口,它从网络层接收数据帧,然后通过媒体访问规则和物理层将数据帧发送到物理链路上。它也从物理层接收数据帧,再送到网络层。总的来说,MAC有三大功能:
- 决定节点何时发送数据包(核心)。
- 将数据帧发送到物理层,然后发送到物理链路。
- 从物理层接收数据帧,然后送给网络层处理。
MAC地址,又称为物理地址、硬件地址,用来定义网络设备的位置。在OSI模型中,第三层网络层负责 IP地址,第二层数据链路层则负责 MAC地址。因此一个主机会有一个MAC地址,而每个网络位置会有一个专属于它的IP地址。 MAC地址长度是48bit(6字节),由16进制的数字组成,下面是几个常见的MAC地址:
- 00:xx:xx:xx:xx:xx是MAC单播地址。
- 01:xx:xx:xx:xx:xx是MAC组播地址。
- 01:00:5e:xx:xx:xx是IPv4组播地址。
- ff:ff:ff:ff:ff:ff则作为广播地址。
- 00:50:c2:xx:xx:xx 是意法半导体的MAC地址。
2.PHY
PHY(Port Physical Layer),即端口物理层,是一个对OSI模型物理层的简称。现在常用于STM32的有DP83848,LAN8270,DM9161/9162等。这些PHY芯片都大同小异,基本寄存器都是一样的,只有扩展寄存和厂商专门设置的寄存器不同。如果用户将其中一个PHY驱动成功了,驱动另一个也是非常方便的。
具体管理和控制动作是通过读写PHY内部的寄存器实现的。PHY寄存器的地址空间为5位,从0到31最多可以定义32个寄存器(随着芯片功能不断增加,很多PHY芯片采用分页技术来扩展地址空间以定义更多的寄存器,在此不作讨论),IEEE802.3定义了地址为0-15这16个寄存器的功能,地址16-31的寄存器留给芯片制造商自由定义。
三、工程示例
1.PHY与MAC通信(物理层与数据链路层)
CMSIS-Driver软件包中已经封装好了常用的网卡芯片驱动库,包含ETH开头的驱动文件(这些芯片是MAC+PHY)和PHY开头的驱动文件(仅有PHY驱动的芯片)。如果很幸运你的单片机网卡芯片型号正好没有,需要我们自己去实现,可以仿照驱动库自己编写,代码需要实现的函数大同小异,下面是一个PHY驱动的实例。
//用于获取当前的 PHY 驱动 版本。
static ARM_DRIVER_VERSION GetVersion (void) {
return DriverVersion;
}
//初始化读写PHY 芯片所需要 的 API
static int32_t Initialize (ARM_ETH_PHY_Read_t fn_read, ARM_ETH_PHY_Write_t fn_write) {
if ((fn_read == NULL) || (fn_write == NULL)) {
return ARM_DRIVER_ERROR_PARAMETER; }
if ((PHY.flags & PHY_INIT) == 0U) {
/* Register PHY read/write functions.*/
PHY.reg_rd = fn_read;
PHY.reg_wr = fn_write;
PHY.bmcr = 0U;
PHY.flags = PHY_INIT;
return ARM_DRIVER_OK;
}
//复位读写 PHY 芯片所需要 的 API 。
static int32_t Uninitialize (void) {
PHY.reg_rd = NULL;
PHY.reg_wr = NULL;
PHY.bmcr = 0U;
PHY.flags = 0U;
return ARM_DRIVER_OK;
}
//用于控制 PHY 的 上电和断电。
static int32_t PowerControl (ARM_POWER_STATE state) {
uint16_t val;
switch ((int32_t)state) {
// 将 PHY 断电
case ARM_POWER_OFF:
// 初始化 状态才可以 配置 POWER OFF
if ((PHY.flags & PHY_INIT) == 0U) {
return ARM_DRIVER_ERROR;
}
PHY.flags &= ~PHY_POWER;
PHY.bmcr = BMCR_POWER_DOWN;
// 设置 BMCR 寄存器 ,断电
return (PHY.reg_wr(ETH_PHY_ADDR, REG_BMCR, PHY.bmcr));
// PHY 上电 ,并清除 BMCR 寄存器
case ARM_POWER_FULL:
// 初始化 状态才可以 配置 POWER FULL
if ((PHY.flags & PHY_INIT) == 0U) {
return ARM_DRIVER_ERROR;
}
// 已经 处于 POWER ON 状态 ,直接返回 OK
if (PHY.flags & PHY_POWER) {
return ARM_DRIVER_OK;
}
// 读取 设备
PHY.reg_rd(ETH_PHY_ADDR, REG_PHYIDR1, &val);
// 读取 ID1
if (val != PHY_ID1) {
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
PHY.reg_rd(ETH_PHY_ADDR, REG_PHYIDR2, &val);
// 读取 ID2, 此处 做了一个特别处理, 屏蔽 后面 8 个 bit ,方便 D M9162 和 DM9161 都可以 识别,因为这两个PHY 后面 的 ID 不同 。
if ((val & 0xFF00) != PHY_ID2) {
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
// BMCR 寄存器 清零
if (PHY.reg_wr(ETH_PHY_ADDR, REG_BMCR, PHY.bmcr) != ARM_DRIVER_OK) {
return ARM_DRIVER_ERROR;
}
PHY.flags |= PHY_POWER;
return ARM_DRIVER_OK;
case ARM_POWER_LOW:
default:
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
}
//用于配置使用 SMII RMII 还是 MII 接口 外接的 PHY 芯片 。
static int32_t SetInterface (uint32_t interface) {
int32_t status;
if ((PHY.flags & PHY_POWER) == 0U) { return ARM_DRIVER_ERROR; }
// 仅作 了 RMII 接口支持
switch (interface) {
case ARM_ETH_INTERFACE_RMII: status = ARM_DRIVER_OK; break;
default:
status = ARM_DRIVER_ERROR_UNSUPPORTED; break
}
return (status);
}
2.MAC与MCU通信(数据链路层)
和PHY的驱动一样,CMSIS-Driver库中有常用的MAC的驱动库,如果你的没有你使用的型号,同样的道理,也需要仿照驱动库完成使用的对应的MAC驱动库。
MAC与MCU的通信的基础是MCU的总线,这里以SPI协议为例,MCU通过SPI实现对MAC寄存器的操作,进而打通MCU与PHY底层的通信。
static uint16_t reg_rd (uint8_t reg) {
uint8_t buf[2];
buf[0] = SPI_CMD_REG_READ;
if (reg & 2) buf[0] |= 0x3 << 4;
else buf[0] |= 0x3 << 2;
buf[0] |= reg >> 6;
buf[1] = reg << 2;
ptrSPI->Control (ARM_SPI_CONTROL_SS, ARM_SPI_SS_ACTIVE);
ptrSPI->Send (buf, 2);
while (ptrSPI->GetStatus().busy);
ptrSPI->Receive (buf, 2);
while (ptrSPI->GetStatus().busy);
ptrSPI->Control(ARM_SPI_CONTROL_SS, ARM_SPI_SS_INACTIVE);
return ((buf[1] << 8) | buf[0]);
}
static void reg_wr (uint8_t reg, uint16_t val) {
uint8_t buf[4];
buf[0] = SPI_CMD_REG_WRITE;
if (reg & 2) buf[0] |= 0x3 << 4;
else buf[0] |= 0x3 << 2;
buf[0] |= reg >> 6;
buf[1] = reg << 2;
buf[2] = (uint8_t)val;
buf[3] = (uint8_t)(val >> 8);
ptrSPI->Control (ARM_SPI_CONTROL_SS, ARM_SPI_SS_ACTIVE);
ptrSPI->Send (buf, 4);
while (ptrSPI->GetStatus().busy);
ptrSPI->Control(ARM_SPI_CONTROL_SS, ARM_SPI_SS_INACTIVE);
}
static ARM_DRIVER_VERSION MAC_GetVersion (void) {
return MAC_DriverVersion;
}
static int32_t MAC_Initialize (ARM_ETH_MAC_SignalEvent_t cb_event) {
(void)cb_event;
if (ETH.flags & ETH_INIT) { return ARM_DRIVER_OK; }
/* Initialize SPI interface */
ptrSPI->Initialize (NULL);
ETH.flags = ETH_INIT;
return ARM_DRIVER_OK;
}
static int32_t MAC_Uninitialize (void) {
ETH.flags = 0;
/* Un-initialize SPI interface */
ptrSPI->Uninitialize ();
return ARM_DRIVER_OK;
}
static int32_t MAC_PowerControl (ARM_POWER_STATE state) {
uint16_t val;
switch ((int32_t)state) {
case ARM_POWER_OFF:
ETH.flags &= ~ETH_POWER;
/* Disable interrupts */
reg_wr (REG_IER, 0);
/* Disable Rx and flush Rx queue */
reg_wr (REG_RXCR1, REG_RXCR1_FRXQ);
reg_wr (REG_RXQCR, 0);
reg_wr (REG_RXCR1, 0);
/* Disable Tx and flush Tx queue */
reg_wr (REG_TXCR, REG_TXCR_FTXQ);
reg_wr (REG_TXQCR, 0);
reg_wr (REG_TXCR, 0);
/* Enable Power Save Mode */
reg_wr (REG_PMECR, REG_PMECR_PSAVE);
ptrSPI->PowerControl(ARM_POWER_OFF);
break;
case ARM_POWER_LOW:
return ARM_DRIVER_ERROR_UNSUPPORTED;
case ARM_POWER_FULL:
if ((ETH.flags & ETH_INIT) == 0) {
return ARM_DRIVER_ERROR;
}
if (ETH.flags & ETH_POWER) {
return ARM_DRIVER_OK;
}
ptrSPI->PowerControl(ARM_POWER_FULL);
ptrSPI->Control (ARM_SPI_MODE_MASTER | ARM_SPI_CPOL0_CPHA0 |
ARM_SPI_MSB_LSB | ARM_SPI_SS_MASTER_SW |
ARM_SPI_DATA_BITS(8), ETH_SPI_BUS_SPEED);
/* Check Device Identification. */
val = reg_rd (REG_CIDER);
if ((val & 0xFFF0) != PHY_ID) {
/* Wrong PHY device. */
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
/* Reset device */
reg_wr (REG_GRR, REG_GRR_GSR);
reg_wr (REG_GRR, 0);
/* Enable Tx Frame Data Pointer Auto Increment */
reg_wr (REG_TXFDPR, REG_TXFDPR_TXFPAI);
/* Flush TX queue */
reg_wr (REG_TXCR, REG_TXCR_FTXQ);
/* Enable QMU transmit */
ETH.txcr = REG_TXCR_TXCE | REG_TXCR_TXPE | REG_TXCR_TXFCE;
reg_wr (REG_TXCR, ETH.txcr);
reg_wr (REG_TXQCR, REG_TXQCR_TXQMAM);
/* Enable Rx Frame Data Pointer Auto Increment */
reg_wr (REG_RXFDPR, REG_RXFDPR_RXFPAI);
/* Configure Receive Frame Threshold for one frame */
reg_wr (REG_RXFCTR, 1);
/* Flush RX queue */
reg_wr (REG_RXCR1, REG_RXCR1_FRXQ);
/* Accept unicast packets and enable QMU receive */
ETH.rxcr1 = REG_RXCR1_RXPAFMA | REG_RXCR1_RXUE |
REG_RXCR1_RXMAFMA | REG_RXCR1_RXME | REG_RXCR1_RXFCE;
reg_wr (REG_RXCR1, ETH.rxcr1);
ETH.rxcr2 = REG_RXCR2_IUFFP | REG_RXCR2_UDPLFE | REG_RXCR2_SRDBL_FRM;
reg_wr (REG_RXCR2, ETH.rxcr2);
/* Enable RX Frame Count Threshold and Auto-Dequeue */
ETH.rxqcr = REG_RXQCR_RXFCTE | REG_RXQCR_ADRFE;
reg_wr (REG_RXQCR, ETH.rxqcr);
ETH.rx_count = 0;
ETH.tx_id = 0;
ETH.tx_len = 0;
ETH.flags |= ETH_POWER;
/* Enable receive interrupts */
reg_wr (REG_IER, REG_IER_RXIE | REG_IER_RXOIE);
/* Clear interrupt status */
reg_wr (REG_ISR, 0xFFFF);
break;
default:
return ARM_DRIVER_ERROR_UNSUPPORTED;
}
return ARM_DRIVER_OK;
}
static int32_t MAC_GetMacAddress (ARM_ETH_MAC_ADDR *ptr_addr) {
uint16_t val;
if (ptr_addr == NULL) {
return ARM_DRIVER_ERROR_PARAMETER;
}
if ((ETH.flags & ETH_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
val = reg_rd (REG_MARL);
ptr_addr->b[5] = (uint8_t)val;
ptr_addr->b[4] = (uint8_t)(val >> 8);
val = reg_rd (REG_MARM);
ptr_addr->b[3] = (uint8_t)val;
ptr_addr->b[2] = (uint8_t)(val >> 8);
val = reg_rd (REG_MARH);
ptr_addr->b[1] = (uint8_t)val;
ptr_addr->b[0] = (uint8_t)(val >> 8);
return ARM_DRIVER_OK;
}
static int32_t MAC_SendFrame (const uint8_t *frame, uint32_t len, uint32_t flags) {
uint8_t hdr[4];
uint32_t tick;
int32_t err;
if ((frame == NULL) || (len == 0U)) {
return ARM_DRIVER_ERROR_PARAMETER;
}
if ((ETH.flags & ETH_POWER) == 0U) {
return ARM_DRIVER_ERROR;
}
if (ETH.tx_len == 0) {
/* Start of a new transmit frame */
if ((reg_rd (REG_TXMIR) & 0x1FFF) < ETH_BUF_SIZE) {
/* Tx is busy, not enough memory free in Tx queue */
return ARM_DRIVER_ERROR_BUSY;
}
if (flags & ARM_ETH_MAC_TX_FRAME_FRAGMENT) {
/* Fragmented frame, copy to local buffer */
memcpy (tx_buf, frame, len);
ETH.tx_len = len;
return ARM_DRIVER_OK;
}
}
else {
/* Sending frame fragments in progress */
memcpy (tx_buf + ETH.tx_len, frame, len);
len += ETH.tx_len;
if (flags & ARM_ETH_MAC_TX_FRAME_FRAGMENT) {
/* More fragments to come */
ETH.tx_len = len;
return ARM_DRIVER_OK;
}
frame = tx_buf;
}
/* Disable interrupts */
reg_wr (REG_IER, 0);
/* Set Start DMA Access bit in RXQCR */
reg_wr (REG_RXQCR, ETH.rxqcr | REG_RXQCR_SDA);
ptrSPI->Control (ARM_SPI_CONTROL_SS, ARM_SPI_SS_ACTIVE);
hdr[0] = SPI_CMD_TXQ_FIFO_WR;
ptrSPI->Send (hdr, 1);
while (ptrSPI->GetStatus().busy);
/* Send TX frame header */
hdr[0] = ETH.tx_id++ & 0x3F;
hdr[1] = 0;
hdr[2] = (uint8_t)len;
hdr[3] = (uint8_t)(len >> 8);
ptrSPI->Send (hdr, 4);
while (ptrSPI->GetStatus().busy);
/* Send frame data, add dummy bytes to align to 4 bytes */
ptrSPI->Send (frame, (len + 3) & ~3u);
while (ptrSPI->GetStatus().busy);
ptrSPI->Control (ARM_SPI_CONTROL_SS, ARM_SPI_SS_INACTIVE);
/* Clear Start DMA Access bit in RXQCR */
reg_wr (REG_RXQCR, ETH.rxqcr);
reg_wr (REG_TXQCR, REG_TXQCR_TXQMAM | REG_TXQCR_METFE);
/* Wait until transmit done */
err = ARM_DRIVER_ERROR_TIMEOUT;
tick = get_sys_tick();
do {
if (!(reg_rd (REG_TXQCR) & REG_TXQCR_METFE)) {
/* Enqueue bit cleared, frame sent */
err = ARM_DRIVER_OK;
break;
}
}
while ((get_sys_tick() - tick) < get_tx_timeout());
/* Enable interrupts */
reg_wr (REG_IER, REG_IER_RXIE | REG_IER_RXOIE);
return err;
}
函数过多就不一一列出...
3.TCP/IP协议(传输层、网络层)
TCP/IP是通过使用现有的开源协议栈来移植的,本工程是用lwip协议栈,移植的过程也有很多的内容需要注意,后面打算单独拎出来讲讲。
在这一部分,MCU将要发送的数据,用lwip协议栈封装好网络帧头(即实现了传输层和网络层),然后通过MAC、PHY发送出去。
4.应用层
这一块就是要交互的数据,通过何种应用层协议发送,比如通过MQTT协议封装好帧头后,再扔进lwip协议栈封装帧头,再通过MAC/PHY发送到网络中。
总结
好了,完成上述几个部分,就可以通过MCU实现网络通信。
以上就是调试网卡进行以太网网络通信需要提前了解的内容,以及简单介绍了相关的理论知识和部分工程的代码。