Enables fast realtime communication over Ethernet by using plain Ethernet frames (overhead of TCP/IP network layers is stripped away).

Dependents:   RawEthernet_Hello

RawEthernet.cpp

Committer:
hudakz
Date:
2018-08-20
Revision:
0:ca476e0a1a28

File content as of revision 0:ca476e0a1a28:

//------------------------------------------------------------------------------
// Functions for using an ENC28J60 ethernet controller, optimized for 
// communication speed.
//
// Written by Rogier Schouten http://www.rogiershikes.tk
// Based on code by Guido Socher http://www.tuxgraphics.org
// Idea modified and further updated by Guido Socher
// Ported to MBED by Zoltan Hudak hudakz@outlook.com
// License: GPL V2
//
// Enables to read sensor data or control IO-ports with less than a millisecond delay. 
// The trick is in removing the TCP/IP overhead and use raw Ethernet frames. 
// A Linux application using raw network sockets is controlling this slave device.
//
// Assumptions:
// - Max. payload data: 255 bytes per packet
// - The network consists of a master PC and one or more slave devices. 
//   Only plain hubs and switches are allowed between PC and ethernet device.
//   Note that some wlan routers have internal switches which switch only
//   IP packets. They will discard plain ethernet frames. A normal 100Mbit 
//   office/workgroup switch will however work.
// 
// Based on these assumptions, we can optimize the protocol for faster communication:
// - Master and slave send unicast packets.
// - We use raw ethernet and no higher-level protocol such as UDP or TCP/IP
// - We use the EtherType field of a packet as a length field. Actually, we only
//   use one byte of it since we have max 255 length packets.
//  
// Furthermore, there are a few code optimizations:
// - Minimize communication between ENC and MCU.
// - No unnecessary memory bank checks
// 
#include "RawEthernet.h"

// ENC28J60 Control Registers

// Control register definitions are a combination of address,
// bank number, and Ethernet/MAC/PHY indicator bits.
// - Register address       (bits 0-4)
// - Bank number            (bits 5-6)
// - MAC/PHY indicator      (bit 7)
#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)
#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 MACON4 Register Bit Definitions
#define MACON4_DEFER    0x40
#define MACON4_BPEN     0x20
#define MACON4_NOBKOFF  0x10
// 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 PHSTAT2H Register Bit Definitions
#define PHSTAT2H_LSTAT  0x04
// 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
// Buffer memory allocation within controlller.
// Assuming the controller is slower than the network, we allocate one transmit
// packet and leave the rest for receiving.
// The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
#define RXSTART_INIT    0x0
#define RXSTOP_INIT     (0x1FFF - 0x0600 - 2)   // note: MUST be odd (see errata point 13)
#define TXSTART_INIT    (0x1FFF - 0x0600)
// Maximum packet length: this software has a limit of 255 payload data bytes
// and then there is some ethernet overhead (srd, dst, len, fcs)
#define ENC28J60_MAX_PACKET_LEN ((uint16_t) 273)
// field or packet lengths
#define ETH_HEADER_LEN      14
#define ETH_CHECKSUM_LEN    4
#define ETH_ENVELOPE_LEN    (ETH_HEADER_LEN + ETH_CHECKSUM_LEN)
#define ETH_PAYLOAD_MIN     46
#define ETH_PAYLOAD_MAX     1500
#define ETH_PACKET_MIN      64
#define ETH_PACKET_MAX      1518
// field locations in ethernet packet
#define ETH_DST_MAC_P   0
#define ETH_SRC_MAC_P   6
#define ETH_TYPE_H_P    12
#define ETH_TYPE_L_P    13
#define ENC28J60_HAS_PENDING_TRANSMIT_ON_TRANSMIT

/* Static member initialization */
char RawEthernet::_arpReqHdr[10] = { 8, 6, 0, 1, 8, 0, 6, 4, 0, 1 };

/**
  * @brief   Constructor
  * @note
  * @param   mosi SPI master-out slave-in pin #
  * @param   miso SPI master-in slave-out pin #
  * @param   sclk SPI serial clock pin #
  * @param   cs SPI chip select pin #
  * @retval
  */
RawEthernet::RawEthernet(PinName mosi, PinName miso, PinName sclk, PinName cs, uint8_t myMac[6], uint8_t myIp[4]) :
    _spi(mosi, miso, sclk),
    _cs(cs)
{
    int i;

    for (i = 0; i < 6; i++)
        _mac[i] = myMac[i];

    for (i = 0; i < 4; i++)
        _ip[i] = myIp[i];
}

/**
  * @brief   Read operation
  * @note    Reads from ENC28J60 register
  * @param   op operation code
  * @param   address register address
  * @retval  Register value
  */
uint8_t RawEthernet::readOp(uint8_t op, uint8_t address)
{
    uint8_t result;

    _cs = 0;

    // issue read command
    _spi.write(op | (address & ADDR_MASK));

    // read data
    result = _spi.write(0x00);

    // do dummy read if needed (for mac and mii, see datasheet page 29)
    if (address & 0x80)
        result = _spi.write(0x00);

    // release CS
    _cs = 1;
    return(result);
}

/**
 * @brief   Write operation
 * @note    Writes to ENC28J60 register
 * @param   op operation code
 * @param   address register address
 * @retval  data data to be written
 */
void RawEthernet::writeOp(uint8_t op, uint8_t address, uint8_t data)
{
    _cs = 0;

    // issue write command
    _spi.write(op | (address & ADDR_MASK));

    // write data
    _spi.write(data);
    _cs = 1;
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void RawEthernet::setBank(uint8_t address)
{
    // set the bank (if needed)
    if ((address & BANK_MASK) != _bank) {
        // set the bank
        writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1 | ECON1_BSEL0));
        writeOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK) >> 5);
        _bank = (address & BANK_MASK);
    }
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
uint8_t RawEthernet::readReg(uint8_t address)
{
    setBank(address);
    readOp(ENC28J60_READ_CTRL_REG, address);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void RawEthernet::writeReg(uint8_t address, uint8_t data)
{
    setBank(address);
    writeOp(ENC28J60_WRITE_CTRL_REG, address, data);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void RawEthernet::readBuffer(uint16_t len, uint8_t* data)
{
    _cs = 0;

    // issue read command
    _spi.write(ENC28J60_READ_BUF_MEM);
    while (len) {
        len--;

        // read data
        *data = _spi.write(0x00);
        data++;
    }

    //*data = '\0';
    _cs = 1;
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void RawEthernet::writeBuffer(uint16_t len, uint8_t* data)
{
    _cs = 0;

    // issue write command
    _spi.write(ENC28J60_WRITE_BUF_MEM);
    while (len) {
        len--;

        // write data
        _spi.write(*data);
        data++;
    }

    _cs = 1;
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
uint16_t RawEthernet::phyReadH(uint8_t address)
{
    // set the right address and start the register read operation
    writeReg(MIREGADR, address);
    writeReg(MICMD, MICMD_MIIRD);
    wait_us(15);

    // wait until the PHY read completes
    while (readReg(MISTAT) & MISTAT_BUSY);

    // reset reading bit
    setBank(2);
    writeReg(MICMD, 0x00);

    return(readReg(MIRDH));
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void RawEthernet::phyWrite(uint8_t address, uint16_t data)
{
    setBank(2);

    // set the PHY register address
    writeReg(MIREGADR, address);

    // write the PHY data
    writeReg(MIWRL, data);
    writeReg(MIWRH, data >> 8);

    // wait until the PHY write completes
    setBank(3);
    while (readReg(MISTAT) & MISTAT_BUSY) {
        wait_us(15);
    }
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void RawEthernet::linkTo(uint8_t remoteMac[6])
{
    for (int i = 0; i < 6; i++)
        _remoteMac[i] = remoteMac[i];

    _spi.format(8, 0);          // 8bit, mode 0
    _spi.frequency(7000000);    // 7MHz
    wait(1);                    // 1 second for stable state
    // initialize I/O
    _cs = 1;
    // perform system reset
    writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
    wait_ms(50);
    // The CLKRDY does not work. See Rev. B4 Silicon Errata point. Just wait.
    //while(!(read(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
    writeReg(ERXSTL, RXSTART_INIT & 0xFF);
    writeReg(ERXSTH, RXSTART_INIT >> 8);

    // set receive pointer address
    writeReg(ERXRDPTL, RXSTART_INIT & 0xFF);
    writeReg(ERXRDPTH, RXSTART_INIT >> 8);

    // RX end
    writeReg(ERXNDL, RXSTOP_INIT & 0xFF);
    writeReg(ERXNDH, RXSTOP_INIT >> 8);

    // TX start
    writeReg(ETXSTL, TXSTART_INIT & 0xFF);
    writeReg(ETXSTH, TXSTART_INIT >> 8);

    // TX end (initialize for a packet with a payload of 1 byte)
    uint16_t    address = (TXSTART_INIT + ETH_HEADER_LEN + 1);
    writeReg(ETXNDL, address & 0xFF);
    writeReg(ETXNDH, address >> 8);

    // prepare the parts of the transmit packet that never change
    // write per-packet control byte (0x00 means use macon3 settings)
    writeReg(EWRPTL, (TXSTART_INIT) & 0xFF);
    writeReg(EWRPTH, (TXSTART_INIT) >> 8);
    writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);

    // write broadcast address as DST MAC
    uint8_t i = 0;
    while (i < 6) {
        writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0xFF);
        i++;
    }

    // set our MAC address as the SRC MAC into the transmit buffer
    // set the write pointer to start of transmit buffer area
    writeBuffer(6, const_cast<uint8_t*>(_mac));
    // First EtherType/length byte is always 0, initialize second byte to 1
    writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
    writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0x01);

    // do bank 1 stuff, packet filter:
    setBank(1);
    // only allow unicast packets destined for us and that have a correct CRC
    writeReg(ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN);

    // do bank 2 stuff
    setBank(2);
    // enable MAC receive, disable flow control (only needed in full-duplex)
    writeReg(MACON1, MACON1_MARXEN);
    // bring MAC out of reset
    writeReg(MACON2, 0x00);
    // enable automatic padding to 60bytes and CRC operations
    // also, force half-duplex operation
    writeReg(MACON3, MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN);
    // half-duplex only: back-off settings
    writeReg(MACON4, MACON4_DEFER | MACON4_BPEN | MACON4_NOBKOFF);
    // set the maximum packet size which the controller will accept
    // do not send packets longer than MAX_FRAMELEN:
    writeReg(MAMXFLL, ENC28J60_MAX_PACKET_LEN & 0xFF);
    writeReg(MAMXFLH, ENC28J60_MAX_PACKET_LEN >> 8);
    // set inter-frame gap (non-back-to-back)
    writeReg(MAIPGL, 0x12);
    writeReg(MAIPGH, 0x0C);
    // set inter-frame gap (back-to-back)
    writeReg(MABBIPG, 0x12);

    // do bank 3 stuff
    // write MAC address
    // NOTE: MAC address in ENC28J60 is byte-backward
    writeReg(MAADR5, _mac[0]);
    writeReg(MAADR4, _mac[1]);
    writeReg(MAADR3, _mac[2]);
    writeReg(MAADR2, _mac[3]);
    writeReg(MAADR1, _mac[4]);
    writeReg(MAADR0, _mac[5]);

    // no loopback of transmitted frames
    phyWrite(PHCON2, PHCON2_HDLDIS);

    // switch to bank 0
    setBank(ECON1);

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

    // enable packet reception
    writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);

    // change clkout from 6.25MHz to 12.5MHz
    writeReg(ECOCON, 2 & 0x7);
    wait_us(60);

    /* magjack leds configuration, see enc28j60 datasheet, page 11 */
    // LEDA=green,        LEDB=yellow,
    // LEDA=links status, LEDB=receive/transmit
    // phyWrite(PHLCON,0b0000 0100 0111 0110);
    phyWrite(PHLCON, 0x476);

    // wait until the link is up, then send a gratuitous ARP request
    // to inform any conected switch about my existence
    while (!isLinkUp());
    wait_ms(50);
    // send gratuitous ARP request
    gratuitousArp();
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
bool RawEthernet::isLinkUp(void)
{
    return(phyReadH(PHSTAT2) & PHSTAT2H_LSTAT);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
uint8_t RawEthernet::receive(uint8_t* buf, uint8_t maxlen)
{
    uint16_t    len;
    uint16_t    currentPacketPtr = _nextPacketPtr;
    uint16_t    address;
    uint16_t    framelen;

    // check if a packet has been received and buffered
    setBank(1);
    if (readReg(EPKTCNT) == 0)
        return(0);

    setBank(0);

    // Somehow, the read pointer is NOT already at the start of the next packet
    // even though we leave it in that state
    writeReg(ERDPTL, (_nextPacketPtr & 0xff));
    writeReg(ERDPTH, (_nextPacketPtr) >> 8);
    // Read the next packet pointer 
    _nextPacketPtr = readOp(ENC28J60_READ_BUF_MEM, 0);
    _nextPacketPtr |= readOp(ENC28J60_READ_BUF_MEM, 0) << 8;
    // read the frame length
    framelen = readOp(ENC28J60_READ_BUF_MEM, 0);
    framelen |= readOp(ENC28J60_READ_BUF_MEM, 0) << 8;
    if (maxlen > framelen - 14) {
        // subtract eth source, dest and length fields
        maxlen = framelen - 14;
    }

    // Read EtherType (we use this as a length field) (note +6 for receive vectors)
    // Set read pointer (taking care of wrap-around)
    address = currentPacketPtr + ETH_TYPE_H_P + 6;
    if (address > RXSTOP_INIT) {
        address -= (RXSTOP_INIT - RXSTART_INIT + 1);
    }

    writeReg(ERDPTL, (address & 0xff));
    writeReg(ERDPTH, (address) >> 8);
    readBuffer(6, _remoteMac);

    // A value of less than 0x05dc in the EtherType has to be interpreted as length.
    // This is what we do here.
    // The length is 16 bit. The upper 8 bits must be zero
    // otherwise it is not our packet.
    len = readOp(ENC28J60_READ_BUF_MEM, 0);
    if (len == 0) {
        // read the lower byte of the length field
        len = readOp(ENC28J60_READ_BUF_MEM, 0);
        // limit retrieve length to maxlen, ignoring anything else
        if (len > maxlen) {
            len = maxlen;
        }

        // copy payload data from the receive buffer
        readBuffer(len, buf);
    }
    else
        len = 0;

    // Move the RX read pointer to the start of the next received packet
    // This frees the memory we just read out.
    // However, compensate for the errata point 13, rev B4: enver write an even address!
    if ((_nextPacketPtr - 1 < RXSTART_INIT) || (_nextPacketPtr - 1 > RXSTOP_INIT)) {
        writeReg(ERXRDPTL, (RXSTOP_INIT) & 0xFF);
        writeReg(ERXRDPTH, (RXSTOP_INIT) >> 8);
    }
    else {
        writeReg(ERXRDPTL, (_nextPacketPtr - 1) & 0xFF);
        writeReg(ERXRDPTH, (_nextPacketPtr - 1) >> 8);
    }

    // Decrement the packet counter indicate we are done with this packet
    writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
    return(len);
}

// sends gratuitous ARP request (spontaneous arp request) to teach

// switches what our mac is.
void RawEthernet::gratuitousArp()
{
    uint8_t     i = 0;
    uint16_t    address;
    setBank(0);

    // (control byte, and SRC MAC have already been set during init)
#ifdef ENC28J60_HAS_PENDING_TRANSMIT_ON_TRANSMIT
    // Check no transmit in progress

    while (readOp(ENC28J60_READ_CTRL_REG, ECON1) & ECON1_TXRTS) {
        // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.
        if ((readReg(EIR) & EIR_TXERIF)) {
            writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
            writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
        }
    }

#else
    // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.

    if ((readReg(EIR) & EIR_TXERIF)) {
        writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
        writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
    }
#endif
    // Set the write pointer to start of transmit buffer area

    // +1 to skip the per packet control byte and write directly the mac
    // The control byte was set to zero during initialisation and remains like that.
    writeReg(EWRPTL, (TXSTART_INIT + 1) & 0xFF);
    writeReg(EWRPTH, (TXSTART_INIT + 1) >> 8);
    // write a broadcase destination mac (all ff):
    while (i < 6) {
        writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0xFF);
        i++;
    }

    // The mac in the ethernet field does not need to be changed.
    // Set the write pointer to the first byte of the EtherType field
    address = TXSTART_INIT + 1 + ETH_TYPE_H_P;
    writeReg(EWRPTL, address & 0xFF);
    writeReg(EWRPTH, address >> 8);
    // there are 10 fixed bytes in the arp request
    i = 0;
    while (i < 10) {
        writeOp(ENC28J60_WRITE_BUF_MEM, 0, (_arpReqHdr[i]));
        i++;
    }

    i = 0;
    while (i < 6) {
        writeOp(ENC28J60_WRITE_BUF_MEM, 0, _mac[i]);
        i++;
    }

    i = 0;
    while (i < 4) {
        writeOp(ENC28J60_WRITE_BUF_MEM, 0, _ip[i]);
        i++;
    }

    // target data:
    i = 0;
    while (i < 6) {
        writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0xff);
        i++;
    }

    // to self, for gratuitous arp:
    i = 0;
    while (i < 4) {
        writeOp(ENC28J60_WRITE_BUF_MEM, 0, _ip[i]);
        i++;
    }

    // Set the TXND pointer to correspond to the payload size given
    address = (TXSTART_INIT + 42);
    writeReg(ETXNDL, address & 0xFF);
    writeReg(ETXNDH, address >> 8);

    // Send the contents of the transmit buffer onto the network
    writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void RawEthernet::send(uint8_t* buf, uint8_t len)
{
    uint16_t    address;
    setBank(0);

    // (control byte, and SRC MAC have already been set during init)
#ifdef ENC28J60_HAS_PENDING_TRANSMIT_ON_TRANSMIT
    // Check no transmit in progress

    while (readOp(ENC28J60_READ_CTRL_REG, ECON1) & ECON1_TXRTS) {
        // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.
        if ((readReg(EIR) & EIR_TXERIF)) {
            writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
            writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
        }
    }

#else
    // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.

    if ((readReg(EIR) & EIR_TXERIF)) {
        writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST);
        writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST);
    }
#endif
    // Set the write pointer to start of transmit buffer area

    // +1 to skip the per packet control byte and write directly the mac
    // The control byte was set to zero during initialisation and remains like that.
    writeReg(EWRPTL, (TXSTART_INIT + 1) & 0xFF);
    writeReg(EWRPTH, (TXSTART_INIT + 1) >> 8);
    writeBuffer(6, _remoteMac);

    // Set the write pointer to the first byte of the EtherType field
    // (field after the mac address). This is the 802.3 length field.
    address = TXSTART_INIT + 1 + ETH_TYPE_H_P;
    writeReg(EWRPTL, address & 0xFF);
    writeReg(EWRPTH, address >> 8);
    // write the length of the data in the ethernet type field.
    // The type field to be interpreted by the receiver as ieee802.3 length if
    // the value is less than 0x05dc (see e.g. http://www.cavebear.com/archive/cavebear/Ethernet/type.html ):
    writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0);
    writeOp(ENC28J60_WRITE_BUF_MEM, 0, len);
    // Copy the payload into the transmit buffer
    writeBuffer(len, buf);  // remove dest mac and write the rest
    // Set the TXND pointer to correspond to the payload size given
    address = (TXSTART_INIT + ETH_HEADER_LEN + len);
    writeReg(ETXNDL, address & 0xFF);
    writeReg(ETXNDH, address >> 8);

    // Send the contents of the transmit buffer onto the network
    writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
uint8_t RawEthernet::getRev(void)
{
    uint8_t rev;

    rev = readReg(EREVID);

    // microchip forgott to step the number on the silcon when they
    // released the revision B7. 6 is now rev B7. We still have
    // to see what they do when they release B8. At the moment
    // there is no B8 out yet
    if (rev > 5)
        rev++;
    return(rev);
}