Conexión Ethernet TCP con Arduino

publicado en: Portada | 25

Desde el punto de vista del software, establecer una conexión Ethernet con Arduino es muy sencillo. Para hacerlo se utiliza la librería Ethernet. Esta librería está diseñada para una Ethernet Shield que está basada en el integrado W5100, pero existen otras placas o módulos diferentes y/o que utilizan otros integrados, como el ENC28J60. Para simplificar su uso y aumentar la compatibilidad, otras librerías utilizan (casi) la misma API que la librería Ethernet, sólo habrá que sustituir la librería alternativa por la original o incluirla (cuando el nombre sea diferente) en su lugar aunque en el código se usen las mismas (o muy parecidas) funciones. En mi caso, utilizo la librería UIPEthernet de Norbert Truchsess siguiendo el mismo proceso que voy a describir en este texto.

módulo ENC28J60 para usar con la librería UIPEthernet

1. Definir la conexión Ethernet

Tanto si se va a adoptar el papel de cliente como el de servidor, en primer lugar hay que definir la conexión con la función begin() a la que se le puede pasar como parámetro sólo la dirección MAC y esperar que un servidor DHCP en la red le asigne una dirección IP y el resto de la configuración o también es posible indicar (opcionalmente) más parámetros hasta definir la configuración completa:

  1. Dirección MAC (la que ya se ha citado)
  2. Dirección IP del shield o módulo
  3. Dirección IP del servidor DNS (sólo un servidor)
  4. Dirección IP de la puerta de enlace
  5. Máscara de red

Es recomendable indicar todos los parámetros, salvo que su deducción sea la habitual, para evitar que la configuración no sea correcto (por ejemplo, que la pasarela no sea la primera dirección de la red)

De lo anterior parece que queda claro que hay que usar bastantes veces datos que representen direcciones IP, por eso la librería incluye la clase IPAddress de la que instanciar objetos dirección IP. Los parámetros que la definen son los cuatro bytes de una dirección IPV4

La dirección MAC se define para esta librería como una matriz de 6 bytes. La dirección MAC es (se supone que es) un identificador único en el que los primeros bytes indican el fabricante y el modelo y los últimos al dispositivo en concreto. El integrado ENC28J60 no incluye una dirección MAC salvo que se opte por comprar además un integrado de dirección MAC de Microchip (o todo un bloque OUI de direcciones al IEEE si la tirada de dispositivos es lo bastante grande como para que merezca la pena). Cuando no se dispone de una dirección MAC se puede inventar una cuidando que no entre en conflicto con otras en la red en la que se encuentre el dispositivo.

Si la configuración se realiza con un servidor DHCP en lugar de «a mano», la función localIP() es útil para consultar la dirección que el servidor le ha asignado al módulo. Para renovar la dirección asignada (si el tiempo correspondiente hubiera expirado) la librería Ethernet proporciona la función maintain() que además informará devolviendo un código que corresponde con el estado de la renovación:

  1. La operación no ha tenido ningún efecto
  2. Error al renovar (renew) la dirección IP
    No se ha podido prolongar el uso de la dirección IP asignada en el mismo servidor
  3. Dirección IP renovada correctamente
  4. Error al reasignar (rebind) la dirección IP
    No se ha podido prolongar el uso de la dirección IP asignada en ningún servidor
  5. Dirección IP reasignada correctamente

Con la información vista hasta ahora ya se puede escribir un ejemplo de cómo se iniciaría una conexión Ethernet configurando la dirección IP por medio de un servidor DHCP en la red. En el siguiente código de ejemplo se trata de renovar la dirección IP cada cierto periodo de tiempo y se informa del resultado.

En el ejemplo de abajo se asigna la dirección IP y el resto de la configuración manualmente utilizando objetos IPAddress para que resulte más cómodo leerlo y (en caso de código más complejo) evitar los errores que se podrían producir si se escribiera (mal) la dirección en cada uso.

2. Iniciar la conexión en modo cliente o servidor

Al iniciar una conexión en modo servidor, es el sistema microcontrolado que se está desarrollando el que queda a la escucha de las peticiones de otros sistemas. Para iniciar la conexión como servidor se utiliza EthernetServer() y se indica como parámetro el puerto en el que el servidor escuchará. EthernetServer() es el constructor de la clase Server, que soporta todas las operaciones Ethernet como servidor. Aunque lo más ortodoxo es realizar una llamada al constructor EthernetServer(), no es raro encontrar algunos ejemplos que usan directamente la clase Server o librerías alternativas para conexión Ethernet que eligen usar ese sistema de instanciado.

La conexuión como cliente es la que realiza las peticiones al sistema servidor que es el que las espera y las contesta según corresponda. Para inicar una conexión como cliente se utiliza EthernetClient() que es el constructor de la clase Client origen de todas las operaciones Ethernet como cliente.

A diferencia de lo que ocurre con el modo servidor, que se supone funcionando desde que se instancia la clase (aunque responderá a los clientes sólo si lo está realmente), se debe verificar que la conexión cliente está preparada antes de utilizarla. El objeto cliente que se crea al iniciar la conexión puede consultarse para verificar si está disponible. Por ejemplo, las operaciones de consulta pueden incluirse en una estructura if(EthernetClient) para ejecutarlas sólo cuando la conexión cliente esté disponible.

3. Establecer una conexión como cliente

Como se ha dicho, una vez creada la conexión, es el cliente el que toma la iniciativa de realizar las consultas. El servidor estará esperando esa iniciativa y responderá como proceda. Es, por tanto, el cliente el que conecta al servidor, para hacerlo se utiliza connect() indicando como parámetros el servidor (la dirección IP o la URL) y el puerto en el que escucha.

Según el resultado de la operación, la función devolverá los valores

  1. (SUCCESS) Conexión establecida correctamente
  2. Estableciendo la conexión
  3. (TIMED_OUT) Ha pasado el tiempo de espera sin que se establezca la conexión
  4. (INVALID_SERVER) No se ha encontrado el servidor o no responde correctamente
  5. (TRUNCATED) La conexión se ha cortado antes de establecerse completamente
  6. (INVALID_RESPONSE) La respuesta del servidor es incorrecta

Antes de empezar a realizar consultas es necesario verificar que la conexión está operativa con la función connected() que devolverá true si ya está disponible o false en caso contrario.

El ejemplo de abajo ilustra la conexión como cliente verificando cada 10 segundos si existe conexión con el servidor (no pretende ser nada productivo, sólo mostrar la sintaxis de las funciones) algo que, por cierto, no gustaría mucho a un servidor web en producción.

4. Enviar datos

Igual que otras clases más conocidas, como ocurre con Serial, y con un uso equiparable, las clases Client y Server disponen de las funciones

  • write(dato) o write(buffer,longitud)

    Envía información usando el objeto cliente o servidor desde el que se invoque. El parámetro «dato» es un único byte o char mientras que «buffer» es una matriz de byte o char de la que se envía una cantidad igual a «longitud» Esta función es la que se utiliza para las operaciones binarias, frente a las dos siguientes que suelen reservarse para enviar texto.

  • print(dato,base)

    Envía como cliente o servidor (según la clase desde la que se use) la información correspondiente a «dato» como texto. Si la información no está expresada como texto (por ejemplo es un número entero) puede utilizarse el parámetro opcional «base» con el que elegir la de la conversión que podrá ser una de las constantes BIN, OCT, DEC o HEX que indican, respectivamente las bases correspondientes a binario (base 2), octal (base 8), decimal (base 10) y hexadecimal (base 16)

  • println(dato,base)

    El funcionamiento es idéntico a la anterior excepto por enviar, después de la información indicada expresamente por el parámetro «dato», un retorno de carro (el código 13 que se puede representar como \r) y un final de línea (el código 10, que se puede representar por \n) Frecuentemente se hace referencia a estos códigos, respectivamente, por las siglas CR (Carriage Return) y LF (Line Feed)

Las tres funciones anteriores devuelven el número de bytes que se han enviado, como ocurre también con las funciones equivalentes de la clase Serial; como se dijo arriba, el funcionamiento es equiparable.

5. Recibir datos

Igual que en el caso de las operaciones de envío de datos, las de recepción son equiparables a las de la ampliamente usada Serial. El protocolo de recepción también es similar: verificar si hay (suficientes) datos disponibles (available) y en tal caso leerlos


  • available()

    Devuelve el número de bytes que hay disponibles para ser leídos. Esta función está presente tanto en la clases Client como Server; en el primer caso informa del número de bytes que ha enviado el servidor en respuesta a una petición y que está disponible para que el cliente la lea (read), y en el segundo caso el (objeto) cliente que ha realizado una operación o false en caso de que no haya ninguno.

  • read()

    Sirve para leer la información que se ha recibido. Esta función sólo está disponible en la clase Client. Si la aplicación que se está desarollando cumple con el papel de servidor, para leer la información que ha llegado debe instanciarse un objeto cliente con la respuesta de la función available() comentada en el anterior apartado.

El siguiente ejemplo es un «servidor de mayúsculas» que escucha en el puerto 2000 y responde a las peticiones con lo que se haya enviado pasado a mayúsculas cuando sea posible. Puede probarse, por ejemplo con PuTTY o simplemente con telnet 2000 Ciertamente no es algo muy práctico, su finalidad sólo es mostrar cómo obtener en el servidor los datos enviados al mismo por un cliente.

6. Finalizar la conexión

Mientras que lo habitual es que una aplicación servidor funcione indefinidamente, las conexiones cliente se establece, realizan conexiones y terminan, lo que permite recuperar recursos y emplearlos en otras conexiones o dedicarlos a otros usos del programa. La función stop() de la clase Client se utiliza para terminar una conexión cliente y liberar los recursos que esté utilizando.

De cara al servidor, que el cliente termine la conexión cuando se ha enviado o recibido la información objeto de la consulta también le permite liberar recursos para destinarlos a otras conexiones o distintos fines. En definitiva, aunque parece algo menor, es conveniente terminar la conexión al terminar las operaciones del cliente.

Otra buena práctica al terminar una conexión cliente es vaciar el que utiliza la clase. Para hacerlo se dispone de la función flush() a la debería llamarse después de terminar la conexión cliente con stop()

Ejemplo de consulta HTTP GET

Para aclarar mejor todo lo anterior a continuación se incluye un ejemplo más completo de peticiones TCP usando el peticiones GET usando el protocolo HTTP. En el ejemplo se envían los los valores obtenidos por unos sensores analógicos conectados a una placa Arduino a un servidor web que los almacena en una base de datos.

Víctor Ventura

Desarrollando aplicaciones para la web conocí el potencial de internet de las cosas, encontré la excusa perfecta para satisfacer la inquietud de aprender electrónica que había tenido desde siempre. Ahora puedo darme el gusto de programar las cosas que yo mismo diseño y fabrico.

Más entradas - Página web

Sígueme:
TwitterLinkedIn

Seguir Víctor Ventura:

Programador multimedia y web + IoT. Mejor con software libre.

Desarrollando aplicaciones para la web conocí el potencial de internet de las cosas, encontré la excusa perfecta para satisfacer la inquietud de aprender electrónica que había tenido desde siempre. Ahora puedo darme el gusto de programar las cosas que yo mismo diseño y fabrico.

25 Respuestas

    • Daniel

      Muy buen trabajo, te felicito. Gracias por la información, saludos desde Argentina.

  1. Gregorio

    Enhorabuena Víctor, por tu claridad en la exposición. Muy buena, gracias.

  2. Rodrigo Andrés

    Gracias victor por tu aporte, no sabes cuanto necesitaba de esta información.

  3. Galileo

    Muy bueno el informe. Estoy comenzando a utilizar esta librería. Tendrás un ejemplo de cómo leer desde el servidor en Arduino por ejemplo la acción de presionar un botón en el cliente (navegador web /HTML)? (por ejemplo encender un led conectado a la arduino presionando un botón de la página html que aparezca al ingresa en el navegador la IP de la Arduino). Gracias y éxitos!

  4. Arnau

    Buenas tardes,
    Estoy haciendo un sistema de apertura de puertas con tarjetas NFC. Lo que estoy intentando hacer es que con el ID de cada NFC, lo envíe a un PHP, éste compruebe en un base de datos MySQL si puede acceder o no. Y retorne un 1 o 0 que lo reciba el Arduino. ¿Cómo puedo hacer esto?
    Muchas gracias por adelantado.
    Saludos

    • Víctor Ventura

      Hola, Arnau.

      El proceso que describes sería el que yo seguiría: mandar el id de puerta y llave al servidor, consultar en la base de datos si el par está autorizado y responder consecuentemente (sí/no)

      Seguramente ya conoces la librería para hacer consultas HTTP con un módulo WiFi ESP8266 y Arduino que resuelve la parte de Arduino ¿Qué es lo que te da problemas, el acceso con PHP a la base de datos?

      ¿Has programado ya algo que no te funciona (conocerlo nos ayudaría a ayudarte) o no sabes cómo empezar?

      Saludos.

  5. Nacho

    Muchas gracias Víctor por compartir. Quería hacerte una consulta. Tengo un DUE con ethershield y un nano con enc28j60. Ambos funcionan por separado ok como server web. Al nano le conecto un sensor de temperatura y el Due quiero que lo muestre en una pantalla. El DUE es el que espera como server web y el nano solo lee temp. Como hago para pasar la temperatura al DUE por top en lugar de por serial.

    Muchas gracias

    • Víctor Ventura

      Hola, Nacho.

      Si la placa Arduino Due ya está haciendo de servidor web, lo único que tienes que hacer es preparar en ella una página que reciba los datos que le mande la placa Arduino Nano tal como se explica en el ejemplo de consulta HTTP GET.

      Como en tu comentario hablas de pasar por TCP (espero que lo de «top» sea un error o no sé a qué te refieres) a lo mejor lo de «servidor web» es sólo una forma de hablar y te refieres a «servidor» genérico. En tal caso, tendrías que establecer una conexión como cliente con connect desde la placa Arduino Nano hasta la placa Arduino Due (que supongo que ya espera como servidor) enviar la información de la temperatura con write o con print y recibirlos y procesarlos (presentarlos en la pantalla) en la placa Arduino Due con read cuando available detecte que están disponibles.

      Todo eso está en el texto, así que a lo peor no entendido bien tu pregunta o el problema que se te plantea. No tengas problema en repreguntar con más información para tratar de ayudarte mejor si no es esto lo que preguntas.

      Saludos.

  6. Rafael

    Hola, ante todo gracias por iluminar en esta gran oscuridad. Al fin alguien habla el idioma de los iniciados.
    Fe de errata : En el apartado 3 linea 32-> falta punto y coma.
    En linea 15-> EthernetServer cliente=EthernetClient(); es correcto?? no habis dicho que el constructor del cliente es EthrnetClient.
    Critica constructiva :

    • Víctor Ventura

      Hola, Rafael.

      Gracias por avisar del error. Ya lo he corregido en el ejemplo para no confundir a los visitantes 🙂
      En realidad es la clase (el tipo) lo que está mal, EthernetClient es el constructor pero de la clase EthernetClient no EthernetServer (como estaba inicialmente escrito en el ejemplo) Para colmo de lío (en las versiones antiguas) funcionaba por razones de herencia 🙁 Ahora está mucho más claro. Como siempre, lo mejor de la casa, los clientes.

      ¡Gracias por tu aportación!

  7. Rafael

    Hola de nuevo, me dejé la crítica constructiva.

    Sería interesante, hacer mención de como se conecta el Hardware. Esta bien expresado el software pero no me quedó claro donde podía hacer la prueba, si directamente desde mi portatil o conectado al router. Al final pude realizer Ping conéxito desde la linea de commando MSDos conectando el modulo Ethernet y mi portatil al Router .

    Felicidades por la página Web. Es sencilla, clara y plenamente interesante.

    Muchas Gracias!!

    • Víctor Ventura

      Hola, Rafael.

      Puedes encontrar información sobre cómo conectar el módulo Ethernet ENC28J60 en mi artículo Conexión Ethernet con el integrado ENC28J60 de Microchip. He preferido separarlo en dos partes porque tiene lectores un poco diferentes y para no hacerlo demasiado largo (y aburrido)

      Por supuesto, si no queda lo bastante claro, no tengas problema en volver a consultar; seguro que podemos explicarlo mejor.

      ¡Otra vez gracias por tus sugerencias! ¡Hasta pronto!

  8. Miky

    Hola Victor,

    estoy en una situación un tanto rara, estoy utilizando un M-DUino 58 para fines industriales, el cual lleva un enc28J60 i actua como una genuino mega 2560

    El tema són las librerias, he incluido la libreria tal y como comentas en el post pero el IDE de arduino me dice que tal librería queda invalidada he provado la Ethernet-h a ver si sonaba la flauta pero tampoco, puesto que usa otro shield de ethernet, alguna sugerencia?

    La ide aes mandar a través de un programa de PC tramas UDP que recogerea el Arduino via Ethernet, luego recogerá unos bytes de dicha trama y los utilizará en sus funciones.

    • Víctor Ventura

      Hola, Miky.

      No conozco la placa que estás usando pero, si he entendido bien, debería «aparentar» ser un Arduino Mega 2560. Creo recordar que la librería funcionaba en Mega, pero no la he probado con el nuevo IDE. En cuanto tenga un momento le echo un vistazo por si algo de la nueva versión no fuera compatible. Si actualizo código que dependa de la librería también modificaré este artículo y te avisaré.

      Un saludo.

      • Miky

        El tema es que he conseguido hacer correr bien la libreria pero tengo un problema con el tema de configuraciones, tengo un programa que manda UDP frames al arduino, entonces ahi hay para modificar la ip y puerto del que envia y tambien para configurar la IP y puerto del que recibe. me gustaría entender que papel juegan cada uno.

        mil Gracias Victor

  9. Jorge leonardo

    hola victor,
    primero, felicitarte al fin alguien que da información clara del funcionamiento de esta shild

    segundo estoy tratando de enviar un parametro que recojo de una alarma, y lo que quiero es enviarla a un servidor, solo que no por GET si no por socked, entiendo muy bien cuando explicas la diferencia entre servidor y cliente pero me enredo al querer enviar el dato al servidor, ya que tu ejemplo lee un dato del servidor con lo de las letras mayusculas, estoy usando la shild enc28j60 y un arduino uno me facilitarías mucho las cosas si me explicas como enviar el dato

    • Víctor Ventura

      Hola, Jorge.

      La forma de enviar un dato a un servidor es la que puedes ver en el apartado 4: usando print o usando write según el dato sea texto o no. Esa función es independiente de que se utilice el protocolo HTTP (GET) o cualquier otro.

      Como no utilizas HTTP, en la conexión (ver apartado 3) debes indicar el puerto en el que el servidor escucha (segundo parámetro) después de la dirección IP que tiene asignada (primer parámetro).

      El protocolo que utilice el servidor (no nos dices si es algo genérico o algo que has programado específicamente para este uso) será el que determine qué «forma» tienen los datos que envías (si se separan por comas, si se encierran entre paréntesis, si terminan en fin de línea…)

      Espero que esto te sea útil. Gracias por participar en el blog.

      • Jorge leonardo

        hola victor
        tengo una duda en el apartado 3 la parte de codigo que tienes en el void loop() que funcion cumple como tal es necesaria para realizar la conexcion como cliente?o simplemente puedo enviar el dato aviertamente con las funciones que mencionas ?

        • Víctor Ventura

          Hola, Jorge.

          La parte de código de ejemplo que hay en loop() no es imprescindible para que el programa funcione. El primer bloque es para indicar si ha conectado y el segundo para volver a conectar si se pierde la conexión.

          Normalmente será en loop() donde se vayan enviando los datos con write() y/o print().

          Saludos.

  10. IVO

    Hola,
    Estoy como tú desarrollando mis propias cosas de IoT. Gracias por tu escrito me ayudara a enteder mejor como fucniona mi ENC28J60 porque ahora no me esta registrando los datos en la base de datos en XAMPP. tienes un escrito similar para ESP8266? Saludos y Muchas gracias.

  11. Francisco

    Hola, buena tarde….
    Disculpa, me podrías dar un ejemplo de como establecer comunicación entre el Arduino Uno con Ethernet Shiel W5100 y una placa RaspBerry Pi 3, necesito hacer que reciban y envíen datos entre ellos vía Ethernet sin usar un servidor web, eh intentado varias cosas pero asta hoy ninguna me a funcionado, te preguntaras por que no los conecto directamente por el puerto serie y es por la distancia que hay entre ellos, espero y me puedas ayudar, saludos y gracias…..

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *