二、嵌入式之网络模块调试


1 说明

在不同的平台,会使用不同的网络组件,形成具有集成不同功能种类的网络协议。

在Linux上,一般会有很齐全的协议。
在小型嵌入式上(有限的ram和rom),会使用uip或者lwip这些开源的协议栈。

1.1 从windows上看网络模块

从windows上的网卡的属性看,其可供参考的条目有:

  • ARP减负
  • IPv4硬件校验和
  • NS减负
  • TCP硬件校验和(IPv4)
  • TCP硬件校验和(IPv6)
  • UDP硬件校验和(IPv4)
  • UDP硬件校验和(IPv6)
  • 传送缓冲区
  • 大量传送减负 v2(IPv4)
  • 大量传送减负 v2(IPv6)
  • 关机 网络唤醒
  • 环保节能
  • 接收端调整
  • 接收端调整最大伫列
  • 接收缓冲区
  • 节能乙太网路
  • 巨型帧
  • 连接速度和双工模式
  • 流控制
  • 魔术封包唤醒
  • 网络地址
  • 网络唤醒和关机连接速度
  • 样式比对唤醒
  • 优先级和VLAN
  • 中断调整
  • 自动关闭 Gigabit

1.2 网络基础知识

1.2.1 单播,多播(组播)和广播

网络上的单播,即Unicast。

1.2.2 简单服务发现协议

即Simple Service Discovery Protocol,是一种应用层协议。是构成通用即插即用(UPnP)技术的核心协议之一。

1.2.3 MAC地址

即Medium Access Control,为介质访问控制,它通常被固化在每个以太网网卡(NIC,Network Interface Card)。MAC(硬件)地址长48位(6字节),采用十六进制格式,下图说明了48位的MAC地址及其组成部分。

二、嵌入式之网络模块调试_第1张图片
MAC地址格式
  • 组织唯一标识符(OUI)由IEEE(电气和电子工程师协会)分配给厂商,它包含24位。厂商再用剩下的24位(EUI,扩展唯一标识符)为其生产的每个网卡分配一个全球唯一的全局管理地址,一般来说大厂商都会购买多个OUI。
  • I/G(Individual/Group)位,如果I/G=0,则是某台设备的MAC地址,即单播地址;如果I/G=1,则是多播地址(组播+广播=多播)。
  • G/L(Global/Local,也称为U/L位,其中U表示Universal)位,如果G/L=0,则是全局管理地址,由IEEE分配;如果G/L=1,则是本地管理地址,是网络管理员为了加强自己对网络管理而指定的地址。

对于I/G和G/L位的位置,目前有两种说法,或者说两种格式。

对于数据传输来说,数据是按每个字节中一位一位地传输的,一个字节传输完了才到下一个字节。

1.2.4 本地链路多播名称解析

即Link-Local Multicast Name Resolution,本地链路多播名称解析,也称也称为多播 DNS 或 mDNS,用来解析本地网段上的名称.用来在局域网中解析本局域网主机名称对应的ip地址。

1.2.5 SSDP

关于该协议的参考,可以查看链接:链接

Simple Service Discovery Protocol,即简单服务发现协议,是应用层协议,是构成通用即插即用(UPnP)技术的核心协议之一。SSDP一般使用多播地址239.255.255.250和UDP端口号1900。根据互联网地址指派机构的指派,SSDP在IPv6环境下使用多播地址FF0x::C,这里的X根据scope的不同可以有不同的取值。

1.2.6 TLS V1.2协议

关于该协议的内容,可以参考以下链接:链接

2 一些模块与关于网络模块的一些说明

不具有内置协议栈的模块,例如ENC28J60,所以只能跑软件协议栈。
具有内置硬件协议栈的模块,例如W5500。
内存类芯片,例如DM9000。

对于网络模块的接口组合类型,有很典型的几种方案:

  • MAC+PHY集成组合。基于该方案,成品就是一颗芯片,内部已经有了MAC和PHY,那么其外围通信方式就会有SPI,内存类控制方式等。比较有代表性的产品为:ENC28J60,DM9000,W5500。
  • SOC MAC+芯片PHY组合。基于该方案,类型比较多,例如全志A20芯片就带有MAC,而PHY就可以自己选型,外挂。
  • SOC MAC+SOC PHY组合。有这种组合,就比较强大了。对于硬件工程师来说,就只需要外加一个变压器,网络方案就能完成。例如:全志H3芯片。

再说说网络变压器的中心抽头:
在ENC28J60的参考手册上,看到了中心抽头接到一个0.001uF的电容,然后接地。而在某些的参考设计上,却是接到电源上,关于这个问题,有一些人,有一些说法,就拿来参考好了:

  • 中心抽头为什么有些接电源?有些接地?这个主要是与使用的PHY芯片UTP口驱动类型决定的,这种驱动类型有两种,电压驱动和电流驱动。电压驱动的就要接电源;电流驱动的就直接接个电容到地即可!所以对于不同的芯片,中心抽头的接法,与PHY是有密切关系的,具体还要参看芯片的datasheet和参考设计了。
  • 为什么接电源时,又接不同的电压呢?这个也是所使用的PHY芯片资料里规定的UTP端口电平决定的。决定的什么电平,就得接相应的电压了。即如果是2.5v的就上拉到2.5v,如果是3.3v的就上拉到3.3v。
  • 这个变压器到底是什么作用呢,可不可以不接呢。从理论上来说,是可以不需要接变压器,直接接到RJ45上,也是能正常工作的。但是呢,传输距离就很受限制,而且当接到不同电平网口时,也会有影响。而且外部对芯片的干扰也很大。当接了网络变压器后,它主要用于信号电平耦合。其一,可以增强信号,使其传输距离更远;其二,使芯片端与外部隔离,抗干扰能力大大增强,而且对芯片增加了很大的保护作用(如雷击);其三,当接到不同电平(如有的PHY芯片是2.5V,有的PHY芯片是3.3V)的网口时,不会对彼此设备造成影响

做的总结就是:

  • 网络变压器主要有信号传输、阻抗匹配、波形修复、信号杂波抑制和高电压隔离等作用。

具体作用就是:

中心抽头作用:

  1. 通过提供差分线上共模噪声的低阻抗回流路径,降低线缆上共模电流和共模电压;
  2. 对于某些收发器提供一个直流偏置电压或功率源。
    集成的RJ45共模抑制可以做的更好些,寄生参数影响也比较小;
    选用独立器件有一个好处,就是可以把隔离变压器下面的地分开,即GND和PGND,内部的共模干扰不但不会出去,外部网线即使耦合噪声也会通过网线对PGND的分布电容下到机壳上

参考ENC28J60数据手册上的说法:
需要相对较高的电流才能驱动双绞线接口,那么,看起来确实是电流型驱动。

3 操作ENC28J60

ENC28J60是只能跑软件协议栈的。

3.1 操作ENC28J60

3.1.1 调试ENC28J60

a.先列出ENC28J60的控制寄存器:

二、嵌入式之网络模块调试_第2张图片
ENC28J60(1).png

b.了解SPI与ENC28J60交互的方法:

二、嵌入式之网络模块调试_第3张图片
EN28J60(2).png

乘着贴出来的SPI指令集,说说为什么会有位域清零与位域置1这两种操作。
假如说,对一个寄存器已经设置好值了。如果要重新设值,却只关心里面的几个位。位域操作的好处这就体现出来了。
位域置1,是或操作。
位域清零是非于操作。
这能避免一般操作的先读取值,改值,然后写入的耗时、繁琐流程。当然,在ENC28J60的数据手册中,也明显说了一句"这有助于防止在写命令执行期间意外更改已发生改变的标志位"。

读时序,如下图:

二、嵌入式之网络模块调试_第4张图片
EN28J60(3).png

基于以上的3张图,可以确定出,访问地址值小于0x1A的寄存器的步骤为:

    1. 选定Bank。
    1. 选定对应的地址,即可访问到对应的寄存器。

举个例子:
配置MAADR1寄存器,则:

  • 选择Bank3
  • 配置地址值为0x00的寄存器。

估计有疑惑的地方是Bank如何选择,从手册可以看到,所有存储区的最后5个单元(0x1B到0x1F)指向同一组寄存器:EIE,EIR,ESTAT,ECON2和ECON1。从这个意义上看,访问最后的5个单元,其实并没有Bank的概念。

所以只能从这5个单元中,必定有一个能选择出选择哪个Bank。从手册上看到,选择Bank使用的是ECON1寄存器。

写时序,如下图所示:

二、嵌入式之网络模块调试_第5张图片
ENC28J60(4).png
二、嵌入式之网络模块调试_第6张图片
ENC28J60(5).png

设置ECON1思路:

  • 属于ETH寄存器类。是写控制寄存器。操作码为:010b。ECON1地址为:11111b。
    整个命令为:0101 1111b。十六进制为:0x5f。
  • 写入要设置的值。

读取ECON1思路:

  • 属于ETH寄存器类。是读控制寄存器。操作码为:000b。ECON1地址为:11111b。
    整个命令为:0001 1111b。十六进制为:0x1f。
  • 获取值。

有了以上的只是的了解,再看SI,SO表示的含义:

  • SO表示的是MISO,在主机端的角度看,是主机的输出。对应的从机端,就是其数据的输入引脚。
  • SI表示的是MOSI,在主机端的角度看,是主机的输入。对应的从机端,就是其数据输出的引脚。

在调试的时候,需要注意的基本知识:

  • 在读取控制寄存器ETH的时候,依照EN28J60(3).png看,主机先输出一个字节的数据(操作码+地址),然后从机输出数据。在这个基础上,不是说,主机发送了一个字节的数据,就乖乖的数据就来了。
    我要强调的是,时钟的问题。也就是说,主机发送了数据之后,若想获取从机发送过来的数据,就要保证有持续的时钟。
  • 上面说的时钟,是贯穿在数字电路的始终。对时钟的了解,也能侧面体现出对嵌入式的理解深度。
二、嵌入式之网络模块调试_第7张图片
ENC28J60(6).png

接下来就是测试基本的SPI能不能走通的过程:

  • ECON2在复位后的值为0x80,可以直接通过SPI指令获取该寄存器的值,若能获取,则能表示SPI是通的。
  • 继续获取ENC28J60的版本

贴一下,在STM32F103VET6,SPI1下获取ECON2寄存器值的示意代码:

ENC28J60_CS(0);
i_ret = SPI1_ReadWrite(0x1e);
i_ret = SPI1_ReadWrite(0xff);   
ENC28J60_CS(1);

获取ENC28J60的版本的代码:

ENC28J60_CS(0);
i_ret = SPI1_ReadWrite(ECON1 | (0x2 << 5));
i_ret = SPI1_ReadWrite(0x3);
ENC28J60_CS(1);
delay();

ENC28J60_CS(0);
i_ret = SPI1_ReadWrite(EREVID | (0x0 << 5));
i_ret = SPI1_ReadWrite(0xff);   
ENC28J60_CS(1);

代码中的参数0xff,需要解释一下:这还是时钟的问题。第二个i_ret就是要实际从从机获取的数据,前面说过,从从机获取数据,主机是需要保证时钟的,那么主机保证时钟的时候,它给从机的数据就不重要了。可以将0xff换成任意一个字节的值。

完成这些内容,就表示SPI通了,ENC28J60通信也成功了。完成基本的调试了,下面是最重要的功能使用部分。

3.1.2 配置ENC28J60

直接写出参考AVR+ENC28J60后的配置步骤:

1.系统复位。
2.设置接收,发送缓冲区起始位置与指针位置。留给发送缓冲区的大小为1536。
3.设置基本的寄存器。

系统复位命令时序如下所示:

二、嵌入式之网络模块调试_第8张图片
ENC28J60(7).png

根据ENC28J60的数据手册的说明,在系统复位后,需要至少50us的延时,才能对任何PHY寄存器执行读写操作。

现在来讲解操作ENC28J60的方法:

1.需要设置bank,除4个bank中通用的地址范围在0x1B-0x1F的寄存器外。
2.读写寄存器操作。
3.读写缓冲区操作。

先粘贴出来.h文件的内容:

#define ADDR_MASK        0x1F
#define BANK_MASK        0x60
#define SPRD_MASK        0x80
// All-bank registers
#define EIE              0x1B
#define EIR              0x1C
#define ESTAT            0x1D
#define ECON2            0x1E
#define ECON1            0x1F
// Bank 0 registers
#define ERDPTL           (0x00|0x00)
#define ERDPTH           (0x01|0x00)
#define EWRPTL           (0x02|0x00)
#define EWRPTH           (0x03|0x00)
#define ETXSTL           (0x04|0x00)
#define ETXSTH           (0x05|0x00)
#define ETXNDL           (0x06|0x00)
#define ETXNDH           (0x07|0x00)
#define ERXSTL           (0x08|0x00)
#define ERXSTH           (0x09|0x00)
#define ERXNDL           (0x0A|0x00)
#define ERXNDH           (0x0B|0x00)
//ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中
//的哪个位置写入其接收到的字节。 指针是只读的,在成
//功接收到一个数据包后,硬件会自动更新指针。 指针可
//用于判断FIFO 内剩余空间的大小。
#define ERXRDPTL         (0x0C|0x00)
#define ERXRDPTH         (0x0D|0x00)
#define ERXWRPTL         (0x0E|0x00)
#define ERXWRPTH         (0x0F|0x00)
#define EDMASTL          (0x10|0x00)
#define EDMASTH          (0x11|0x00)
#define EDMANDL          (0x12|0x00)
#define EDMANDH          (0x13|0x00)
#define EDMADSTL         (0x14|0x00)
#define EDMADSTH         (0x15|0x00)
#define EDMACSL          (0x16|0x00)
#define EDMACSH          (0x17|0x00)
// Bank 1 registers
#define EHT0             (0x00|0x20)
#define EHT1             (0x01|0x20)
#define EHT2             (0x02|0x20)
#define EHT3             (0x03|0x20)
#define EHT4             (0x04|0x20)
#define EHT5             (0x05|0x20)
#define EHT6             (0x06|0x20)
#define EHT7             (0x07|0x20)
#define EPMM0            (0x08|0x20)
#define EPMM1            (0x09|0x20)
#define EPMM2            (0x0A|0x20)
#define EPMM3            (0x0B|0x20)
#define EPMM4            (0x0C|0x20)
#define EPMM5            (0x0D|0x20)
#define EPMM6            (0x0E|0x20)
#define EPMM7            (0x0F|0x20)
#define EPMCSL           (0x10|0x20)
#define EPMCSH           (0x11|0x20)
#define EPMOL            (0x14|0x20)
#define EPMOH            (0x15|0x20)
#define EWOLIE           (0x16|0x20)
#define EWOLIR           (0x17|0x20)
#define ERXFCON          (0x18|0x20)
#define EPKTCNT          (0x19|0x20)
// Bank 2 registers
#define MACON1           (0x00|0x40|0x80)
#define MACON2           (0x01|0x40|0x80)
#define MACON3           (0x02|0x40|0x80)
#define MACON4           (0x03|0x40|0x80)
#define MABBIPG          (0x04|0x40|0x80)
#define MAIPGL           (0x06|0x40|0x80)
#define MAIPGH           (0x07|0x40|0x80)
#define MACLCON1         (0x08|0x40|0x80)
#define MACLCON2         (0x09|0x40|0x80)
#define MAMXFLL          (0x0A|0x40|0x80)
#define MAMXFLH          (0x0B|0x40|0x80)
#define MAPHSUP          (0x0D|0x40|0x80)
#define MICON            (0x11|0x40|0x80)
#define MICMD            (0x12|0x40|0x80)
#define MIREGADR         (0x14|0x40|0x80)
#define MIWRL            (0x16|0x40|0x80)
#define MIWRH            (0x17|0x40|0x80)
#define MIRDL            (0x18|0x40|0x80)
#define MIRDH            (0x19|0x40|0x80)
// Bank 3 registers
#define MAADR1           (0x00|0x60|0x80)
#define MAADR0           (0x01|0x60|0x80)
#define MAADR3           (0x02|0x60|0x80)
#define MAADR2           (0x03|0x60|0x80)
#define MAADR5           (0x04|0x60|0x80)
#define MAADR4           (0x05|0x60|0x80)
#define EBSTSD           (0x06|0x60)
#define EBSTCON          (0x07|0x60)
#define EBSTCSL          (0x08|0x60)
#define EBSTCSH          (0x09|0x60)
#define MISTAT           (0x0A|0x60|0x80)
#define EREVID           (0x12|0x60)
#define ECOCON           (0x15|0x60)
#define EFLOCON          (0x17|0x60)
#define EPAUSL           (0x18|0x60)
#define EPAUSH           (0x19|0x60)
// PHY registers
#define PHCON1           0x00
#define PHSTAT1          0x01
#define PHHID1           0x02
#define PHHID2           0x03
#define PHCON2           0x10
#define PHSTAT2          0x11
#define PHIE             0x12
#define PHIR             0x13
#define PHLCON           0x14

// ENC28J60 ERXFCON Register Bit Definitions
#define ERXFCON_UCEN     0x80
#define ERXFCON_ANDOR    0x40
#define ERXFCON_CRCEN    0x20
#define ERXFCON_PMEN     0x10
#define ERXFCON_MPEN     0x08
#define ERXFCON_HTEN     0x04
#define ERXFCON_MCEN     0x02
#define ERXFCON_BCEN     0x01
// ENC28J60 EIE Register Bit Definitions
#define EIE_INTIE        0x80
#define EIE_PKTIE        0x40
#define EIE_DMAIE        0x20
#define EIE_LINKIE       0x10
#define EIE_TXIE         0x08
#define EIE_WOLIE        0x04
#define EIE_TXERIE       0x02
#define EIE_RXERIE       0x01
// ENC28J60 EIR Register Bit Definitions
#define EIR_PKTIF        0x40
#define EIR_DMAIF        0x20
#define EIR_LINKIF       0x10
#define EIR_TXIF         0x08
#define EIR_WOLIF        0x04
#define EIR_TXERIF       0x02
#define EIR_RXERIF       0x01
// ENC28J60 ESTAT Register Bit Definitions
#define ESTAT_INT        0x80
#define ESTAT_LATECOL    0x10
#define ESTAT_RXBUSY     0x04
#define ESTAT_TXABRT     0x02
#define ESTAT_CLKRDY     0x01
// ENC28J60 ECON2 Register Bit Definitions
#define ECON2_AUTOINC    0x80
#define ECON2_PKTDEC     0x40
#define ECON2_PWRSV      0x20
#define ECON2_VRPS       0x08
// ENC28J60 ECON1 Register Bit Definitions
#define ECON1_TXRST      0x80
#define ECON1_RXRST      0x40
#define ECON1_DMAST      0x20
#define ECON1_CSUMEN     0x10
#define ECON1_TXRTS      0x08
#define ECON1_RXEN       0x04
#define ECON1_BSEL1      0x02
#define ECON1_BSEL0      0x01
// ENC28J60 MACON1 Register Bit Definitions
#define MACON1_LOOPBK    0x10
#define MACON1_TXPAUS    0x08
#define MACON1_RXPAUS    0x04
#define MACON1_PASSALL   0x02
#define MACON1_MARXEN    0x01
// ENC28J60 MACON2 Register Bit Definitions
#define MACON2_MARST     0x80
#define MACON2_RNDRST    0x40
#define MACON2_MARXRST   0x08
#define MACON2_RFUNRST   0x04
#define MACON2_MATXRST   0x02
#define MACON2_TFUNRST   0x01
// ENC28J60 MACON3 Register Bit Definitions
#define MACON3_PADCFG2   0x80
#define MACON3_PADCFG1   0x40
#define MACON3_PADCFG0   0x20
#define MACON3_TXCRCEN   0x10
#define MACON3_PHDRLEN   0x08
#define MACON3_HFRMLEN   0x04
#define MACON3_FRMLNEN   0x02
#define MACON3_FULDPX    0x01
// ENC28J60 MICMD Register Bit Definitions
#define MICMD_MIISCAN    0x02
#define MICMD_MIIRD      0x01
// ENC28J60 MISTAT Register Bit Definitions
#define MISTAT_NVALID    0x04
#define MISTAT_SCAN      0x02
#define MISTAT_BUSY      0x01
// ENC28J60 PHY PHCON1 Register Bit Definitions
#define PHCON1_PRST      0x8000
#define PHCON1_PLOOPBK   0x4000
#define PHCON1_PPWRSV    0x0800
#define PHCON1_PDPXMD    0x0100
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
#define PHSTAT1_PFDPX    0x1000
#define PHSTAT1_PHDPX    0x0800
#define PHSTAT1_LLSTAT   0x0004
#define PHSTAT1_JBSTAT   0x0002
// ENC28J60 PHY PHCON2 Register Bit Definitions
#define PHCON2_FRCLINK   0x4000
#define PHCON2_TXDIS     0x2000
#define PHCON2_JABBER    0x0400
#define PHCON2_HDLDIS    0x0100

// ENC28J60 Packet Control Byte Bit Definitions
#define PKTCTRL_PHUGEEN  0x08
#define PKTCTRL_PPADEN   0x04
#define PKTCTRL_PCRCEN   0x02
#define PKTCTRL_POVERRIDE 0x01

// SPI operation codes
#define ENC28J60_READ_CTRL_REG       0x00
#define ENC28J60_READ_BUF_MEM        0x3A
#define ENC28J60_WRITE_CTRL_REG      0x40
#define ENC28J60_WRITE_BUF_MEM       0x7A
#define ENC28J60_BIT_FIELD_SET       0x80
#define ENC28J60_BIT_FIELD_CLR       0xA0
#define ENC28J60_SOFT_RESET          0xFF

// The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
// buffer boundaries applied to internal 8K ram
// the entire available packet buffer space is allocated
//
// start with recbuf at 0/
#define RXSTART_INIT     0x0
// receive buffer end
#define RXSTOP_INIT      (0x1FFF-0x0600-1)
// start TX buffer at 0x1FFF-0x0600, pace for one full ethernet frame (~1500 bytes)
#define TXSTART_INIT     (0x1FFF-0x0600)
// stp TX buffer at end of mem
#define TXSTOP_INIT      0x1FFF
//
// max frame length which the conroller will accept:
#define        MAX_FRAMELEN        1500        // (note: maximum ethernet frame length would be 1518)
//#define MAX_FRAMELEN     600

则,先从函数设置bank来分析。

void enc28j60SetBank(unsigned char address)
{
    // set the bank (if needed)
    if((address & BANK_MASK) != Enc28j60Bank)
    {
        // set the bank
        enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
        enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
        Enc28j60Bank = (address & BANK_MASK);
    }
}

因为设置Bank在ECON1的bit1和bit0上。对其他的bit位并不关关心,所以使用了位域置1,位域清0操作。

再来看enc28j60WriteOp操作:

void enc28j60WriteOp(unsigned char op, unsigned char address, unsigned char data)
{
    unsigned char dat = 0;
    
    ENC28J60_CSL();
    // issue write command
    dat = op | (address & ADDR_MASK);
    SPI1_ReadWrite(dat);
    // write data
    dat = data;
    SPI1_ReadWrite(dat);
    ENC28J60_CSH();
}

参数定义为,操作码,地址,数据。
根据数据手册的说明,读控制器,写控制器等,需要先发送1个字节的数据(操作码+地址),然后就是数据的读取或者写入。
而对读取的控制器MAC和MII寄存器,需要在操作码+地址和数据字节输出之间有一个字节的无效字节。所以在定义MAC和MII的时候,在读取的时候就需要注意一个多余的字节操作。

再来看读取操作:

unsigned char enc28j60ReadOp(unsigned char op, unsigned char address)
{
    unsigned char dat = 0;
    
    ENC28J60_CSL();
    
    dat = op | (address & ADDR_MASK);
    SPI1_ReadWrite(dat);
    dat = SPI1_ReadWrite(0xFF);
    // do dummy read if needed (for mac and mii, see datasheet page 29)
    if(address & 0x80)
    {
        dat = SPI1_ReadWrite(0xFF);
    }
    // release CS
    ENC28J60_CSH();
    return dat;
}

根据以上的说明,所以在MAC和MII寄存器定义的时候,会在最高位上或0x80,表示该寄存器需要做特殊的处理。

接着看读取缓冲区的数据:

void enc28j60ReadBuffer(unsigned int len, unsigned char* data)
{
    ENC28J60_CSL();
    // issue read command
    SPI1_ReadWrite(ENC28J60_READ_BUF_MEM);
    while(len)
    {
        len--;
        // read data
        *data = (unsigned char)SPI1_ReadWrite(0);
        data++;
    }
    *data='\0';
    ENC28J60_CSH();
}

读取缓冲的数据,在数据手册中的4.2.2 的读缓冲存储器命令中有描述。

接下来就是写缓冲器数据:

void enc28j60WriteBuffer(unsigned int len, unsigned char* data)
{
    ENC28J60_CSL();
    // issue write command
    SPI1_ReadWrite(ENC28J60_WRITE_BUF_MEM);
    
    while(len)
    {
        len--;
        SPI1_ReadWrite(*data);
        data++;
    }
    ENC28J60_CSH();
}

读取基本的控制寄存器如下:

unsigned char enc28j60Read(unsigned char address)
{
    // set the bank
    enc28j60SetBank(address);
    // do the read
    return enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);
}

写基本的控制寄存器:

void enc28j60Write(unsigned char address, unsigned char data)
{
    // set the bank
    enc28j60SetBank(address);
    // do the write
    enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, data);
}

对PHY的操作,根据手册的说明,必须要借助MII才行:
PHY操作,分为读写两个部分,这在数据手册的3.3 PHY寄存器上有说明。

void enc28j60PhyWrite(unsigned char address, unsigned int data)
{
    // set the PHY register address
    enc28j60Write(MIREGADR, address);
    // write the PHY data
    enc28j60Write(MIWRL, data);
    enc28j60Write(MIWRH, data>>8);
    // wait until the PHY write completes
    while(enc28j60Read(MISTAT) & MISTAT_BUSY)
    {
        //Del_10us(1);
        //_nop_();
    }
}

再来看看发送数据包与接手数据包的操作:
发送数据包:

void enc28j60PacketSend(unsigned int len, unsigned char* packet)
{
    // Set the write pointer to start of transmit buffer area
    enc28j60Write(EWRPTL, TXSTART_INIT&0xFF);
    enc28j60Write(EWRPTH, TXSTART_INIT>>8);
    
    // Set the TXND pointer to correspond to the packet size given
    enc28j60Write(ETXNDL, (TXSTART_INIT+len)&0xFF);
    enc28j60Write(ETXNDH, (TXSTART_INIT+len)>>8);
    
    // write per-packet control byte (0x00 means use macon3 settings)
    enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
    
    // copy the packet into the transmit buffer
    enc28j60WriteBuffer(len, packet);
    
    // send the contents of the transmit buffer onto the network
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
    
    // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.
    if( (enc28j60Read(EIR) & EIR_TXERIF) )
    {
        enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
    }
}

分析一下,发送数据包。

二、嵌入式之网络模块调试_第9张图片
ENC28J60(10).png

发送真实数据前,注意要先通过SPI指令发送WBM命令,包括的是包控制字节。
具体的配置,可以使用MACON3。

二、嵌入式之网络模块调试_第10张图片
ENC28J60(11).png

接收数据包:

// Gets a packet from the network receive buffer, if one is available.
// The packet will by headed by an ethernet header.
//      maxlen  The maximum acceptable length of a retrieved packet.
//      packet  Pointer where packet data should be stored.
// Returns: Packet length in bytes if a packet was retrieved, zero otherwise.
unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet)
{
    unsigned int rxstat;
    unsigned int len;
    
    // check if a packet has been received and buffered
    //if( !(enc28j60Read(EIR) & EIR_PKTIF) ){
    // The above does not work. See Rev. B4 Silicon Errata point 6.
    if( enc28j60Read(EPKTCNT) ==0 )  //收到的以太网数据包长度
    {
        return(0);
    }
    
    // Set the read pointer to the start of the received packet      缓冲器读指针
    enc28j60Write(ERDPTL, (NextPacketPtr));
    enc28j60Write(ERDPTH, (NextPacketPtr)>>8);
    
    // read the next packet pointer
    NextPacketPtr  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
    NextPacketPtr |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
    
    // read the packet length (see datasheet page 43)
    len  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
    len |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
    
    len-=4; //remove the CRC count
    // read the receive status (see datasheet page 43)
    rxstat  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
    rxstat |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
    // limit retrieve length
    if (len>maxlen-1)
    {
        len=maxlen-1;
    }
    
    // check CRC and symbol errors (see datasheet page 44, table 7-3):
    // The ERXFCON.CRCEN is set by default. Normally we should not
    // need to check this.
    if ((rxstat & 0x80)==0)
    {
        // invalid
        len=0;
    }
    else
    {
        // copy the packet from the receive buffer
        enc28j60ReadBuffer(len, packet);
    }
    // Move the RX read pointer to the start of the next received packet
    // This frees the memory we just read out
    enc28j60Write(ERXRDPTL, (NextPacketPtr));
    enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8);
    
    // decrement the packet counter indicate we are done with this packet
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
    return(len);
}

关于接收数据包,需要弄懂几个概念:

  • ERXST:接收数据的缓冲区的起始位置。
  • ERXND:接收数据的缓冲区的结束位置。
    这两个寄存器需要在接收数据包之前,进行编程。最好将ERXST设置为偶地址。
  • ERXWRPT:这个是硬件指针。也就是说,网卡成功接收到了数据,其指针就会有位移。可以从ENC28J60的数据手册的P35上的"6.1 Receive Buffer"得到参考。
  • ERXRDPT:该寄存器需要将值设置为和ERXWRPT的值相同。在"6.1 Receive Buffer"写到,编程的时候,需要先编程ERXRDPTL,然后就是编程ERXRDPTH。
  • ERDPT:在发送RBM命令和常量后,ERDPT指向的存储器中的数据会从SO引脚移出来。

在7.2.3 读接收的数据包 有一些相应的说明。
处理数据包,主控制器会使用SPI的RBM指令从下一个数据包指针的首地址开始读取。主控制器将保存下一数据包指针和接收状态向量的必要字节,然后继续读取数据包的内容。

从这个方面来看,就需要贴出来几个重要的图了:

二、嵌入式之网络模块调试_第11张图片
缓冲区的构成
二、嵌入式之网络模块调试_第12张图片
ENC28J60(8).png
二、嵌入式之网络模块调试_第13张图片
ENC28J60(9).png

所以,对于接收数据部分,需要先设置好缓冲区的读指针,然后开始对数据包进行分拆:

        /* Set the read pointer to the start of the received packet. */
        spi_write(spi_device, ERDPTL, (NextPacketPtr));
        spi_write(spi_device, ERDPTH, (NextPacketPtr)>>8);

        /* read the next packet pointer. */
        NextPacketPtr  = spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0);
        NextPacketPtr |= spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0)<<8;

        /* read the packet length (see datasheet page 43). */
        len  = spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0);       //0x54
        len |= spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0)<<8;    //5554

        len-=4; //remove the CRC count

        // read the receive status (see datasheet page 43)
        rxstat  = spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0);
        rxstat |= ((rt_uint16_t)spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0))<<8;

代码里面,即,先设置缓冲区指针,然后开始读取缓冲区内容。从接收数据包的结构示例中,可以看出的是,分为两个大的内容,也就是一个是数据包的指针,一个是接收状态向量。
接收数据包的指针,第一个部分是低字节,第二个部分是高字节。状态向量顺序也是一样。

总之,形成的结构就是,下一个数据包的位置+接收的字节数+各种状态事件。从这里也就可以体现出来ERDPT的作用了。

上面将发送数据包,和接收数据分析了一下,这里就贴出初始化的程序:

void enc28j60Init(unsigned char* macaddr)
{   
    ENC28J60_CSH();       

    // perform system reset
    enc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
   
    // check CLKRDY bit to see if reset is complete
    // The CLKRDY does not work. See Rev. B4 Silicon Errata point. Just wait.
    //while(!(enc28j60Read(ESTAT) & ESTAT_CLKRDY));
    // do bank 0 stuff
    // initialize receive buffer
    // 16-bit transfers, must write low byte first
    // set receive buffer start address    
    NextPacketPtr = RXSTART_INIT;
    // Rx start    
    enc28j60Write(ERXSTL, RXSTART_INIT&0xFF);    
    enc28j60Write(ERXSTH, RXSTART_INIT>>8);
    // set receive pointer address     
    enc28j60Write(ERXRDPTL, RXSTART_INIT&0xFF);
    enc28j60Write(ERXRDPTH, RXSTART_INIT>>8);
    // RX end
    enc28j60Write(ERXNDL, RXSTOP_INIT&0xFF);
    enc28j60Write(ERXNDH, RXSTOP_INIT>>8);
    // TX start   1500
    enc28j60Write(ETXSTL, TXSTART_INIT&0xFF);
    enc28j60Write(ETXSTH, TXSTART_INIT>>8);
    // TX end
    enc28j60Write(ETXNDL, TXSTOP_INIT&0xFF);
    enc28j60Write(ETXNDH, TXSTOP_INIT>>8);
    // do bank 1 stuff, packet filter:
    // For broadcast packets we allow only ARP packtets
    // All other packets should be unicast only for our mac (MAADR)
    //
    // The pattern to match on is therefore
    // Type     ETH.DST
    // ARP      BROADCAST
    // 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9
    // in binary these poitions are:11 0000 0011 1111
    // This is hex 303F->EPMM0=0x3f,EPMM1=0x30
    
    enc28j60Write(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
    enc28j60Write(EPMM0, 0x3f);
    enc28j60Write(EPMM1, 0x30);
    enc28j60Write(EPMCSL, 0xf9);
    enc28j60Write(EPMCSH, 0xf7);    
    enc28j60Write(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
    // bring MAC out of reset 
    enc28j60Write(MACON2, 0x00);
    
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);
    // set inter-frame gap (non-back-to-back)

    enc28j60Write(MAIPGL, 0x12);
    enc28j60Write(MAIPGH, 0x0C);
    // set inter-frame gap (back-to-back)

    enc28j60Write(MABBIPG, 0x15);
    // Set the maximum packet size which the controller will accept
    // Do not send packets longer than MAX_FRAMELEN:
  
    enc28j60Write(MAMXFLL, MAX_FRAMELEN&0xFF);  
    enc28j60Write(MAMXFLH, MAX_FRAMELEN>>8);
    // do bank 3 stuff
    // write MAC address
    // NOTE: MAC address in ENC28J60 is byte-backward
    enc28j60Write(MAADR5, macaddr[0]);  
    enc28j60Write(MAADR4, macaddr[1]);
    enc28j60Write(MAADR3, macaddr[2]);
    enc28j60Write(MAADR2, macaddr[3]);
    enc28j60Write(MAADR1, macaddr[4]);
    enc28j60Write(MAADR0, macaddr[5]);

    //配置PHY为全双工  LEDB为拉电流
    enc28j60PhyWrite(PHCON1, PHCON1_PDPXMD);    
    
    // no loopback of transmitted frames
    enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS);

    // switch to bank 0    
    enc28j60SetBank(ECON1);

    // enable interrutps
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);

    // enable packet reception
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
}

接着在main调用相应函数,初始化PHY内容:

enc28j60PhyWrite(PHLCON,0x476);

void enc28j60PhyWrite(unsigned char address, unsigned int data)
{
    // set the PHY register address
    enc28j60Write(MIREGADR, address);
    // write the PHY data
    enc28j60Write(MIWRL, data);
    enc28j60Write(MIWRH, data>>8);
    // wait until the PHY write completes
    while(enc28j60Read(MISTAT) & MISTAT_BUSY)
    {
        //Del_10us(1);
        //_nop_();
    }
}


再来一部分以太网数据包格式。

二、嵌入式之网络模块调试_第14张图片
ENC28J60(12).png

3.1.3 ENC28J60出现过的问题

  • 运行大概6小时(1小时不等)的时候,有错误
二、嵌入式之网络模块调试_第15张图片
ENC28J60出现错误
二、嵌入式之网络模块调试_第16张图片
ENC28J60正常状态

这个问题是在ESTAT上值为0x41,错误Ethernet Buffer Error。

二、嵌入式之网络模块调试_第17张图片
ESTAT错误

需要提出的是,中文手册和英文手册,关于这部分内容,在中文手册上,说明上是有问题的,把图贴上来,如下:

二、嵌入式之网络模块调试_第18张图片
中文有错误图例

后面观察指针,ERXWRPT和ERXRDPT,发现值并不相等,所以值是有问题的。

我先来说说是什么导致了这个问题的发生:

PC端是win10,经过一定的时间后,PC端会发送ICMPv6,DHCPv6,SSDP等内容,SSDP内容居多,后面对比调试,发现ENC28J60是读SSDP信息过滤了的,而ARP信息会自动轮询一遍,从192.168.25.2到192.168.25.254,除了192.168.25.15。

我截图一部分:

二、嵌入式之网络模块调试_第19张图片
ARP截图信息

之后,我采取的操作如下所示代码:

static int g_NetErrorTimes = 0;

/* recv packet. */
static struct pbuf *enc28j60_rx(rt_device_t dev)
{
    struct net_device * enc28j60 = (struct net_device *)dev;
    struct rt_spi_device * spi_device = enc28j60->spi_device;
    struct pbuf* p = RT_NULL;

    uint8_t eir, eir_clr;
    uint32_t pk_counter;
    rt_uint32_t level;
    rt_uint32_t len;
    rt_uint16_t rxstat;

    rt_uint16_t previous_pointer;


    enc28j60_lock(dev);

    /* disable enc28j60 interrupt */
    level = enc28j60_interrupt_disable(spi_device);

    /* get EIR */
    eir = spi_read(spi_device, EIR);

    while(eir & ~EIR_PKTIF)
    {
        eir_clr = 0;

        /* clear PKTIF */
        if (eir & EIR_PKTIF)
        {
            NET_DEBUG("EIR_PKTIF\r\n");
            /* switch to bank 0. */
            enc28j60_set_bank(spi_device, EIE);
            /* disable rx interrutps. */
            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, EIE, EIE_PKTIE);
            eir_clr |= EIR_PKTIF;
//            enc28j60_set_bank(spi_device, EIR);
//            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, EIR, EIR_PKTIF);
        }

        /* clear DMAIF */
        if (eir & EIR_DMAIF)
        {
            NET_DEBUG("EIR_DMAIF\r\n");
            eir_clr |= EIR_DMAIF;
//            enc28j60_set_bank(spi_device, EIR);
//            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, EIR, EIR_DMAIF);
        }

        /* LINK changed handler */
        if ( eir & EIR_LINKIF)
        {
            rt_bool_t link_status;

            NET_DEBUG("EIR_LINKIF\r\n");
            link_status = enc28j60_check_link_status(spi_device);


            //rt_kprintf("--------EIR_LINKIF----------\r\n");

            /* read PHIR to clear the flag */
            enc28j60_phy_read(spi_device, PHIR);
            eir_clr |= EIR_LINKIF;
//            enc28j60_set_bank(spi_device, EIR);
//            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, EIR, EIR_LINKIF);

            eth_device_linkchange(&(enc28j60->parent), link_status);
        }

        if (eir & EIR_TXIF)
        {
            /* A frame has been transmitted. */
            enc28j60_set_bank(spi_device, EIR);
            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, EIR, EIR_TXIF);

            tx_ack->free = RT_TRUE;
            tx_ack = tx_ack->next;
            if(tx_ack->free == RT_FALSE)
            {
                NET_DEBUG("[tx isr] Tx chain not empty, continue send the next pkt!\r\n");
                // TX start
                spi_write(spi_device, ETXSTL, (tx_ack->addr)&0xFF);
                spi_write(spi_device, ETXSTH, (tx_ack->addr)>>8);
                // TX end
                spi_write(spi_device, ETXNDL, (tx_ack->addr + tx_ack->len)&0xFF);
                spi_write(spi_device, ETXNDH, (tx_ack->addr + tx_ack->len)>>8);

                spi_write_op(spi_device, ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
            }
            else
            {
                NET_DEBUG("[tx isr] Tx chain empty, stop!\r\n");
            }

            /* set event */
            rt_event_send(&tx_event, 0x01);
        }

        /* wake up handler */
        if ( eir & EIR_WOLIF)
        {
            NET_DEBUG("EIR_WOLIF\r\n");
            eir_clr |= EIR_WOLIF;
//            enc28j60_set_bank(spi_device, EIR);
//            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, EIR, EIR_WOLIF);
        }

        /* TX Error handler */
        if ((eir & EIR_TXERIF) != 0)
        {
            NET_DEBUG("EIR_TXERIF re-start tx chain!\r\n");
            enc28j60_set_bank(spi_device, ECON1);
            spi_write_op(spi_device, ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
            eir_clr |= EIR_TXERIF;
//            enc28j60_set_bank(spi_device, EIR);
//            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, EIR, EIR_TXERIF);

            /* re-init tx chain */
            _tx_chain_init();
        }

        /* RX Error handler */
        if ((eir & EIR_RXERIF) != 0)
        {
            NET_DEBUG("EIR_RXERIF re-start rx!\r\n");

            NextPacketPtr = RXSTART_INIT;
            enc28j60_set_bank(spi_device, ECON1);
            spi_write_op(spi_device, ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXRST);
            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_RXRST);
            /* switch to bank 0. */
            enc28j60_set_bank(spi_device, ECON1);
            /* enable packet reception. */
            spi_write_op(spi_device, ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
            eir_clr |= EIR_RXERIF;
//            enc28j60_set_bank(spi_device, EIR);
//            spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, EIR, EIR_RXERIF);
        }

        enc28j60_set_bank(spi_device, EIR);
        spi_write_op(spi_device, ENC28J60_BIT_FIELD_CLR, EIR, eir_clr);

        eir = spi_read(spi_device, EIR);
    }

    /* read pkt */
    pk_counter = spi_read(spi_device, EPKTCNT);
    if(pk_counter)   
    {
        /* Set the read pointer to the start of the received packet. */
        spi_write(spi_device, ERDPTL, (NextPacketPtr));
        spi_write(spi_device, ERDPTH, (NextPacketPtr)>>8);

    //wit_yuan added at 2017-06-05 
    previous_pointer = NextPacketPtr;

        /* read the next packet pointer. */
        NextPacketPtr  = spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0);
        NextPacketPtr |= spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0)<<8;



        /* read the packet length (see datasheet page 43). */
        len  = spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0);       //0x54
        len |= spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0)<<8;    //5554

        len-=4; //remove the CRC count

        // read the receive status (see datasheet page 43)
        rxstat  = spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0);
        rxstat |= ((rt_uint16_t)spi_read_op(spi_device, ENC28J60_READ_BUF_MEM, 0))<<8;

        // check CRC and symbol errors (see datasheet page 44, table 7-3):
        // The ERXFCON.CRCEN is set by default. Normally we should not
        // need to check this.
        if ((rxstat & 0x80)==0)
        {
            // invalid
            len=0;
        }
        else
        {
            /* allocation pbuf */
            p = pbuf_alloc(PBUF_LINK, len, PBUF_RAM);
            if (p != RT_NULL)
            {
                struct pbuf* q;
#ifdef ETH_RX_DUMP
                rt_size_t dump_count = 0;
                rt_uint8_t * dump_ptr;
                rt_size_t dump_i;
                NET_DEBUG("rx_dump, size:%d\r\n", len);
#endif
                for (q = p; q != RT_NULL; q= q->next)
                {
                    uint8_t cmd = ENC28J60_READ_BUF_MEM;
                    rt_spi_send_then_recv(spi_device, &cmd, 1, q->payload, q->len);
#ifdef ETH_RX_DUMP
                    dump_ptr = q->payload;
                    for(dump_i=0; dump_ilen; dump_i++)
                    {
                        NET_DEBUG("%02x ", *dump_ptr);
                        if( ((dump_count+1)%8) == 0 )
                        {
                            NET_DEBUG("  ");
                        }
                        if( ((dump_count+1)%16) == 0 )
                        {
                            NET_DEBUG("\r\n");
                        }
                        dump_count++;
                        dump_ptr++;
                    }
#endif
                }
#ifdef ETH_RX_DUMP
                NET_DEBUG("\r\n");
#endif
            }
        }

        /* Move the RX read pointer to the start of the next received packet. */
        /* This frees the memory we just read out. */
        spi_write(spi_device, ERXRDPTL, (NextPacketPtr));
        spi_write(spi_device, ERXRDPTH, (NextPacketPtr)>>8);



        //2017-06-05 add something mendings yuan.
        //if(((len+10+ previous_pointer) % TXSTART_INIT == NextPacketPtr) || ((len+11+ previous_pointer) % TXSTART_INIT == NextPacketPtr))
        if(((len+10+ previous_pointer) % (TXSTART_INIT - 1) == NextPacketPtr))
        {

        }
        else
        {
            rt_uint16_t u_temp;
            u_temp = (spi_read(spi_device, ERXWRPTH) << 8) | spi_read(spi_device, ERXWRPTL),
            spi_write(spi_device, ERXRDPTL, (u_temp));
            spi_write(spi_device, ERXRDPTH, (u_temp)>>8);

            NextPacketPtr = u_temp;

            g_NetErrorTimes ++;
        }

        /* decrement the packet counter indicate we are done with this packet. */
        spi_write_op(spi_device, ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
    }
    else
    {
        /* switch to bank 0. */
        enc28j60_set_bank(spi_device, ECON1);
        /* enable packet reception. */
        spi_write_op(spi_device, ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);

        level |= EIE_PKTIE;
    }

    /* enable enc28j60 interrupt */
    enc28j60_interrupt_enable(spi_device, level);

    enc28j60_unlock(dev);

    return p;
}

也就是说,一旦出现错误,有两个指针是有不正确的,分别为ERXWRPT和ERXRDPT,只需要将这两个指针重新定位一下即可。这在TCP数据传输上是没有问题的,因为TCP有交互机制。而UDP在这种情况下,会丢数据,不过,话说回来,对于UDP来说,丢点数据也是正常的。

3.1.4 ENC28J60的知识回顾

1.接收过滤器

可以自动拒绝不需要的数据包。使用了6种不同的数据包过滤器:单播,格式匹配,Magic Packet,哈希表,组播,广播等。这些是由ERXFCON寄存器控制的。

2.不支持自适应配置

若连接到自适应网络,则会被检测为半双工器件。若要在全双工模式下通信,需要将双方手动配置为全双工工作模式。

3.全双工半双工模式设置

半双工模式:MACON3.FULDPX = 0并且PHCON1.PDPXMD = 0。
全双工模式:MACON3.FULDPX = 1并且PHCON1.PDPXMD = 1。

4.中断

在电平下降沿触发中断。中断只有两种情况,一个是控制事件(INT),另外一个是LAN唤醒(WOL)。
EIE和EWOLIE寄存器包含中断源的中断允许位。
EIR和EWOLIR寄存器包含相应的中断标志位。
产生中断后,相应的中断引脚就会保持低电平,一直到所有引发该中断的标志位被主控制器清零或者屏蔽。

5.几个指针

ERXST:
ERXND:
这两个指针,表示接收数据的起始位置和结束位置,硬件接收的数据不会写入这两个位置以外的地方。

ERXWRPT:
这表示的是硬件向上面定义的缓冲区中写入的数据的位置。所以这个指针只能是只读的,而且如果接收的数据正确,硬件会自动更新指针。因此,这个也可以用来判断缓冲区中剩余的空间的大小。

ERXRDPT:
表示的是禁止硬件写入的缓冲区的位置。也就是说,保留给用户的缓冲区位置。就算硬件接收到数据,过缓冲区了,也是不会覆盖该指针后面的数据内容的。这就要求用户在接收到数据后,合理的移动该指针位置。

ERDPT:
EWRPT:
这两个寄存器,表示的是读写缓冲存储器的数据的指针。

ETXST:
ETXND:
在编程的时候,硬件并不检查与接收缓冲区是否覆盖。

3.1.5 ENC28J60参考资料

1.AVR+ENC28J60 软硬件设计,与一些例子。转到链接
软件代码,下载链接,enc28j60.c 说明了控制步骤。

3.2 操作W5500

3.2.1 简介

简单的贴一下官网的说法:
W5500是WIZnet推出的高性能以太网接口芯片系列之一,内部集成全硬件TCP/IP协议栈+MAC+PHY。全硬件协议栈技术采用硬件逻辑门电路实现复杂的TCP/IP协议簇,其应用具有简单快速、可靠性高、安全性好等显著优势;内部集成MAC和PHY工艺,使得单片机接入以太网方案的硬件设计更为简捷和高效。

访问W5500的方式如下:

二、嵌入式之网络模块调试_第20张图片
W5500-1.png

也就是说,SPI数据帧包括16位地址段的偏移地址,8位控制段和N字节数据段。
8位控制段可以通过修改区域选择位(BSB[4:0]),读/写访问模式位(RWB)以及SPI工作模式位(OM[1:0])来重新定义。区域选择位选择了归属于偏移地址的区域。

W5500 支持数据的连续读/写。其流程为数据从(2/4/N 字节连续数据的)偏移地址的基址开始传输,偏移地址会(自增寻址)加 1 传输接下来 的数据。

位格式如下:

BSB4 BSB3 BSB2 BSB1 BSB0 RWB OM1 OM0
7 6 5 4 3 2 1 0

控制段:

BSB4 BSB3 BSB2 BSB1 BSB0 功能
0 0 0 0 0 选择通用寄存器
0 0 0 0 1 选择Socket 0寄存器
0 0 0 1 0 选择Socket 0发送缓存
0 0 0 1 1 选择Socket 0接收缓存
0 0 1 0 0 保留位
0 0 1 0 1 选择Socket 1寄存器
0 0 1 1 0 选择Socket 1发送缓存
0 0 1 1 1 选择Socket 1接收缓存
0 1 0 0 0 保留位
0 1 0 0 1 选择Socket 2寄存器
0 1 0 1 0 选择Socket 2发送缓存
0 1 0 1 1 选择Socket 2接收缓存
0 1 1 0 0 保留位
0 1 1 0 1 选择Socket 3寄存器
0 1 1 1 0 选择Socket 3发送缓存
0 1 1 1 1 选择Socket 3接收缓存
1 0 0 0 0 保留位
1 0 0 0 1 选择Socket 4寄存器
1 0 0 1 0 选择Socket 4发送缓存
1 0 0 1 1 选择Socket 4接收缓存
1 0 1 0 0 保留位
1 0 1 0 1 选择Socket 5寄存器
1 0 1 1 0 选择Socket 5发送缓存
1 0 1 1 1 选择Socket 5接收缓存
1 1 0 0 0 保留位
1 1 0 0 1 选择Socket 6寄存器
1 1 0 1 0 选择Socket 6发送缓存
1 1 0 1 1 选择Socket 6接收缓存
1 1 1 0 0 保留位
1 1 1 0 1 选择Socket 7寄存器
1 1 1 1 0 选择Socket 7发送缓存
1 1 1 1 1 选择Socket 7接收缓存

注意,不要选择上保留位,否则会导致W5500故障。

RWB 功能
0
1
OM1 OM0 功能
0 0 可变数据长度模式,N字节数据段(1<= N)
0 1 固定数据长度模式,1字节数据段(N = 1)
1 0 固定数据长度模式,1字节数据段(N = 2)
1 1 固定数据长度模式,1字节数据段(N = 4)

来说明一下使用SPI操作的方法:
在VDM(可变数据长度模式):

  • 写访问
二、嵌入式之网络模块调试_第21张图片
VDM模式下.png

1)1字节的数据写访问
向通用寄存器区域中的Socket中断屏蔽寄存器写入数据0xAA。

SPI 数据帧的写操作如下所示:

Offset Address = 0×0018 
BSB[4:0] = ‘00000’ 
RWB = ‘1’ 
OM[1:0] = ‘00’ 
1st Data = 0xAA

2)N字节写访问
向通用寄存器区域中的 Socket 中断屏蔽寄存器写入 5 字节数据时(0×11, 0×22, 0×33, 0×44, 0×55),SPI 数据帧的写操作如下所示:

Offset Address = 0x0040
BSB[4:0]       = '00110'
RWB            = '1'
OM[1:0]        = '00'
1st DATA       = 0x11
2st DATA       = 0x22
3st DATA       = 0x33
4st DATA       = 0x44
5st DATA       = 0x55
二、嵌入式之网络模块调试_第22张图片
VDM下,向Socket1发送缓冲区0x0040写入5字节数据.png
  • 读访问
二、嵌入式之网络模块调试_第23张图片
VDM模式下读SPI数据帧.png

1)1字节的数据读访问
在VDM模式下,当主机读取Socket 7寄存器区的socket状态寄存器(S7_SR),SPI数据帧的数据读取如下所示。要设置S7_SR为socket建立模式下(0x17)

Offset Address = 0×0003 
BSB[4:0] = ‘11101’ 
RWB = ‘0’ 
OM[1:0] = ‘00’ 
1st Data = 0x17
二、嵌入式之网络模块调试_第24张图片
在VDM模式下读取S7_SR.png

1)N字节的数据读访问
在 VDM 模式下,当从Socket3 的地址为 0×0100 的读取缓存中读取 5 字节的数据(0xAA, 0xBB, 0xCC, 0xDD,0xEE)。这 5 个字节数据的读访问 SPI 数据帧如下所示:

Offset Address = 0x0100
BSB[4:0]       = '01111'
RWB            = '0'
OM[1:0]        = '00'
1st DATA       = 0xaa
2st DATA       = 0xbb
3st DATA       = 0xcc
4st DATA       = 0xdd
5st DATA       = 0xee
二、嵌入式之网络模块调试_第25张图片
N字节读访问.png

在FDM(固定数据长度模式):
该模式下,SCSn管脚接地,这样的话,主机就不能控制W5500,从这个意义上讲,SPI被W5500独占了。

  • 写访问

1) 1字节写访问

FDM模式下,1字节写访问SPI数据帧.png

2)2字节写访问

二、嵌入式之网络模块调试_第26张图片
FDM模式下,2字节写访问SPI数据帧.png

3)4字节写访问

二、嵌入式之网络模块调试_第27张图片
FDM模式下,4字节写访问SPI数据帧.png
  • 读访问

1) 1字节读访问

二、嵌入式之网络模块调试_第28张图片
在 FDM 模式下,1 字节读访问 SPI 数据帧.png

2) 2字节读访问

二、嵌入式之网络模块调试_第29张图片
在 FDM 模式下,2字节读访问 SPI 数据帧.png

3) 4字节读访问

二、嵌入式之网络模块调试_第30张图片
在 FDM 模式下,4字节读访问 SPI 数据帧.png

寄存器与内存访问:
W5500有1个通用寄存器,8个socket寄存器区,以及对应每个socket的收/发缓冲区。
无论每个socket分配多大的收/发缓存,都必须在16位的偏移地址范围内(从0x0000到0xffff)。

通用寄存器区:
通用寄存器区配置了W5500的基本信息,例如:IP及MAC地址。可以通过SPI数据帧的区域选择位(BSB[4:0])的值来选定。

二、嵌入式之网络模块调试_第31张图片
通用寄存器.png

根据以上内容,我选择STM32F429 阿波罗板子做一版测试程序。


U4
W5500

原理图如下:


二、嵌入式之网络模块调试_第32张图片
SPI2
二、嵌入式之网络模块调试_第33张图片
W5500接线

关于这个芯片,我大致讲解一下其使用方法,以TCP服务器模式,轮询方式来说:

该芯片分为地址段+控制段+数据段。而其地址空间其实是通过地址段+控制端来保证唯一性的。其读取数据与写入数据其实是使用循环缓冲实现的。

1.设置w5500初始化环境。
---->设置网关,子网掩码,MAC地址,源ip等。然后就可以使用主机ping该ip地址。

2.设置tcp服务器相关的寄存器值
---->设置socket0模式寄存器为TCP模式,接收发送缓冲为8KB,配置开始打开tcp,监听tcp等。

3.接收发送数据
接收数据的时候,需要先查看一下空闲缓冲的数据大小,如果有数据,则读取缓冲区中的读指针。然后读取数据即可。接着是将读指针与读取的大小的和写入相应寄存器,最后一步也是重要的一步,就是将socket配置寄存器中写入RECV指令。

发送数据时候,也需要查看一下空闲缓冲区的数据大小,要注意发送的数据大小不能大于该空闲缓冲区的数据大小。然后就可以发送数据,之后是将指针更新,最后写入SEND指令,才会触发真正的指针写入相应寄存器。

我写了一个测试程序,问题很多,但仅仅用来了解该芯片的流程还是可以的。传送门

建议可以使用stm32f429igt6测试完成之后,看一下流程,然后可以使用比较好的代码再去看一遍,就能能完全掌握该芯片了。

具体使用方法为:

$  git clone https://github.com/yuanzhaoming/stm32f4_prj_testcase.git
$  cd stm32f4_prj_testcase/
$  git checkout aee847c

4.其他芯片

4.1 DM9161

DM9161网络传输距离为100米内。

4.2 DP83848

DP83848的网络传输距离为120米,适用于恶劣的工业环境。

5.待续

。。。


你可能感兴趣的:(二、嵌入式之网络模块调试)