Hardware driver for ENC28J60

Based on the hardware abstraction layer for my TCP/IP stack, I wrote a software driver for the ENC28J60 ethernet module.  As said before the module is very cheap but has quite some issues and the software is trying to cover these issues as much as possible.  However there was one I couldn’t solve completely: interrupt on packet reception; that one is completely unreliable even with the workarounds proposed by Microchip.  Thus I decided to stay away from the interrupts, which is not so bad, as I have now more control on the packet buffer usage.

The ENC28J60 module communicates with the micro controller via SPI, the maximum speed of the SPI interface is 20MHz, so I had to downscale the STM32F407 as the APB2 clock is running at 84MHz, a prescaler of 8 is doing the trick.  I tried to run with prescaler of 4 but it yielded an unstable ENC28J60.  Besides the SPI connection, you’ll also need the /CS (chip select for the SPI) and the /RESET pins.

The hardware abstraction layer requires at least 3 methods:

  • initialization of the hardware
  • read a packet
  • send a packet

Developing the driver is not so difficult if you keep the datasheet at hand but at several occasions you also need to take into account the errata:

ENC28J60 (1.2 MiB)

ENC28J60 ERRATA (261.8 KiB)

My version of the hardware driver can be found here:

drvEnc28j60.h (12.9 KiB)

drvEnc28j60.c (11.4 KiB)

In the drvEnc28j60_packetRecv function you’ll see a strange piece of code at the end which forces the Rx read pointer to next packet to be odd all the time; this was one of the errata I had to implement in the driver.  Without this the ENC28J60’s internal packet buffer got corrupted very often.

Tagged , , , . Bookmark the permalink.

33 Responses to Hardware driver for ENC28J60

  1. Gojavac says:

    Hi.

    I looked for your ENC28J60 implementation with TCP/IP protocol.

    Is this for posts prepared to work? How can I connect let’s say to some webpage and get data back?

    Thank you for reply 😉

    • PatrickPatrick says:

      My TCP/IP implementation isn’t ready. Unfortunately I’m too busy for the moment to continue on my TCP/IP stack. The principle is pretty much the same as the rest of the network stack with one major difference: TCP/IP will require fragments; unlike UDP not all data will fit into one single packet. The difficulty is to reassemble the packets, which may arrive in random order; so the stack will require quite some memory to reassemble these packets…
      Webpages require indeed TCP/IP. You could implement a ‘quick-and-dirty’ solution when your webpage is fairly simple and doesn’t require more than 1518-1538 bytes (the maximum length of the packet). In that case you can implement a TCP/IP protocol with a single packet.

      I hope to continue on this after the holidays, September…

      • Gojavac says:

        Let’s see if I’m thinking correct.
        I initialialize enc and connect with eth cable.

        Then, check for my router to get proper IP for enc, and tried to connect to them via HTTP request.
        And actually, when I try to connect, data is stored in his RX buffer?
        When I read his RX buffer, I will read HTTP request headers?

        • PatrickPatrick says:

          Not entirely…
          You need an IP address first, either a fixed one or get one via DHCP (that module works in my stack). You will get data in the RX buffers as soon as the MAC address (hardware address) matches the one in the packets transmitted in your network, or when the packet is a broadcast packet.
          Then you need to figure out what type of packet it is. In principle the first bytes in packets are part of the headers, and via the headers you should be able to determine what this packet really is, look here (from bottom to top)
          The http request headers can be found a couple of levels deeper in the stack; but you will have to traverse all layers from the figure in the above post. Hopefully they are sent in one single segment, if not, you will have to reassemble the packet.
          You can always have a look at the lwIP project, as mine is been derived from that albeit in a simplified manner.

  2. Pavel says:

    Hi.
    I am very interested in your tcp/ip implementation.
    Could you make an example for PING.
    Thanks in advance.

    • PatrickPatrick says:

      I don’t have a simple implementation for ping. It’s part of the tcp/ip stack, which is now half implemented (I’m still missing tcp as I don’t need it yet).

  3. Gojavac says:

    Hi,

    Can you post entire Coocox project here, so I can see how to use this in practice?
    I would like to test, if I will get any response like you did on image from your LCD.

    Thanks for answer.

    • Gojavac says:

      Sorry for double post.

      Are you planning to make port to LwIP?
      I’m trying to do that without luck.

      I’m stuck how to properly read packets.
      If you can post coocox project, that would be really great.

      • PatrickPatrick says:

        Two times no…
        I’m replacing LwIP, I don’t need their full stack. If you know what you’re doing you can easily rewrite bits and pieces of LwIP and have a very light implementation. Since I don’t need to go any further than UDP, this is more than fine for me.

        Posting my full code won’t help you a thing, you’ll need my hardware as well.

  4. Gojavac says:

    Thanks for your post.

    Actually, I have Discovery board, ENC and also this LCD (SSD1963, ILI9325)?

    And no problem if you don’t want to share it.

    • PatrickPatrick says:

      Not that I don’t want to share, but my setup is far more complex than that. I’m using quite some internally developed libraries (timers, scheduler, debugging, …) This setup is what I need for my home automation system I’m developing. It is a cooperation of 8 CoIDE projects, each responsible for a piece in my software. Unfortunately this kind of setup works only for me.

  5. Thank you so much. But i don’t know how to config IP address to connect with webserver. Sorry my Enghlish so bad.
    Ex: i want config stm32f407 with webserver and control it by that. I hope you can help me…thank you

    • PatrickPatrick says:

      If I understand it right, you want to run a webserver on your STM32F4 and access it via another computer.
      You need to write more than only the hardware driver, you will need a complete TCP/IP stack if you want a real webserver. However in most cases simple UDP commands could be sufficient to control your STM32F4 and send some data back.
      Now for the IP address, it must be embedded in the Internet Layer (IPv4), check my post about the IP stack for the ENC28J60, which explains the stack’s structure.
      In my downloads section you can find more documentation and a working stack up to UDP. I didn’t implement TCP as I don’t need it, and it is quite heavy.

  6. chaitanya says:

    Hi, Nice work…

    Can u upload err.h file that is used in your tcp library. That would be a great help for me.

    • chaitanya says:

      i managed to get that work from lw ip, but i coulnot find massert.h and driver/nic.h please include them……..

      • PatrickPatrick says:

        err.h, massert.h, massert.c and nic.h have been added

        • chaitanya says:

          please post one main function to send a udp packet, i could not make out if my hardware fault or code fault, as my code compiles fine but wireshark is not detecting packets as i intended, please help..

          • PatrickPatrick says:

            Did you configure the network adapter correctly (MAC address, IP address, netmask, gateway, …) via the netif_add function? MAC address is mandatory, others can be left blank if you use DHCP.
            Then check the NTP modules (ntp.c and ntp.h) they’re using a UDP socket.
            netif_t *enc28j60 = NULL;
            uint32_t ip_addr = inet_addr(192,168,1,30);
            uint32_t netmask = inet_addr(255,255,255,0);
            uint32_t gw = inet_addr(192,168,1,1);
            enc28j60 = malloc(sizeof(netif_t));
            memset(enc28j60, 0, sizeof(netif_t));
            memcpy(enc28j60->hwaddr, MAC, 6);
            enc28j60 = netif_add(enc28j60, ip_addr, netmask, gw, NULL, drvEnc28j60_init, eth_input);
            netif_set_default(enc28j60);

          • chaitanya says:

            My main is as follows,

            netif_t interface;
            netif_t *enc28j60;
            pbuf_t data;
            pbuf_t *packet;
            uint8_t a[] = {‘c’, ‘h’, ‘a’, ‘i’, ‘t’, ‘a’, ‘n’, ‘y’, ‘a’};
            int i=0;
            udp_pcb_t *pcb = udp_new();
            uint32_t myip= 0x0A02A8C0;
            uint32_t mask= 0xffffff00;
            uint32_t gwip= 0x0102A8C0;
            uint8_t mymac[6]= {172, 172, 172, 172, 172, 172};
            enc28j60= &interface;
            packet= &data;
            //mac setup

            //ipsetup
            enc28j60->ip_addr = myip;
            enc28j60->netmask = mask;
            enc28j60->gateway = gwip;
            enc28j60->flags = 0;
            enc28j60->dhcp = NULL;
            enc28j60->status_callback = NULL;
            enc28j60->link_callback = NULL;
            for(i=0; ihwaddr[i] = mymac[i];
            packet->length= 600;
            packet->state = PBUF_OCCUPIED;

            drvEnc28j60_init(enc28j60);
            netif_set_up (enc28j60);
            netif_set_default(enc28j60);
            netif_init();

            while(1)
            {
            udp_sendto(pcb, packet, 0x00000000, 504);
            delay_nms(10000);
            }
            }

            iam getting packets on wireshark but only when i press reset on my discovvery board, but not if i use real IP in udpsend(); (working with brodcast ip only when i press rest, burst of udp packets are seen in wireshark from my enc28j60); suggest what might have gone wrong?

  7. chaitanya says:

    and enc 28j60 is not sending packets after sending for few seconds. what might be the issue?

  8. chaitanya says:

    as pbuf size crosses max udp length wire shark rejects the packets as it crossed valid udp max length..

    • chaitanya says:

      sorry there is some mistake in the pasted code;
      for(i=0; ihwaddr[i] = mymac[i];
      this is the peice of code i used to assign mac. i am poor in C programming so use basics to get the job done.. 🙂

      • chaitanya says:

        omg,

        the code changes as soon as i posted it, may be bacause of html tags found in the code, actually i used a for loop to assign ihwaddr[i] = mymac[i] from i=0 to 5,

  9. Kevin says:

    Hello,

    I’ve also written an enc28j60 driver for stm32f407 Discovery board. I seem to be getting corrupted reads from the SPI interface. I am using the same SPI settings as you. A lot of time writing and reading registers works, but writing to the internal memory using WBM, and reading it back using RBM is not working. Also, the ENC28J60 seems to be receiving packets correclty, but when I try to read them using the RBM command the output doesn’t make sense. For example, the npp and packet length are way too big.

    Do you have any idea what can be causing this problem?

    Thanks,

    Kevin

    • PatrickPatrick says:

      I had a similar problem initially and I solved it by putting a very big capacitor over the power supply of the enc28j60. That device is extremely sensitive to small variations in the PSU.

      • Kevin says:

        Thanks Patrick.

        I found out that my initialization order was wrong. I was initializing the MAC / PHY modules before configuring the RX buffer. Once I initialized the RX buffer before everything else, some of the problem went away. I am now able to read packets correctly from the buffer, but only the first 4 or 5 packets. After about 5 packets, the RBM starts giving me garbage again. I am following the Errata about setting ERXRDPTR to odd values so this shouldn’t be the problem.

        I am using the Olimex ENC28J60H module, which apparently already has a couple of 100nF capacitors between VCC and ground. You think I should add something bigger?

        Also the STM32F407 Discovery board has a 3V pin and a 5V pin. Which one did you use to power the ENC28J60? Neither of them are giving me good results.

  10. PatrickPatrick says:

    None of them, I’m using a separate power supply derived from a 12V adapter. I made 2 separate regulators (switching ones) for both 5V and 3V3 lines.

  11. Mark says:

    Hi!
    Can you help me, i have one more question what if i want to power it from an external power suply like battery. do i connect the drivers IC from the battery or after the voltage regulator circuit? I want to use this tyoe of connectors http://hardware.be/wieland/ but I’m not sure. Can u post the battery connection?

    • PatrickPatrick says:

      You always need a regulator, either stepdown or stepup, depending on the available voltage. For batteries you have dedicated circuits to maintain a specified voltage level. Plenty of circuits exist, just Google them. I can’t help you with a battery-based regulator, my circuits a powered via a DC-adapter and a stepdown regulator.

  12. Avinash says:

    Hello,

    Thanks for sharing!

    I am developing a driver for ENC28J60 on STM32F0 (cortex m0).

    Everything is fine. I am written the initialization code. Also code to transmit data. Also created a sample ARP request packet and successfully sent it. I also saw it appearing correctly in WireShark (sniffer tool). But i am unable to receive any data. Nor even the ARP reply from Ethernet switch. The packet counter is fixed at 0. Sometimes it goes to 1 but still then the buffer memory does not have any meaningful data. All garbage only. The first byte indicates the len of packet. But it id so high value that it cannot be true. Can you tell what may be the problem?

Leave a Reply

Your email address will not be published. Required fields are marked *

WordPress Anti Spam by WP-SpamShield