美文网首页我爱编程
二、嵌入式之网络模块调试

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

作者: wit_yuan | 来源:发表于2017-05-09 18:28 被阅读0次

    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地址及其组成部分。

    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的控制寄存器:

    ENC28J60(1).png

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

    EN28J60(2).png

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

    读时序,如下图:

    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寄存器。

    写时序,如下图所示:

    ENC28J60(4).png 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看,主机先输出一个字节的数据(操作码+地址),然后从机输出数据。在这个基础上,不是说,主机发送了一个字节的数据,就乖乖的数据就来了。
      我要强调的是,时钟的问题。也就是说,主机发送了数据之后,若想获取从机发送过来的数据,就要保证有持续的时钟。
    • 上面说的时钟,是贯穿在数字电路的始终。对时钟的了解,也能侧面体现出对嵌入式的理解深度。
    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.设置基本的寄存器。

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

    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);
        }
    }
    

    分析一下,发送数据包。

    ENC28J60(10).png

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

    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指令从下一个数据包指针的首地址开始读取。主控制器将保存下一数据包指针和接收状态向量的必要字节,然后继续读取数据包的内容。

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

    缓冲区的构成 ENC28J60(8).png 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_();
        }
    }
    
    
    

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

    ENC28J60(12).png

    3.1.3 ENC28J60出现过的问题

    • 运行大概6小时(1小时不等)的时候,有错误
    ENC28J60出现错误 ENC28J60正常状态

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

    ESTAT错误

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

    中文有错误图例

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

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

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

    我截图一部分:

    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_i<q->len; 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的方式如下:

    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(可变数据长度模式):

    • 写访问
    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
    
    VDM下,向Socket1发送缓冲区0x0040写入5字节数据.png
    • 读访问
    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
    
    在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
    
    N字节读访问.png

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

    • 写访问

    1) 1字节写访问

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

    2)2字节写访问

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

    3)4字节写访问

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

    1) 1字节读访问

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

    2) 2字节读访问

    在 FDM 模式下,2字节读访问 SPI 数据帧.png

    3) 4字节读访问

    在 FDM 模式下,4字节读访问 SPI 数据帧.png

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

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

    通用寄存器.png

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


    U4 W5500

    原理图如下:


    SPI2 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.待续

    。。。


    相关文章

      网友评论

        本文标题:二、嵌入式之网络模块调试

        本文链接:https://www.haomeiwen.com/subject/gflltxtx.html