arduino-ethernet-enc28j60

Conectar Arduino a Internet con módulo Ethernet ENC28J60

¿Qué es el ENC28J60?

El ENC28J60 es un controlador de Ethernet diseñado para sistemas embebidos fabricado por Microchip Technology Inc. Podemos usar el ENC28J60 junto a un procesador como Arduino para conectar nuestros proyectos de electrónica y robótica con Internet.

El ENC28J60 se controla a través de bus SPI, por lo que la conexión con Arduino es muy sencilla. El ENC28J60 opera a 3.3, pero es tolerante a señales de 5V, por lo que su integración es aún más sencilla.

El ENC28J60 soporta velocidades de 10 Mbits/s y los modos Dúplex (Full-Duplex) y Semi-dúplex (Half-Duplex) con detección y corrección automática de la polaridad. El ENC28J60 cumple con las especificaciones IEEE 802.3 10BASE-T.

El ENC28J60 incorpora filtrado de paquetes para limitar el número de paquetes entrantes, un módulo DMA interno para facilitar el flujo de datos y hardware especifico para el cálculo de las sumas de control (IP checksums).

El ENC28J60 es uno de los procesadores más baratos para dotar conectividad a nuestros proyectos, y es más barato que otras alternativas como el W5100.

Sin embargo, el ENC28J60 carece de una pila de TCP/IP por hardware como sí que incluye el W5100. Por tanto, su uso es más complejo y requiere una mayor carga del procesador.

Precio

El ENC28J60 es un módulo muy barato, siendo precisamente una de sus principales ventajas. Podemos encontrarlo por unos 2.10€, buscando en vendedores internacionales de eBay o AliExpress.

arduino-ethernet-enc28j60-componente

Esquema de montaje

La conexión de un módulo de Ethernet ENC28J60 es muy sencilla ya que la comunicación se realiza a través del SPI como vimos en esta entrada.

arduino-ethernet-enc28j60-esquema

La conexión en este caso, vista desde Arduino, sería la siguiente.

arduino-ethernet-ENC28J60-conexin

Ejemplos de código

Para controlar los módulos de Ethernet ENC28J60 usaremos la librería Ethercard.h disponible en este enlace.

La librería proporciona varios ejemplos de uso del ENC28J60 que resulta aconsejable revisar. Los siguientes ejemplos son modificaciones a partir de los disponibles en la librería.

Cliente Ethernet – Leer páginas web

En este ejemplo Arduino actúa como cliente, es decir, se conecta a una página web para leerla. Leer una página completa y volcarla por el puerto serie es muy lento, y es una de las muestras de las limitaciones de Arduino frente a un ordenador.

Sin embargo, puede ser útil para Arduino capture información desde un servidor. Por ejemplo, podemos hacer que sincronice la hora, que lea una serie de parámetros de un fichero de texto, que realice una determinada acción si existe un fichero, etc.

Para mostrar en este ejemplo esta capacidad de lectura de datos desde un servidor en Internet vamos a usar www.pasted.co, una de muchas páginas web que nos permiten añadir un texto para compartirlo con más gente.

En la página http://www.pasted.co/2434bc64 he pegado el texto 1.2.3.4.5. Los ‘~’ los usaremos como separadores para encontrar el texto deseado ‘1.2.3.4.5’, que simula una serie de parámetros que queremos capturar de un servidor.

El siguiente ejemplo se conecta con esta dirección y realiza la búsqueda del texto 1.2.3.4.5, que muestra por puerto serie. En un ejemplo real emplearíamos estos valores, por ejemplo, para controlar un robot, cambiar los parámetros de medición de una estación, encender o apagar un dispositivo, etc.

Arduino no tiene potencia suficiente para gestionar la encriptación necesaria en páginas https, por lo que sólo podremos leer páginas http.

#include <EtherCard.h>

static byte mymac[] = { 0xDD, 0xDD, 0xDD, 0x00, 0x01, 0x05 };
static byte myip[] = { 192, 168, 1, 177 };

byte Ethernet::buffer[700];
static uint32_t timer;

const char website[] PROGMEM = "www.pasted.co";
const char dataLocationC[] = "/2434bc64";

// called when the client request is complete
static void my_callback (byte status, word off, word len) {
  Serial.println(">>>");
  Ethernet::buffer[off+300] = 0;
  Serial.print((const char*) Ethernet::buffer + off);
  Serial.println("...");
}

void setup () {
  Serial.begin(57600);
  Serial.println(F("\n[webClient]"));

  if (ether.begin(sizeof Ethernet::buffer, mymac) == 0) 
    Serial.println(F("Failed to access Ethernet controller"));
  if (!ether.dhcpSetup())
    Serial.println(F("DHCP failed"));

  ether.printIp("IP:  ", ether.myip);
  ether.printIp("GW:  ", ether.gwip);  
  ether.printIp("DNS: ", ether.dnsip);  

  if (!ether.dnsLookup(website))
    Serial.println("DNS failed");
    
  ether.printIp("SRV: ", ether.hisip);
}

void loop () {
  ether.packetLoop(ether.packetReceive());
  
  if (millis() > timer) {
    timer = millis() + 5000;
    Serial.println();
    Serial.print("<<< REQ ");

    // ether.browseUrl(PSTR(dataLocationC), "", website, my_callback); // No funciona en IDE Standard
    ether.browseUrl(PSTR("/2434bc64"), "", website, my_callback);  // En entorno IDE
  }
}

Servidor Ethernet – Visualizar entradas

En el siguiente código Arduino actúa como servidor, es decir, devuelve una página web cuando un cliente (un PC, un móvil, otro Arduino…) se conecta a él.

En este caso, vamos a mostrar una página web con el estado de las entradas digitales y analógicas de Arduino. Para ello, simplemente servimos una cadena de texto en html, en la que incluimos los valores de las entradas.

arduino-ethernet-ENC28J60-salida2 ar conectado en la misma red local que el Arduino. Si queremos conectar desde internet deberemos definir una conexión bridge en el router que direcciones la IP externa a la IP local del Arduino.

#include <EtherCard.h>

static byte mymac[] = { 0xDD, 0xDD, 0xDD, 0x00, 0x01, 0x05 };
static byte myip[] = { 192, 168, 1, 177 };
byte Ethernet::buffer[700];

void setup() {

  Serial.begin(9600);

  if (!ether.begin(sizeof Ethernet::buffer, mymac, 10))
    Serial.println("No se ha podido acceder a la controlador Ethernet");
  else
    Serial.println("Controlador Ethernet inicializado");

  if (!ether.staticSetup(myip))
    Serial.println("No se pudo establecer la dirección IP");
  Serial.println();
}

static word mainPage()
{
  BufferFiller bfill = ether.tcpOffset();
  bfill.emit_p(PSTR("HTTP/1.0 200 OKrn"
    "Content-Type: text/htmlrnPragma: no-cachernRefresh: 5rnrn"
    "<html><head><title>Luis Llamas</title></head>"
    "<body>"
    "<div style='text-align:center;'>"
    "<h1>Entradas digitales</h1>"
    "Tiempo transcurrido : $L s"
    "<br /><br />D00: $D"
    "<br /><br />D01: $D"
    "<br /><br />D02: $D"
    "<br /><br />D03: $D"
    "<br /><br />D04: $D"
    "<br /><br />D05: $D"
    "<br /><br />D06: $D"
    "<br /><br />D07: $D"
    "<br /><br />D08: $D"
    "<br /><br />D09: $D"
    "<br /><br />D10: $D"
    "<br /><br />D11: $D"
    "<br /><br />D12: $D"
    "<br /><br />D13: $D"

    "<h1>Entradas analogicas</h1>"
    "<br /><br />AN0: $D"
    "<br /><br />AN1: $D"
    "<br /><br />AN2: $D"
    "<br /><br />AN3: $D"
    "<br /><br />AN4: $D"
    "<br /><br />AN5: $D"
    "<br /><br />AN6: $D"
    "<br /><br />"
    "</body></html>"), 
    millis() / 1000, 
    digitalRead(0),
    digitalRead(1),
    digitalRead(2),
    digitalRead(3),
    digitalRead(4),
    digitalRead(5),
    digitalRead(6),
    digitalRead(7),
    digitalRead(8),
    digitalRead(9),
    digitalRead(10),
    digitalRead(11),
    digitalRead(12),
    digitalRead(13), 
    analogRead(0),
    analogRead(1),
    analogRead(2),
    analogRead(3),
    analogRead(4),
    analogRead(5),
    analogRead(6));

  return bfill.position();
}

void loop() 
{
  // wait for an incoming TCP packet, but ignore its contents
  if (ether.packetLoop(ether.packetReceive())) 
  {
    ether.httpServerReply(mainPage());
  }
}

Servidor Ethernet – Controlar salidas

El este ejemplo Arduino actúa también como servidor, pero esta vez queremos que el usuario pueda realizar acciones sobre Arduino a través de la página web que servimos.

En este caso, vamos a controlar dos salidas digitales, a las que podemos conectar un Led para visualizar la respuesta.

Para ello, en primer lugar servimos la página web de forma similar al ejemplo anterior, pero en esta incluimos dos botones para cada salida.

arduino-ethernet-ENC28J60-salida1

Al pulsar en cada botón se realiza una nueva solicitud a Arduino, con diferente URL a la original. Arduino captura la nueva solicitud, y emplea la URL recibida para realizar las acciones oportunas.

#include <EtherCard.h>

static byte mymac[] = { 0xDD, 0xDD, 0xDD, 0x00, 0x01, 0x05 };
static byte myip[] = { 192, 168, 1, 177 };
byte Ethernet::buffer[700];

const int pinLed1 = 13;
const int pinLed2 = A0;
char* statusLed1 = "OFF";
char* statusLed2 = "OFF";

void setup() {

  Serial.begin(9600);

  if (!ether.begin(sizeof Ethernet::buffer, mymac, 10))
    Serial.println("No se ha podido acceder a la controlador Ethernet");
  else
    Serial.println("Controlador Ethernet inicializado");

  if (!ether.staticSetup(myip))
    Serial.println("No se pudo establecer la dirección IP");
  Serial.println();

  pinMode(pinLed1, OUTPUT);
  pinMode(pinLed2, OUTPUT);
  digitalWrite(pinLed1, LOW);
  digitalWrite(pinLed2, LOW);
}

static word mainPage()
{
  BufferFiller bfill = ether.tcpOffset();
  bfill.emit_p(PSTR("HTTP/1.0 200 OKrn"
    "Content-Type: text/htmlrnPragma: no-cachernRefresh: 5rnrn"
    "<html><head><title>Luis Llamas</title></head>"
    "<body>"
    "<div style='text-align:center;'>"
    "<h1>Salidas digitales</h1>"
    "<br /><br />Estado LED 1 = $S<br />"
    "<a href='./?data1=0'><input type='button' value='OFF'></a>"
    "<a href='./?data1=1'><input type='button' value='ON'></a>"
    "<br /><br />Estado LED 2 = $S<br />"
    "<a href='./?data2=0'><input type='button' value='OFF'></a>"
    "<a href='./?data2=1'><input type='button' value='ON'></a>"
    "<br /></div>\n</body></html>"), statusLed1, statusLed2);

  return bfill.position();
}

void loop() 
{
  word len = ether.packetReceive();
  word pos = ether.packetLoop(len);

  if (pos) 
  {
    if (strstr((char *)Ethernet::buffer + pos, "GET /?data1=0") != 0) {
      Serial.println("Led1 OFF");
      digitalWrite(pinLed1, LOW);
      statusLed1 = "OFF";
    }

    if (strstr((char *)Ethernet::buffer + pos, "GET /?data1=1") != 0) {
      Serial.println("Led1 ON");
      digitalWrite(pinLed1, HIGH);
      statusLed1 = "ON";
    }

    if (strstr((char *)Ethernet::buffer + pos, "GET /?data2=0") != 0) {
      Serial.println("Led2 OFF recieved");
      digitalWrite(pinLed2, LOW);
      statusLed2 = "OFF";
    }

    if (strstr((char *)Ethernet::buffer + pos, "GET /?data2=1") != 0) {
      Serial.println("Led2 ON");
      digitalWrite(pinLed2, HIGH);
      statusLed2 = "ON";
    }

    ether.httpServerReply(mainPage());
  }
}

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github. github-full