sábado, 6 de abril de 2019

Sensor de Presión y temperatura BMP180

De sus siglas BaroMetric Pression, este sensor es un extraordinario elemento electrónico nos ofrece la presión base, la presión atmosférica, la altura y también dispone en su interior de un transistor que nos permite conocer la temperatura.

El BMP180 es la evolución del famoso BMP085, ambos son elementos altamente resistentes y eficaces.


El GY-68 se corresponde al BMP180 y el GY-65 al BMP085,  su precio ronda los 3€ y podemos encontrarlos Aliexpress, en Banggood o en Amazon por ejemplo.

Si hacemos uso del segundo solamente emplearemos los pines con el mismo nombre del BMP180 SDA-SCL-GND-VIN ó VCC. Personalmente recomiendo el uso del GY-68 ya que la librería diseñada para su uso es mas resultona y nos ahorra muchísimo código. Hay que descargarla!!!

El diagrama de conexión se queda así: (no he encontrado icono para BMP180 en fritzing!!!, sorry)


A continuación veremos el código mas elemental para ver su funcionamiento a través de la consola:

/* ESQUEMA DE CONEXIONES, Compilación Arduino 1.8.1 14/03/2019
         
                      7-14v.                  +-----+
                  ____[PWR]___________________| USB |__
                 |                            +-----+  |
                 |         GND/RST2  [ ][ ]            |
                 |       MOSI2/SCK2  [ ][ ]  A5/SCL[ ] | <---- SCL del sensor
                 |          5V/MISO2 [ ][ ]  A4/SDA[ ] | <---- SDA del sensor
                 |                             AREF[ ] |
                 |                              GND[ ] |
                 | [ ]N/C                    SCK/13[ ] |
                 | [ ]IOREF                 MISO/12[ ] |
                 | [ ]RST                   MOSI/11[ ]~| 
                 | [ ]3V3    +---+               10[ ]~|      
VCC BMP180 ----> | [ ]5v    -| A |-               9[ ]~| 
GND BMP180 ----> | [ ]GND   -| R |-               8[ ] |  
                 | [ ]GND   -| D |-                    |
                 | [ ]Vin   -| U |-               7[ ] |
                 |          -| I |-               6[ ]~|   
                 | [ ]A0    -| N |-               5[ ]~|   
                 | [ ]A1    -| 0 |-               4[ ] |                  
                 | [ ]A2     +---+           INT1/3[ ]~| 
                 | [ ]A3                     INT0/2[ ] |
                 | [ ]A4/SDA  RST SCK MISO     TX>1[ ] |   
                 | [ ]A5/SCL  [ ] [ ] [ ]      RX<0[ ] |   
                 |            [ ] [ ] [ ]              |
                 |  UNO_R3    GND MOSI 5V  ____________/
                  \_______________________/
*/
#include <SFE_BMP180.h>// importamos la libreria para su uso 
#include <Wire.h>// la libreria Wire es necesaria para la gestion de su direccion y envio de datos
SFE_BMP180 pressure;
double Presion = 0;
double PresionBase=0;
double Temperatura = 0;
char status;
void setup(){
  Serial.begin(9600);
  SensorStart();// inicializa el sensor
}
void loop(){
  ReadSensor(); // Leemos el sensor
  Serial.print("Temperatura: ");Serial.print(Temperatura);Serial.println(" grados C");//imprimimos la temperatura
  Serial.print("Presion: ");Serial.print(Presion);Serial.println(" milibares");//imprimimos la presion atmosferica
  Serial.println();
  delay(500);
}

void SensorStart(){  //Secuencia de inicio del sensor
  if (pressure.begin()){
    Serial.println("BMP180 Conexion exitosa");
  }else{
    Serial.println("BMP180 fallo de conexion (desconectado?)\n\n");
    while (1);
    }
  status = pressure.startTemperature();  //Se inicia la lectura de temperatura
  if (status !=0 ){
     delay(status);
     status = pressure.getTemperature(Temperatura);//Se lee una temperatura inicial
  }
  if (status !=0 ){
     status = pressure.startPressure(3);//Se inicia la lectura de presiones
  }
  if(status !=0 ){
     delay(status);
     status = pressure.getPressure(PresionBase, Temperatura); //Se lee la presión inicial incidente sobre el sensor en la primera ejecución
  }

} // cierra SensorStart

void ReadSensor() {//En este método se hacen las lecturas de presión y temperatura
    status = pressure.startTemperature(); // Se inicia la lectura de temperatura
    if (status != 0){
        delay(status);
        status = pressure.getTemperature(Temperatura);
    }
    if (status != 0){
       status = pressure.startPressure(3);
    }
    if (status != 0){
      delay(status);
      status = pressure.getPressure(Presion, Temperatura);
    }
}

El resultado debería ser algo así:


y la conexión: (la foto ha quedado un poco fea, lo sé)


Como vemos es bastante sencillo, ahora quizás vamos a crear un código para que nos muestre los resultados por el LCD que además emplea los mismos pines, (SCL, SDA) con lo que comprobaremos la utilidad de la librería Wire al gestionar ella misma las direcciones:

/* ESQUEMA DE CONEXIONES, Compilación Arduino 1.8.1 14/03/2019
                      7-14v.                  +-----+
                  ____[PWR]___________________| USB |__
                 |                            +-----+  |
                 |         GND/RST2  [ ][ ]            |
                 |       MOSI2/SCK2  [ ][ ]  A5/SCL[ ] | <---- SCL del LCD
                 |          5V/MISO2 [ ][ ]  A4/SDA[ ] | <---- SDA del LCD
                 |                             AREF[ ] |
                 |                              GND[ ] |
                 | [ ]N/C                    SCK/13[ ] |
                 | [ ]IOREF                 MISO/12[ ] |
                 | [ ]RST                   MOSI/11[ ]~| 
                 | [ ]3V3    +---+               10[ ]~|      
VCC BMP180,LCD-> | [ ]5v    -| A |-               9[ ]~| 
GND BMP180,LCD-> | [ ]GND   -| R |-               8[ ] |  
                 | [ ]GND   -| D |-                    |
                 | [ ]Vin   -| U |-               7[ ] |
                 |          -| I |-               6[ ]~|   
                 | [ ]A0    -| N |-               5[ ]~|   
                 | [ ]A1    -| 0 |-               4[ ] |                  
                 | [ ]A2     +---+           INT1/3[ ]~| 
                 | [ ]A3                     INT0/2[ ] |
 SDA BMP180 ---> | [ ]A4/SDA  RST SCK MISO     TX>1[ ] |   
 SCL BMP180 ---> | [ ]A5/SCL  [ ] [ ] [ ]      RX<0[ ] |   
                 |            [ ] [ ] [ ]              |
                 |  UNO_R3    GND MOSI 5V  ____________/
                  \_______________________/
*/

#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)
#include <SFE_BMP180.h>// importamos la libreria para su uso 
#include <Wire.h>// la libreria Wire es necesaria para la gestion de su direccion y envio de datos
SFE_BMP180 pressure;
double Presion = 0;
double PresionBase=0;
double Temperatura = 0;
char status;

void setup(){
  miLCD.init();// Inicializa el LCD
  miLCD.backlight();// Enciende la iluminacion del LCD
  miLCD.print("Iniciando"); // Imprime (por defecto en el primer espacio disponible)
  delay(500);// espera 1/2 segundo
  miLCD.clear();// Limpia el LCD
  SensorStart();// inicializa el sensor BMP180
}

void loop(){
  ReadSensor(); // Leemos el sensor
  miLCD.setCursor(0,0);miLCD.print("Temp: ");miLCD.print(Temperatura);miLCD.print(" C");//imprimimos la temperatura
  miLCD.setCursor(0,1);miLCD.print("Presion: ");miLCD.print(Presion);miLCD.print(" mb");//imprimimos la presion atmosferica
  delay(500);
}
void SensorStart(){  //Secuencia de inicio del sensor
  if (pressure.begin()){
    Serial.println("BMP180 Conexion exitosa");
  }else{
    Serial.println("BMP180 fallo de conexion (desconectado?)\n\n");
    while (1);
    }
  status = pressure.startTemperature();  //Se inicia la lectura de temperatura
  if (status !=0 ){
     delay(status);
     status = pressure.getTemperature(Temperatura);//Se lee una temperatura inicial
  }
  if (status !=0 ){
     status = pressure.startPressure(3);//Se inicia la lectura de presiones
  }
  if(status !=0 ){
     delay(status);
     status = pressure.getPressure(PresionBase, Temperatura); //Se lee la presión inicial incidente sobre el sensor en la primera ejecución
  }

} // cierra SensorStart

void ReadSensor() {//En este método se hacen las lecturas de presión y temperatura
    status = pressure.startTemperature(); // Se inicia la lectura de temperatura
    if (status != 0){
        delay(status);
        status = pressure.getTemperature(Temperatura);
    }
    if (status != 0){
       status = pressure.startPressure(3);
    }
    if (status != 0){
      delay(status);
      status = pressure.getPressure(Presion, Temperatura);
    }
}

Lo bueno es que no necesitamos hacer nada, el protocolo I2C integrado en nuestra Arduino se encarga de todo. Para saber más acerca del BUS I2C os recomiendo esta entrada de Luis Llamas.

La conexión se queda así:


Por cierto, el shield que estáis viendo es donde tengo conectados todos los sensores de mi Estación Meteorológica 1.0, es una plaqueta exclusiva para que te puedas hacer tus propias virguerías con tu Arduino:


Me costo muy mucho encontrarlo, Aquí mismo os dejo un enlace al sitio:


Bueno, con este ya casi hemos tratado con todos los elementos para crear nuestra EstMet, solo falta por tratar dos elementos más, El Anemómetro y el Pluviómetro. Estas serán las próximas entregas antes de crear nuestro cocktail Meteorológico.

sábado, 2 de marzo de 2019

Sensor de humedad y temperatura DHT-22, DHT-11


Los DHT22 y DHT11 son elementos que nos permiten detectar tanto la humedad como la temperatura, su popularidad es indiscutible y son muy empleados en el mundo de Arduino.




El DHT22 es el hermano mayor del DHT11 se dice por ahí que es mas preciso, con rangos de lectura mas amplios y algunas cosas mas... en mi experiencia lo que puedo decir es que los DHT22 tienen una vida media de 6 meses, mientras que los DHT11 que compre hace alrededor de 4 ó 5 años siguen funcionando.

Por su parte el precio de estos sensores oscila entre los 0,40€ para el DHT11 y los 3€ para los DHT22

Arduino cuenta con librerías para hacer uso de ellos con mucha comodidad. Hay que descargarla!!!

Por su parte la conexión es idéntica en ambos casos y sería algo así:


El valor de la resistencia puede oscilar entre 2,2KΩ y 10KΩ


Para mayor comodidad este sensor se suele vender con su shield, que nos va facilitar la conexión:




El de la izquierda cuenta con tres pines para la conexión directa a la Arduino: Positivo - señal - negativo.

Vamos a ver un código para comprobar su funcionamiento a través del serial:


/* ESQUEMA DE CONEXIONES - Compilación Arduino 1.8.1 14/03/2019
         
                      7-14v.                  +-----+ 
                  ____[PWR]___________________| USB |__
                 |                            +-----+  |
                 |         GND/RST2  [ ][ ]            |
                 |       MOSI2/SCK2  [ ][ ]  A5/SCL[ ] |
                 |          5V/MISO2 [ ][ ]  A4/SDA[ ] | 
                 |                             AREF[ ] |
                 |                              GND[ ] |
                 | [ ]N/C                    SCK/13[ ] |
                 | [ ]IOREF                 MISO/12[ ] |
                 | [ ]RST                   MOSI/11[ ]~| 
                 | [ ]3V3    +---+               10[ ]~|   
    + DHT -----> | [ ]5v    -| A |-               9[ ]~| 
    - DHT -----> | [ ]GND   -| R |-               8[ ] |  
                 | [ ]GND   -| D |-                    |
                 | [ ]Vin   -| U |-               7[ ] |<-- Señal DHT
                 |          -| I |-               6[ ]~|   
                 | [ ]A0    -| N |-               5[ ]~|   
                 | [ ]A1    -| 0 |-               4[ ] |                  
                 | [ ]A2     +---+           INT1/3[ ]~| 
                 | [ ]A3                     INT0/2[ ] |
                 | [ ]A4/SDA  RST SCK MISO     TX>1[ ] |   
                 | [ ]A5/SCL  [ ] [ ] [ ]      RX<0[ ] |   
                 |            [ ] [ ] [ ]              |
                 |  UNO_R3    GND MOSI 5V  ____________/
                  \_______________________/
*/
#include "DHT.h" // Incluimos la libreria DHT
#define DHTPIN 7 // Conectado al pin de datos del sensor

//#define DHTTYPE DHT11 // descomentar para emplear DHT 11 
#define DHTTYPE DHT22 // comentar para emplear el DHT11
DHT dht(DHTPIN, DHTTYPE);

void setup() {
 Serial.begin(9600); 
 Serial.println("Iniciando DHT");
 dht.begin(); // Inicializa el sensor
}

void loop() {
   float hum = dht.readHumidity(); // asignamos el valor de humedad a nuestra variable hum
   float temp = dht.readTemperature();// asignamos el valor de temperatura a nuesta variable temp
   if (isnan(temp) || isnan(hum)) { // la funcion boolena isnan (isnotanumber), devuelve true si lo leido no es un numero
      Serial.println("ERROR al leer DHT");
   } else {
       Serial.print("Humedad: "); 
       Serial.print(hum);
       Serial.print(" %\t");
       Serial.print("Temperatura: "); 
       Serial.print(temp);
       Serial.println(" C");
      }
      delay(500);
}

La salida por Serial ...


y el conexionado ...



Como podemos ver, es de lo mas sencillo gracias a la librería DHT de Adafruit que podéis descargar AQUÍ MISMO


sábado, 2 de febrero de 2019

LCD a la vista

Ya llevo algunas entradas por este canal y todavía estoy dando palos de ciego... nuestros proyectos necesitan algo de luz e independencia.
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.