Estaremos de acuerdo que una pantallita a nuestra Arduino nos va a independizar del ordenador con su puerto serie y así poder instalar nuestro ArduinoSystem donde nos plazca.
Existen varias opciones a tener en cuenta, las pantallas de LCD Nokia 5110, están muy estandarizadas por la red, te permite diseñar tus propios gráficos y cuenta con sus propias librerías para Arduino.
A mi personalmente no me satisface demasiado para el proyecto que tenemos en marcha, en primer lugar por que se come 5 pines digitales (ni se han molestado en incorporar un driver) además según el modelo tienes que incluir elementos electrónicos como resistencias con su debida placa de circuito y todo esto para mostrar la información en una pantalla de 1.5"
El LCD 16X2 o 20X4 lo encontramos con su debido driver y nos imprime unas letras bien grandes y hermosas.
Así que vamos a emplear este modelo para nuestras prácticas, y en esta práctica nos haremos un reloj que mostrará la hora, minuto, segundo, dia, mes y año. Y ya que hoy estoy un tanto criticón, tampoco vamos a emplear los RTC que tan extendidos están para nuestra Arduino, principalmente por que en la práctica son una patata, se atrasan, cuelgan el sistema y no puedes ponerlo al día. Esto se debe a que trabajan con interrupciones, algo muy elegante aunque un tanto incompatible con el factor tiempo.
Vamos a crear un reloj propio, independiente y muy preciso.
Para ello nos aprovecharemos de la función millis(), bien es cierto que vamos a tardar un tiempo en hacerlo "muy preciso" pero cuando lo consigamos seremos plenamente felices.
A vueltas con el LCD hay que indicar que tiene algunas curiosidades, como memoria interna o la posibilidad de crear tus propios símbolos, emplean el protocolo I2C (los que incluyen el driver) así que poseerá una dirección exclusiva que debemos conocer.
Para el manejo de estas LCD's disponemos de varias librerías, no exentas de polémicas ya que te pueden dar mas de un dolor de cabeza dependiendo de la compilación que estés empleando.
Vamos a ver el conexionado, que es de lo más sencillo (para los modelos con driver)
VCC a 5 voltios y los demás en el pin del mismo nombre de la Arduino.
Bueno como decía, antes de nada tenemos que conocer que dirección está empleando nuestra LCD, por que de lo contrario no veremos nada de nada.
Para esto alguien se molestó en su momento en crear un código que te indica en que dirección se ha detectado la LCD conectada:
Fíjense que debemos incluir la librería "wire", ésta es necesaria para la gestión de direcciones del protocolo I2C
#include <Wire.h> void setup(){ Wire.begin(); Serial.begin(9600); Serial.println("\nI2C Scanner"); } void loop(){ byte error, address; int nDevices; Serial.println("Scanning..."); nDevices = 0; for(address = 1; address < 127; address++ ) { // The i2c_scanner uses the return value of // the Write.endTransmisstion to see if // a device did acknowledge to the address. Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0){ Serial.print("I2C device found at address 0x"); if (address<16) Serial.print("0"); Serial.print(address,HEX); Serial.println(" !"); nDevices++; } else if (error==4){ Serial.print("Unknow error at address 0x"); if (address<16) Serial.print("0"); Serial.println(address,HEX); } } if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); delay(5000); // wait 5 seconds for next scan }
El código a pesar de lo viciado que está, no solo compila si no que te imprime la dirección correcta de todos los LCD que he comprado...😖
La salida por Serial es la siguiente:
Así que sin vacilaciones copiamos mentalmente o con Ctrl+C el "0x27", ya está, a partir de aquí todo es 'cuestabajo'...(no se como suena esta buena gente ahora mismo, pero un día lo comprobaré )
A continuación os dejo un código básico, comentado a rabiar por que siempre hay alguien que le viene bien:
// LCD_I2C_Basico Compilacion Arduino 1.8.1, 01/03/2019 #include <LiquidCrystal_I2C.h> // Cargamos la libreria LiquidCrystal_I2C LiquidCrystal_I2C miLCD(0x27,16,2);//Declaramos nuestra variable "miLCD" y le pasamos los parametros (direccion I2C, nºColumnas, nºfilas) void setup(){ miLCD.init();// Inicializa el LCD miLCD.backlight();// Enciende la iluminacion del LCD miLCD.print("Hello world"); // Imprime (por defecto en el primer espacio disponible) miLCD.setCursor(0,1); // Situa el cursor en la primera posicion de la segunda linea miLCD.print("funcionando");// Imprime el texto indicado en el parametro delay(1000);// espera un segundo miLCD.clear();// Limpia el LCD } void loop(){ miLCD.setCursor(0,0);// Situa el cursor en la primera posicion de la primera fila miLCD.print("Primera Linea...");// Imprime el texto indicado en el parametro miLCD.setCursor(0,1);// Situa el cursor en la primera posicion de la segunda fila miLCD.print("Segunda Linea...");// Imprime el texto indicado en el parametro }
El posterior código os servirá para comprobar las peculiaridades a la hora de imprimir símbolos sobre la LCD, comprobaréis que existen disparidades frente a la impresión Serial:
// LCD_I2C_Simbolos Especiales, Compilacion Arduino 1.8.1, 01/03/2019 // Para hacer uso del metodo printByte debemos declarar lo siguiente: #if defined(ARDUINO) && ARDUINO >= 100 #define printByte(args) write(args); #else #define printByte(args) print(args,BYTE); #endif // fin printByte #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,16,2); void setup() { Serial.begin(9600); lcd.init(); lcd.backlight(); Serial.print(char(43)); Serial.print(char(126)); Serial.print(char(127)); Serial.print(char(60)); Serial.print(char(62)); lcd.print(char(43));lcd.print(" "); lcd.printByte(43);delay(1000);// El uso de printByte es posible por la declaracion en lineas 3 a 7 lcd.print(char(126));lcd.print(" ");lcd.printByte(126);delay(1000);// Imprime en LCD con dos metodos distintos: lcd.print(char(127));lcd.print(" ");lcd.printByte(127);delay(1000);// lcd.print(char()) y lcd.printByte() lcd.print(char(60));lcd.print(" ");lcd.printByte(60);delay(1000);// Estos resultados son identicos lcd.print(char(62));lcd.print(" ");lcd.printByte(62);delay(1000);// Aunque podria haber variaciones con otros valores lcd.clear(); for (int i=33;i<2048;i++){// caracteres especiales LCD // Por serial no imprime los mismos caracteres que en el LCD, además estos últimos se pueden ver diferentes Serial.print(i);Serial.print(": ");Serial.printByte(i);Serial.println(); lcd.setCursor(0,0);lcd.print(i);lcd.print(": ");lcd.printByte(i); delay(1000); lcd.clear(); } } void loop() { // put your main code here, to run repeatedly: }
Como os comentaba al inicio de esta entrada, es posible crear nuestros propios símbolos, os dejo a continuación en ejemplo básico que os servirá de referencia para que vosotros mismos diseñéis los vuestros:
// LCD_I2C_Crear simbolos personalizados, compilacion Arduino 1.8.1, 01/03/2019 #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,16,2); /* El LCD tiene una estructura de 16 celdas x dos filas cada celdas del LCD tienen una estructura de 5x8bits para iluminar uno de estos bits hay que darle el valor 1 para crear un simbolo debemos operar sobre la celda que queremos tratar creando un array con una longitud igual al nº de filas de la celda(8bits) y cada valor del array tendra una longitud igual nº de columnas de la celda(5bits) para que arduino reconozca los valores indicados, estos deben formatearse con el prefijo 0b seguido de los valores de los bits, 0 apagado y 1 encendido. P.D. Tras la ventana que muestra este codigo se muestra una grafica en excel que aclara lo descrito*/ byte flechaAbajoL[8]={0b00100,0b00100,0b00100,0b00100,0b00100,0b10101,0b01110,0b00100}; byte flechaAbajoM[8]={0b00000,0b00000,0b00100,0b00100,0b00100,0b10101,0b01110,0b00100}; byte flechaAbajoC[8]={0b0,0b0,0b0,0b0,0b00100,0b10101,0b01110,0b00100};// si el codigo solo cotiene ceros se abrevia con 0b0 byte flechaArribaL[8]={0b00100,0b01110,0b10101,0b00100,0b00100,0b00100,0b00100,0b00100}; byte flechaArribaM[8]={0b00100,0b01110,0b10101,0b00100,0b00100,0b00100,0b00000,0b00000}; byte flechaArribaC[8]={0b00100,0b01110,0b10101,0b00100,0b00000,0b00000,0b00000,0b00000}; byte flechaDerC[8]={0b00000,0b00100,0b00010,0b01111,0b00010,0b00100,0b00000,0b00000}; byte flechaIzqC[8]={0b00000,0b00100,0b01000,0b11110,0b01000,0b00100,0b00000,0b00000}; // ! La memoria del LCD solo puede acumular 8 simbolos personalizados ! // si probaís de descomentar los dos siguientes comprobareis que el noveno(grados) pasa a tomar la posicion 0 de nuestros simbolos // una solucion a esta limitacion es crear el char con la funcion lcd.createChar() a medida que lo necesitemos dentro del programa //byte grados[8]={0b01100,0b10010,0b10010,0b01100,0b0,0b0,0b0,0b0}; // //byte centro[8]={0b11111,0b10001,0b10001,0b10101,0b10001,0b10001,0b11111,0b0}; void setup() { lcd.init(); lcd.backlight(); lcd.createChar(0,flechaArribaC);// Carga en la memoria (posicion 0)del LCD el char flechaArribaC lcd.createChar(1,flechaDerC); // Carga en la memoria (posicion 1)del LCD el char flechaDerC lcd.createChar(2,flechaAbajoC); // Carga en la memoria (posicion 2)del LCD el char flechaAbajoC lcd.createChar(3,flechaIzqC); // y asi sucesivamente lcd.createChar(4,flechaAbajoL); lcd.createChar(5,flechaAbajoM); lcd.createChar(6,flechaArribaL); lcd.createChar(7,flechaArribaM); // lcd.createChar(8,grados); // lcd.createChar(9,centro); lcd.setCursor(0,0); lcd.write(0); // al tratarse de un char debemos utilizar el metodo write() para imprimir el simbolo lcd.print(char(1)); // aunque se puede emplear el metodo print de este modo lcd.write(2); lcd.write(3); lcd.write(4); lcd.write(5); lcd.write(6); lcd.write(7); // lcd.write(8); // lcd.write(9); } void loop() { for (int i=0;i<4;i++){ // con este bucle creamos movimiento lcd.setCursor(0,1);//situamos el cursor en la misma posicion en cada iteracion para sobreescribir la impresion // de lo contrario iria avanzando sobre las celdas en cada iteracion lcd.write(i);delay(250); } }
Para finalizar os pongo el programa reloj, como siempre fuertemente comentado, donde además se explican algunos conceptos, el código contiene un método propio para determinar si el año corriente es bisiesto y establecer los días del mes de Febrero, también se hace uso de la EEPROM para ir guardando la hora y así no perderse en caso de reinicio accidental o fallo en el suministro eléctrico.
Otro asunto importante a destacar es el uso de la función millis() en el programa, sin esta sería imposible su operatividad. La función millis() lee el tiempo transcurrido desde que se inicia la Arduino en milisegundos. Podéis conocer mejor esta función desde su referencia al lenguaje AQUI.
Para que nuestra Arduino sea independiente vamos a hechar mano de la EEPROM, en primera instancia debemos cargar un Sketch donde se establece la fecha:
// CargarFecha, SKETCH PREVIO A LA CARGA DE RelojBasico con LCD, Compilacion Arduino 1.8.1, 01/03/2019 #include <EEPROM.h> byte seg=0; // Asigna un valor a la variable seg byte hora=14; // Asigna un valor a la variable hora byte minuto=29; // Asigna un valor a la variable minuto byte dia=1; // Asigna un valor a la variable dia byte mes=3; // Asigna un valor a la variable mes byte any=19; // Asigna un valor a la variable any void setup() { Serial.begin(9600); //for(int i=0;i<1024;i++){EEPROM.write(i,0);}// borrado completo de la EEPROM EEPROM.write(0,seg); // Establece en la posicion 0 de la EEPROM el valor de seg EEPROM.write(1,hora);// Establece en la posicion 1 de la EEPROM el valor de hora EEPROM.write(2,minuto);// Establece en la posicion 2 de la EEPROM el valor de minuto EEPROM.write(3,dia);// Establece en la posicion 3 de la EEPROM el valor de dia EEPROM.write(4,mes);// Establece en la posicion 4 de la EEPROM el valor de mes EEPROM.write(5,any);// Establece en la posicion 5 de la EEPROM el valor de any // Imprime la hora establecida Serial.print(EEPROM.read(1));Serial.print(":");Serial.print(EEPROM.read(2));Serial.print(":");Serial.print(EEPROM.read(0));Serial.print(" "); // Imprime la fecha establecida Serial.print(EEPROM.read(3));Serial.print("/");Serial.print(EEPROM.read(4));Serial.print("/");Serial.print(EEPROM.read(5)+2000); // En la EEPROM solo cabe un byte por eso solo guardamos en ella 19 para la variable any, al recuperar su valor le sumamos 2000 } void loop() {}
Una vez tenemos los datos cargados en la EEPROM ya podemos subir el Sketch RelojBasico el cual a partir de este momento mantendrá actualizada la EEPROM segundo a segundo siempre que no le falte la energía.
Ahí va el susodicho:
// RelojBasico con LCD, Compilacion Arduino 1.8.1 01/03/2019 #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,16,2); #include <EEPROM.h> byte seg=EEPROM.read(0)+2;// cada vez que byte hora=EEPROM.read(1); // se inicie nuestra byte minuto=EEPROM.read(2);// Arduino toma byte dia=EEPROM.read(3);// los datos byte mes=EEPROM.read(4); // de la EEPROM int any=EEPROM.read(5)+2000; // Hay que sumarle 2000 para ver el año en su formato completo long miMilli; // declaramos nuestra variable para el control del tiempo byte Mes[12]={31,28,31,30,31,30,31,31,30,31,30,31}; // Tabla de los dias del mes char tSemana[7]={'L','M','X','J','V','S','D'}; // Tabla de los dias de la semana char dSemana; // Variable que almacena el dia de la semana //-------------------------------------------------------------------------------------------------------------------------------- void setup void setup(){ diaSemana(); // LLamanos a funcion diaSemana para determinar que dia de semana es lcd.init(); // Inicializamos la LCD lcd.backlight(); // Encedemos la iluimnacion del LCD esbisiesto(any); // Llamamos a la funcion esBisiesto para determinar los dias del mes de febrero lcd.setCursor(0,0);lcd.print("Iniciando...");lcd.clear();// Comienza la fiesta } //-------------------------------------------------------------------------------------------------------------------------------- void loop void loop() { miMilli=millis()/999.22; // Asignamos a nuestra variable el momento en el que nos encontramos para calcualar un segundo while(miMilli+1>millis()/999.22){};// Este bucle mantiene el flujo congelado hasta que ha trancurrido un segundo aproximadamente. // En este caso el Sketch cotiene las instrucciones elementales para el funcionamiento del reloj, y estas instrucciones tardan // un determinado tiempo en ejecutarse de ahi que se aproxime mucho a los 1000 ms. si en este Sketch añadimos instrucciones (que es la idea) // el tiempo de ejecucion será superior y habrá que ajustarse a ese tiempo. // Nosotros vamos a controlar el tiempo con la division 999.22, si se adelanta hay que subir el valor de la division y si se atrasa lo bajamos // Si la variacion es muy grande empezamos subiendo o bajando una unidad, si tarda muchos dias en desfasarse cambiamos las decimales, hasta que sea preciso. // Es muy importante que las dos divisiones sean identicas en las dos lineas seg++;EEPROM.write(0,seg);// sumamos un segundo y lo guardamos en la EEPROM if (seg > 59){seg=0;EEPROM.write(0,seg);minuto++;EEPROM.write(2,minuto);} // sumamos un minuto al superar los 59 seg. y actualizamos la EEPROM if (minuto > 59){ if (hora < 23){ minuto=0;EEPROM.write(2,minuto);hora++;EEPROM.write(1,hora); // sumamos una hora si son menos de las 23h. y actualizamos la EEPROM }else{ // si son las 23h. la siguiente hora seran la 0h. y actualizamos la EEPROM minuto=0;EEPROM.write(2,minuto);hora=0;EEPROM.write(1,hora);dia++;EEPROM.write(3,dia); } // cierra else } if (dia > Mes[mes-1]){dia=1;mes++;EEPROM.write(3,dia);EEPROM.write(4,mes);}// Comprobamos que el dia del mes no supere lo establecido en su tabla if (mes > 12 ){mes=1;EEPROM.write(4,mes);any++;EEPROM.write(5,any-2000);esbisiesto(any);} // sumamos un año si el mes es superior a 12 // en este momento habra que comprobar si el nuevo año es bisiesto lcd.clear(); // Limpiamos la LCD if (hora<10){lcd.print("0");} // Añadimos un cero si la hora solo contiene un digito lcd.print(hora); // imprime la hora lcd.print(':'); // imprime pues eso... dos puntos if (minuto<10){lcd.print("0");}// Añadimos un cero si los minutos solo contienen un digito lcd.print(minuto); // imprime el minuto lcd.print(':'); if (seg<10){lcd.print("0");}// Añadimos un cero si los segundos solo contiene un digito lcd.print(seg); // imprime el minuto lcd.setCursor(0,1);// pasamos a la segunda linea lcd.print(dSemana);// imprime el dia de la semana lcd.print(" "); if (dia<10){lcd.print("0");} // imprime la fecha lcd.print(dia); lcd.print('/'); if (mes<10){lcd.print("0");} lcd.print(mes); lcd.print('/'); lcd.print(any); } // cierra loop //------------------------------------------------------------------------------------------------------------------------------ Metodo esbisiesto void esbisiesto(int any){ // Comprueba si el año que recibe es bisiesto if (any %4==0 && any%100!=0 || any%400==0){ // en caso de serlo le asigna al mes de febrero 29 dias Mes[1]=29;lcd.print("Bisiesto");lcd.setCursor(0,1);lcd.print("Febrero ");lcd.print(Mes[1]);lcd.print(" dias"); }else{ // en caso de no ser bisiesto asigna a febrero 28 dias Mes[1]=28;lcd.print("No Bisiesto");lcd.setCursor(0,1);lcd.print("Febrero ");lcd.print(Mes[1]);lcd.print(" dias"); } delay(1000); } //------------------------------------------------------------------------------------------------------------------------------- Metodo diaSemana void diaSemana(){// Determina el dia de la semana byte puntero=1; // sabemos que el 1/1/2019 fue Martes, de ahí que puntero valga 1, que es la posicion de Martes en la tabla tSemana byte d=1; // establecemos una variable dia para ir sumando los dias uno a uno byte m=1; // establecemos una variable mes para ir sumando los meses uno a uno String stfecha=String(dia)+"_"+String(mes);// hacemos un cast y covertimos el d y el m en una string String stF=String(d)+"_"+String(m);// hacemos un cast y covertimos el dia y el mes en una string para ser comparada en el while while(!stF.equals(stfecha)){ // mientras la cadena que irá variando el bucle sea distinta a la fecha actual se sumaran dias if(puntero>5){puntero=0;}else{puntero++;} // controlamos el valor de puntero, éste nos señalará la posicion final d++; // vamos sumando uno a cada iteracion a la variable d if (d > Mes[m-1]){d=1;m++;} // comprobacion para sumar un mes stF=String(d)+"_"+String(m); // a cada iteracion debemos actualizar la string stF para que el bucle la compare con la fecha actual }// cuando las cadenas son identicas se sale del bucle while y se dSemana=tSemana[puntero]; // establece el dia de la semana en funcion de donde haya quedado el puntero. }
Os dejo una imagen con el programa corriendo en este preciso momento...
Bueno, y con esta entrada doy por concluido el apartado de utilidades, a partir de este momento ya disponemos de todos los conocimientos necesarios y contamos con todas las herramientas para realizar nuestra estación meteorológica.
No hay comentarios:
Publicar un comentario
Gracias por tu comentario, en breve lo revisaremos.