IOT项目之网卡驱动

系列文章目录

IOT的项目实践【序言】



前言

本文主要介绍了网卡底层驱动和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实现网络通信。
以上就是调试网卡进行以太网网络通信需要提前了解的内容,以及简单介绍了相关的理论知识和部分工程的代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值