Este documento describe una aplicación IoT que utiliza un Arduino Uno, Ethernet Shield y display LCD para funcionar como un servidor web. El Arduino obtiene una dirección IP ya sea de forma dinámica a través de DHCP o de forma estática. El servidor web devuelve una página almacenada en una tarjeta microSD que muestra el nivel de un depósito simulado según la tensión de entrada analógica. El código analiza las solicitudes del cliente y devuelve la imagen adecuada de la tarjeta microSD.
1. IoT: Servidor web con Arduino Uno, EthernetShield y display
LCD 44780
Osvaldo Cantone
En este ejemplo se propone una aplicación para mostrar el uso combinado de un Arduino Uno
junto con un módulo EthernetShield y un display alfanumérico LCD H44780.
El Arduino Uno en conjunto con el shield Ethernetshield funcionan como un servidor web, que
escucha el puerto TCP 80 y devuelve una página web e imágenes alojadas en la memoria
microSD que se encuentra en el alojaiento incluído en el módulo EthernetShield. En dicha
memoria se encuentran un archivo “.htm” (extensión equivalente a html, solo que es compatible
con un sistema de ocho bits, como es Arduino) y varias imágenes ( jpg ó png ) que se encuentran
en la memoria SD del EthernetShield.. El contenido de estos archivos es enviado como respuesta
ante las peticiones hechas por un navegador cliente.
El módulo EthernetShield permite la conexión del conjunto a una red IEEE 802.3 (Ethernet). En el
código se utilizan las bibliotecas Ethernet.h; SD.h y LiquidCrystal.h, que permiten manejar el
módulo EthernetShield, la memoria microSD y el display respectivamente.
Al iniciar el microcontrolador busca obtener parámetros de red desde un servidor DHCP, tales
como dirección IP, máscara de subred y puerta de enlace. En caso de no encontrar un servidor
DHCP o de no obtener de forma satisfactoria dichos parámetros, establece entonces una IP fija
cuyo valor es 192.168.0.200.
La página web muestra texto e imágenes que muestran en forma dinámica el valor de la tensión
aplidaca a la entrada analógica A0. De hecho, se utilizan diferentes imágenes para graficar el nivel
de un depósito llenándose con un líquido.
El diagrama de la figura muestra la conexión:
2. El sketch del Arduino es el siguiente, donde también se incluyen las explicaciones del
funcionamiento de las sentencias principales de esta aplicación:
/**--------------------------------------------------------------
Programa: Web server SD y LCD.
Descripción: Aplicación de Arduino con EthernetShield. Servidor web.
Envía una página web que contiene imágenes.
Obtiene parámetros de red por DHCP, de no obtenerse
satisfactoriamente, se asigna una IP fija.
Muestra la IP configurada en el display LCD.
Arduino Uno, Arduino Ethernet Shield y módulo LCD 44780
20x4.
Puede trabajar con otros módulos Arduino y ethernet
compatibles.
Micro SD de 2 GigaBytes formateada en FAT16
Los archivos .htm, .jpg y .png se encuentran
en la tarjeta microSD
Fecha: 5 de Julio de 2016
Modificado: 5 de Julio de 2016
Autor: Osvaldo Cantone
* LCD pin#01 Vss 0V
* LCD pin#02 Vdd +5V
* LCD pin#03 Ajuste de brillo caracter. 0V para máximo brillo.
* LCD pin#04 RS al pin digital del Arduino #9
* LCD pin#05 R/!W pin conectado a 0V.
* LCD pin#06 Enable to Arduino digital pin 11
* LCD pin#07 D0 n.c.
* LCD pin#08 D1 n.c.
* LCD pin#09 D2 n.c.
* LCD pin#10 D3 n.c.
* LCD pin#11 D4 pin to digital pin 5
* LCD pin#12 D5 pin to digital pin 4
* LCD pin#13 D6 pin to digital pin 3
* LCD pin#14 D7 pin to digital pin 2
LiquidCrystal lcd(RS, E, D4, D5, D6, D7); // inicializa la biblioteca
con los números de pin.
3. LiquidCrystal lcd(9, 8, 5, 4, 3, 2); //
--------------------------------------------------------------**/
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
#include <LiquidCrystal.h>
#define REQ_BUF_SZ 20 // tamaño del buffer para alojar peticiones HTTP.
File webFile;
char HTTP_req[REQ_BUF_SZ] = {0}; // almacena peticiones HTTP como
//cadenas terminadas en NULL.
char req_index = 0; // index dentro de del buffer HTTP_req[]
int entrada_an; //Entrada analógica.
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 200); // dirección IP, debe modificarse
//dependiendo de la red.
EthernetServer server(80); // crea un server en el port 80
LiquidCrystal lcd(9, 8, 5, 4, 3, 2); /*inicializa la biblioteca con los
números de pin.*/
void setup()
{
// deshabilita Ethernet chip
pinMode(10, OUTPUT);
digitalWrite(10, HIGH);
Serial.begin(9600); // Usa puerto serie para debugging.
lcd.begin(20, 4); //setea número de columnas y filas del LCD.
lcd.setCursor(0, 0); //Coloca el cursor en (col 0 linea 1).
lcd.print("Arduino web server"); // Muestra un mensage en el LCD.
delay(1000);
lcd.setCursor(0, 1); //Coloca el cursor en fila 0, col 1.
lcd.print("Buscando Server DHCP");
delay(1000);
/* inicializa trajeta de memoria SD */
Serial.println("Inicializando tarjeta SD.");
if (!SD.begin(4))
{
Serial.println("ERROR - Fallo inicializando SD.");
4. return; // inicio fallido
}
else{
Serial.println("HECHO - tarjeta SD inicializada.");
}
/* Busca index.htm */
if (!SD.exists("index.htm"))
{
Serial.println("ERROR – No se encuentra el archivo index.html");
return; // no encuentra archivo index.htm
}
else{
Serial.println("Se encontró satisfactoriamente index.htm ");
}
/* Si Ethernet.begin(mac) devuelve 0 significa que no pudo obtenerse
una direccion IP por DHCP, entonces fijara la IP contenida en el array
“ip”. */
if (Ethernet.begin(mac) == 0)
{
Serial.println("Fallo obteniendo IP por DHCP. Se usará la IP fija
192.168.0.20");
lcd.setCursor(0, 1);
lcd.print("IP 192.168.0.200");
Ethernet.begin(mac, ip); // Inicializa con mac e ip fija
}
/* Si obtuvo parámetros por DHCP entonces los muestra en el display */
else{
Serial.print("IP obtenida por dhcp: ");
lcd.setCursor(0, 1);
lcd.print("IP: ");
for (byte thisByte = 0; thisByte < 4; thisByte++)
{ // muestra el valor de cada byte de la dirección IP:
Serial.print(Ethernet.localIP()[thisByte], DEC);
lcd.print(Ethernet.localIP()[thisByte], DEC);
if(punto<3) {
lcd.print(".");
Serial.print(".");
}
}
5. lcd.print(" ");
Serial.println();
}
server.begin(); //comienza a escuchar peticiones de clientes.
} //Fin de setup()
void loop()
{
entrada_an = analogRead(0); //Toma el valor analógico en A0.
EthernetClient cliente = server.available(); // escucha clientes.
if (cliente) { //hay peticiones?
boolean lineaenblanco = true;
while (cliente.connected()) {
if (cliente.available()) { // datos enviados por el cliente disponibles
//para leerlos.
char c = cliente.read();
/* Lee 1 byte (petición HTTP del cliente) y bufferea en el array
HTTP_req[]. Deja al último elemento como 0 para terminar el string */
if (req_index < (REQ_BUF_SZ - 1)) {
HTTP_req[req_index] = c; // guarda cada char de la petición HTTP.
req_index++;
}
// Envia la petición HTTP por el port serial para debbuging.
Serial.print(c);
/* La última linea de la petición del cliente es una línea en blanco y
termina con “n”. Responde al cliente solo luego de recibir la última
linea. */
if (c == 'n' && lineaenblanco) {
/* Abre el archivo de la página web requerido.
La condición del bloque if permite direccionar el archivo index.htm ya
sea que se reciba su nombre de forma explícita o por defecto si no se
recibe ninguna referencia a un archivo específico.
*/
if (StrContains(HTTP_req, "GET /index.htm")||
StrContains(HTTP_req, "GET / ") ) {
6. cliente.println("HTTP/1.1 200 OK");
cliente.println("Content-Type: text/html");
cliente.println("Connnection: close");
cliente.println("Refresh: 3"); // refresca la pág. cada 3 seg.
cliente.println();
webFile = SD.open("index.htm"); // abre la página web.
}
/*
Aquí vemos cómo trabajar con las imágenes inculídas en la memoria
microSD.
El código HTML solicita la incusión de una imágen llamada “nivel.png”,
eso es lo que se puede ver que llega en la petición HTTP_req.
Ahora el servidor enviará como respuesta a esa petición una imagen
elegida en este caso en función del valor de una entrada analógica.
*/
else if (StrContains(HTTP_req, "GET /nivel.png")) {
if(entrada_an < 50){
webFile = SD.open("nivel01.png");
}
if(entrada_an >= 050 && entrada_an < 100){
webFile = SD.open("nivel02.png");
}
if(entrada_an >= 100 && entrada_an < 150) {
webFile = SD.open("nivel03.png");
}
if(entrada_an >= 150 && entrada_an < 200) {
webFile = SD.open("nivel04.png");
}
if(entrada_an >= 200) {
webFile = SD.open("nivel05.png");
}
/* Envía ahora el archivo que contiene la imagen png seleccionada*/
if (webFile) {
cliente.println("HTTP/1.1 200 OK");
cliente.println();
}
}
if (webFile) {
while(webFile.available()) {
cliente.write(webFile.read()); // Envía la pag. Web al cliente.
7. }
webFile.close();
}
//resetea el index del buffer.
req_index = 0;
StrClear(HTTP_req, REQ_BUF_SZ);
break;
}
// cada linea de texto recibida desde el cliente termina con rn
if (c == 'n') {
// el último char en la linea del texto recibido
// comienza una nueva linea
lineaenblanco = true;
}
else if (c != 'r') {
// un carácter se recibió desde el cliente.
lineaenblanco = false;
}
} // end if (cliente.available())
} // end while (cliente.connected())
delay(1); // tiempo para que el navegador reciba los datos.
cliente.stop(); // cierra la conexión.
} // end if (cliente)
}
// Limpia el string inicializando cada elemento con cero.
void StrClear(char *str, char length)
{
for (int i = 0; i < length; i++) {
str[i] = 0;
}
}
// Busca el string sfind dentro del string str
// devuelve 1 si lo encuentra.
// devuelve 0 si no lo encuentra.
char StrContains(char *str, char *sfind)
{
char found = 0;
char index = 0;
char len;
8. len = strlen(str);
if (strlen(sfind) > len) {
return 0;
}
while (index < len) {
if (str[index] == sfind[found]) {
found++;
if (strlen(sfind) == found) {
return 1;
}
}
else {
found = 0;
}
index++;
}
return 0;
}
La página web aparece entonces como se ve, mostrando el nivel del recipiente subiendo o
bajando, en función del valor de tensión de la entrada analógica del Arduino A0.
9. Conclusión:
En este ejemplo ponemos a trabajar juntos un módulo Arduino, junto con un shield que permite
conecciones Ethernet, una memoria microSD desde dónde se leen datos y un display
alfanumárico LCD de 4 x 20 caracteres.
Desde el punto de vista del código, vimos la forma de configurar los parámetros de red, ya sea de
formá dinámica desde u servidor como de forma estática. Así mismo identificamos el contenido de
una petición de un cliente y en función de el valor de tensión de una entrada analógica,
seleccionamos el archivo de imagen a utilizar, leyendolo desde una memoria microSD.