martes, 4 de diciembre de 2012

Uso del módulo Enc28J60 con arduino, 2a. parte, control de salidas digitales desde internet



Protocolos de comunicación: SPI
SPI es un protocolo serial síncrono que se utiliza para comunicar un microcontrolador con otro y con periféricos a distancias cortas. Para hacer una conexion SPI siempre habrá un dispositivo maestro (usualmente un microcontrolador) que controlará uno o varios periféricos (esclavos), se utilizan por lo generar 3 lineas de conexión  y una de selección que son:

* SO o MISO (Master In Slave Out). La linea que utiliza el esclavo para enviar datos al maestro
* SI o MOSI (Master
Out Slave In). Datos del maestro al esclavo.
* SCK (Serial
clock). Pulsos de reloj para sincronizar la comunicación
* CS o
Select. Se usa por el master para habilitar o deshabilitar un determinado periférico

Los
microcontroladores atmel incluyen las 4 lineas para usar protocolo SPI las cuales obviamente están presentes en los pines del arduino, para el arduino uno se deben usar: Pin digital 10 para CS, Pin digital 11 para SI, Pin digital 12 para SO y Pin digital 13 para SCK, en el caso del arduino mega se usaría los sig. pines: 50 (MISO), 51 (MOSI), 52 (SCK), and 53 (CS).

La
alimentación del módulo puede  es a 3.3 los cuales son suministrados por el arduino

Protocolos de comunicación: HTTP
Hypertext Transfer Protocol o HTTP (en español protocolo de transferencia de hipertexto) es el protocolo usado en cada transacción de la Web.
Es un protocolo orientado a transacciones y sigue el esquema petición-respuesta entre un cliente y un servidor.
Al cliente que efectúa la petición (un navegador web o un spider) se lo conoce como "user agent" (agente del usuario). Y a la información transmitida se la llama recurso y se la identifica mediante un localizador uniforme de recursos (URL). Los recursos pueden ser archivos, el resultado de la ejecución de un programa, una consulta a una base de datos, en este caso en particular la respuesta será la lectura de un sensor o el resultado de la ejecución de un proceso en Arduino, etc.
El módulo ENC28J60 y su librería desarrollada para el arduino lo convierten en un pequeño servidor WEB capaz de responder a interacciones del usuario desde cualquier navegador.

Librería ENC28J60
El módulo que usaremos está basado en un chip ENC28J60 de Microchip que interactúa con Arduino y le permite la comunicación de datos mediante un puerto Ethernet.
Se integra el controlador de la MAC address, un buffer de 8 KB de transmisión/recepción de de paquetes de doble puerto y una FIFO circular que se gestionan a nivel de hardware, además permite la programación de retransmisión de datos en caso de colisión.
La librería de funciones fue desarrollada por Jean-Claude Wippler del sitio http://jeelabs.org y liberada con licencia creative commons como software libre, a partir de ahí ha sido utilizada por diferentes fabricantes para soportar diferentes productos basados en el mismo controlador.


Antes de iniciar la operación de nuestro módulo debemos llevara acabo la configuración de los parámetros para comunicación en la red ethernet, datos como:
Dirección IP
Máscara de red
Puerta de enlace
Tamaño del buffer de datos.
También es factible operar con una dirección IP Dinámica, con lo que sólo será necesario llamara a la función DHCP para solicitarla al gateway de la red.

Las principales funciones incluidas en la librería son:
ether.begin : Crea un objeto para interactuar con el controlador
ether.packetReceive(); Devuelve la cantidad de paquetes pendientes de recibir que estan almacenados en el buffer.
ether.packetLoop(len); Da entrada todos los paquetes almacenados en el buffer, len es la cantidad de paquetes que se van a recibir;
ether.httpServerReply(homePage()); Responde al cliente que realizo la petición, homePage es una variable que apunta a un string o cadena de texto con formato HTML con una respuesta válida para el cliente.

El siguiente programa controla el encendido y apagado de un led mediante una pagina web que se accede desde la direccion ip del modulo enc-28j60 conectado al arduino, el diagrama de conexion del modulo con el arduino es el que ya vimos en la primera parte de este tutorial mas un led conectado en el pin digital 2 que sera el que controlemos via red:

// Arduino demo sketch for testing ethernet
// 2012-09-20 lmdiaz@tecnotinker.com

// This sketch is derived from RF12eth.pde:
// May 2010, Andras Tucsni, http://opensource.org/licenses/mit-license.php

#include <EtherCard.h>

#define DEBUG   0   // set to 1 to display free RAM on web page
#define SERIAL  0   // set to 1 to show incoming requests on serial port

// buffer for an outgoing data packet
static char outCount = -1;


// configuration, as stored in EEPROM
struct Config {
    byte band;
    byte group;
    byte collect;
    word refresh;
    byte valid; // keep this as last byte
} config;


#define NUM_MESSAGES  10    // Number of messages saved in history
#define MESSAGE_TRUNC 15    // Truncate message payload to reduce memory use

static BufferFiller bfill;  // used as cursor while filling the buffer

static byte history_rcvd[NUM_MESSAGES][MESSAGE_TRUNC+1]; //history record
static byte history_len[NUM_MESSAGES]; // # of RF12 messages+header in history
static byte next_msg;       // pointer to next rf12rcvd line
static word msgs_rcvd;      // total number of lines received modulo 10,000
byte ledstatus = 0;
byte Ethernet::buffer[1000];   // tcp/ip send and receive buffer

// ethernet interface mac address
static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
// ethernet interface ip address
static byte myip[] = { 192,168,1,188 };
// gateway ip address
static byte gwip[] = { 192,168,1,1 };

#if DEBUG
static int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
#endif

void setup(){
#if SERIAL
    Serial.begin(57600);
    Serial.println("\n[etherNode]");
#endif

    
    if (ether.begin(sizeof Ethernet::buffer, mymac) == 0) 
      Serial.println( "Failed to access Ethernet controller");
    ether.staticSetup(myip, gwip);
  
#if SERIAL
    ether.printIp("IP: ", ether.myip);
#endif
}

char okHeader[] PROGMEM = 
    "HTTP/1.0 200 OK\r\n"
    "Content-Type: text/html\r\n"
    "Pragma: no-cache\r\n"
;

static void homePage(BufferFiller& buf, byte led) {
    
    buf.emit_p(PSTR("$F\r\n"
        "<meta http-equiv='refresh' content='$D'/>"
        "<title>Controlar un led desde internet</title>" 
        "<body><p>Estatus del led: $D</br>"
        "<a href='a'>Apagar</a> </br>"
        "<a href='e'>Encender</a><br></p>"), okHeader, 1, led);

    // Cronometrar el tiempo de ejecucion del programa
    long t = millis() / 1000;
    word h = t / 3600;
    byte m = (t / 60) % 60;
    byte s = t % 60;
    buf.emit_p(PSTR(
        "Tiempo corriendo $D$D:$D$D:$D$D</body>"), h/10, h%10, m/10, m%10, s/10, s%10);
#if DEBUG
    buf.emit_p(PSTR(" ($D bytes free)"), freeRam());
#endif
}

void loop(){
    word len = ether.packetReceive();
    word pos = ether.packetLoop(len);
    // check if valid tcp data is received
    if (pos) {
        bfill = ether.tcpOffset();
        char* data = (char *) Ethernet::buffer + pos;
        
        
#if SERIAL
        Serial.println(data);
#endif


        // Pagina sin solicitud
        if (strncmp("GET / ", data, 6) == 0) {
            homePage(bfill, ledstatus);
        }
        else if (strncmp("GET /a", data, 6) == 0) {
            // Recibimos el parametro a (apagar)
            homePage(bfill, ledstatus);//configPage(data, bfill);
            if(ledstatus == HIGH) {
              digitalWrite(2, LOW);
              ledstatus = LOW;
            } 
        }
        else if (strncmp("GET /e", data, 6) == 0) {
            // Recibimos el parametro e (encender)
            homePage(bfill, ledstatus);
            if(ledstatus == LOW) {
              digitalWrite(2, HIGH);
              ledstatus = HIGH;
            } 
        }
        else 
            bfill.emit_p(PSTR(
                "HTTP/1.0 401 Invalido\r\n"
                "Content-Type: text/html\r\n"
                "\r\n"
                "<h1>401 Unauthorized</h1>"));  
                
        ether.httpServerReply(bfill.position()); // send web page data
    }
}

28 comentarios:

  1. A ver si soy capaz de ponerlo en marcha, Gracias

    ResponderEliminar
  2. hola amigo soy el de mercadolibre me interesa saber en donde estas hubicado mi num de cel es 9933592130 mi correo es manuel_007ga@hotmail.com espero y te comuniques o me digas la direccion para que vea los productos saludos

    ResponderEliminar
  3. hola amigo.. primero que nada gracias por esta aportación que has echo..
    arme mi circuito y funciona bien hasta cierto puto.. conecto el circuito a la red y en el explorador busco la pagina y la uso,el led se enciendo y se apaga, hasta aquí todo bien (esto lo hago con el internet de mi trabajo).. después hablo a casa y pido que busquen la pagina pero no la encuentran, sale error después quedarse cargando un buen rato..
    Me podrías ayudar a solucionar esto?

    Tengo que configurar el módem de mi trabajo?

    ResponderEliminar
    Respuestas
    1. Hola como bien comentas el problema radica en la configuracion del modem, para esto es un poco dificil hacer un tutorial ya que no se podria abarcar en poco espacio toda la variedad de modems que existen, basicamente se trata de redirigir el puerto de servicio usado (puerto 80) desde la interface externa del modem hacia la ip donde esta conectado el arduino y dirigir las solicitudes desde internet hacia la ip "externa" donde esta conectado el modem, o sea si configuras tu arduino en la ip 192.168.1.188, las solicitudes desde internet no se hacen hacia esa ip si no a la ip que te muestra la interface externa del modem, cabe mencionar que las direcciones ip comprendidas en el rango 192.x.x.x no existen realmente en internet y solo se usan para conectividad en redes internas ya sea empresariales o domesticas, el mapeo de puertos lo que hace es redirigir las solicitudes desde internet hacia un equipo en la red interna, por ejemplo si tu ip "externa" fuera "200.23.106.37", en el modem vas a redirigir el trafico desde esa ip hacia la ip de tu red interna, es decir 192.168.1.118, entonces queda de la siguiente forma:

      Internet ---> 200.23.106.37 (puerto 80) <-modem-> 192.168.1.118 (puerto 80)

      Espero haber sido claro si no me puedes preguntar cualquier duda, para hacer el mapeo de puertos revisa el manual de tu modem, espero luego me invites a probar tu arduino conectado a internet, saludos de Tabasco, México

      Eliminar
    2. la verdad no entiendo mucho, se me hace complicado esto de las ip ya que soy nuevo en estos temas..

      pero... podría funcionar si conecto el arduino a un módem domestico y desde otro módem domestico intento ver la pagina?.... esto por que los modems domésticos normalmente no es necesario configuar la pc, es decir, no necesitan cambiar los valores para la dirección IP, Mascara de Subred, Puerta de Enlace Predeterminada y el DNS..

      o aun así habría que configurar el módem al cual esta conectado el arduino?.

      Eliminar
  4. hola amigo. e redirigido el puerto 80 hacia la dirección IP de arduino.. lo ise como indica en este videotutorial http://www.youtube.com/watch?feature=player_embedded&v=8H-Ea9Z_ppc.. intentado abrir la pagina que genera elarduino poniendo la IP de arduino en una pc con internet externo (diferente al modem donde esta conectadoarduino) pero no logro encontrar la pagina.. no se que es lo que tengo que configurar.

    ResponderEliminar
    Respuestas
    1. La ip que debes usar para acceder desde internet es la ip "externa" del modem, la puedes obtener en esta pagina http://www.cual-es-mi-ip.net/, a esta ip que obtengas es a la que tienes que acceder desde otra ubicacion para poder ver tu arduino

      Eliminar
  5. Saludos. Me encuentro trabajando con el mismo chip que señala y he logrado algunas cosas. Me encuentro tratando de realizar el ejemplo de Twitter que viene con la librería. La verdad no he logrado hacer que funcione. Me logro conectar a la red y el nslookup funciona a la maravilla pero el twitter no se realiza. Me pregunto si usted lo ha trabajado alguna ves y si le ha servido. Gracias!

    ResponderEliminar
  6. Muy buen tutorial, soy nuevo en esto de arduino ethernet, el programa me funciona de maravilla; pero como puedo agregar mas botones para controlar el encendido de otros led independientemente.
    y como puedo cambiar la apariencia de la pagina web.

    ResponderEliminar
    Respuestas
    1. Basicamente necesitas ingresar algunas lineas mas de html para ambas cosas, este fin de semana tratare de hacer una tercera parte del tutorial para mostraar como hacerlo, saludos

      Eliminar
  7. Hola. Realmente me has ayudado mucho con este tutorial, es excelente. Al igual que uno de los comentarios anteriores, me interesa mucho poder manejar varios botones para varios led independientes. Vi que comentaste que pondrías esa información en otro tutorial. Podrías indicarme la página para verlo por favor?. Mil gracias.

    ResponderEliminar
    Respuestas
    1. Hola no he tenido tiempo de hacer la siguiente parte del tutorial, para controlar mas de un boton podria implementarse algo de javascript, tambien he pensado agregar un lector de tarjeta sd para poder agregar iconos e imagenes a la pagina para mejorar su apariencia, la verdad es que he estado trabajando con la raspberry para comunicacion desde internet y alla todo es mas facil

      Eliminar
  8. Saludos, muy bueno el tutorial, ya logro enceder un led, pero el un modulo de rele no sirve, hay que cambiar alguna parte del codigo para que funcione.

    ResponderEliminar
    Respuestas
    1. hola, para activarse adecuadamente el rele necesita un transistor, no se si lo estas incluyendo en tu circuito

      Eliminar
    2. Este comentario ha sido eliminado por el autor.

      Eliminar
  9. Gracias! me ha servido de mucha ayuda!

    ResponderEliminar
  10. El codigo no me complia por esto:

    char okHeader[] PROGMEM =

    A que se debe ?

    ResponderEliminar
    Respuestas
    1. estimado como estas , delante de la linea coloca
      static const
      osea quedaria de la siguiente manera
      static const char okHeader[] PROGMEM =
      compila y con eso deberia funcionar
      Espero te aya sido de ayuda

      Eliminar
  11. El codigo no me complia por esto:

    char okHeader[] PROGMEM =

    A que se debe ?

    ResponderEliminar
    Respuestas
    1. Que version de ide de arduino y libreria estas usando?

      Eliminar
    2. Gracias por el tutorial amigo esta muy bien explicado pero tengo un problema al momento de compilar

      El codigo no me complia por esto:

      char okHeader[] PROGMEM =

      la version del IDE es la 1.6.7

      espero tu ayuda gracias.

      Eliminar
    3. me da ese mismo error, mi versión es la 1.6.9

      Eliminar
    4. También me da el mismo error...

      Eliminar
  12. buen tutorial, soy nuevo en esto de arduino ethernet, me pregunto como realizaron la pagina web que monitorea lo que esta conectado al arduino?

    ResponderEliminar
    Respuestas
    1. El html esta embebido en el codigo del arduino, es decir mediante las funciones que brinda la libreria se envian las cadenas de texto equivalentes a instrucciones de html, el browser o cliente solicita el recurso o sea la lectura del html mendiante la direccion ip y el puerto 80 (http)

      Eliminar
  13. Gracias por el tutorial amigo esta muy bien explicado pero tengo un problema al momento de compilar

    El codigo no me complia por esto:

    char okHeader[] PROGMEM =

    la version del IDE es la 1.6.7

    espero tu ayuda gracias.

    ResponderEliminar
  14. Gracias luigy7 pensaba en guardar mi Enc28j60 cuando lo compre no exixtia mucha informacion de como ponerlo a funcionar mas que algunos ejemplos en Ccs pero se me hizo muy complicado ahora con arduino es mas sencillo

    ResponderEliminar
  15. lo puse antes pero se lo comparto a todos , en linea char okHeader del programa falta adelante agregarle static const
    osea quedaria de la siguiente manera
    static const char okHeader[] PROGMEM =
    compilen y con eso deberia funcionar
    Espero les aya sido de ayuda

    ResponderEliminar