domingo, 9 de diciembre de 2018

Veleta con Sensores de efecto Hall 49E

Hasta ahora hemos dedicado tiempo a la preparación, y ha llegado la hora de tratar los elementos con los que construiremos nuestra estación meteorológica.
Esta publicación esta dedicada a la veleta, y debo decir que ha sido lo mas complicado de implementar, tanto para encontrar el sensor adecuado como por su programación.
Después de probar con infinidad de elementos, y tras algunas semanas de trabajo sin mayores complicaciones con el sensor de efecto Hall 49E, he decidido unánimemente que este es el más idóneo.
El sensor de efecto Hall 49E es un sensor analógico, esto significa que lanza una rango de valores que pueden ir de 0 a 1023.
Si lo conectamos a nuestra Arduino como se muestra en la imagen de más abajo, y le acercamos un imán veremos que por su patilla de salida podemos leer el rango de valores descrito anteriormente, en función de la proximidad del campo Norte magnético.


En AliExpress  compré treinta unidades de estos elementos electrónicos por unos 2 Euros.

Por otro lado un día de muchos que dediqué a buscar ideas por la RED para hacer mi veleta, me tope con un post de un Arduinero que había desmontado una veleta comercial para averiguar su sistema, Resultaba que tenia dispuestos dos sensores de efecto hall (un modelo realmente caro) en forma de L y en el centro un imán sujeto al eje de la veleta. Este Arduinero teorizaba acerca de su funcionamiento sin que ofreciera ni solución práctica ni Sketch alguno, pero eso si, me dió suficiente información como para llevarlo a cabo.

La Idea es la siguiente: el sensor dispuesto verticalmente ofrecería los valores de las coordenadas X y el sensor horizontal ofrecería las coordenadas Y. Como es de conocimiento universal en un plano cartesiano, con un valor X y un valor Y obtenemos un vector, ese vector forma un angulo respecto a la vertical y ese angulo es igual al "componente" que necesitamos para determinar la dirección del viento. ¿Es genial verdad?, pues implementarlo me ha llevado un rato.

Espero que la siguiente ilustración aclare algo un poco más lo anteriormente expuesto:



Aviso, como podéis ir imaginando esta entrada será larga de narices...

Ahora esto tenemos que convertirlo en valores útiles, así que vamos a meterle mano al map()
en el lugar apropiado (ya se verá más adelante) de nuestro Sketch; ponemos algo así:

  vectorX=map(sensorX,0,1023,-100,100);
  vectorY=map(sensorY,0,1023,-100,100);


y el map() nos arroja valores tal como se muestran en la siguiente tabla:


Cuadro 1 = 90º cuadro 2 = 180º cuadro 3 = 270º cuadro 4 = 0º
Lectura
Sensor
Valor
Mapeado
Lectura
Sensor
Valor
Mapeado
Lectura
Sensor
Valor
Mapeado
Lectura
Sensor
Valor
Mapeado
X 1023 100 512 0 0 -100 512 0
Y 512 0 0 -100 512 0 1023 100

A continuación una ilustración creada con una hoja de cálculo para las comprobaciones oportunas:
AQUI MISMO el enlace para descargarlo, si alguien tiene problemas que lo diga.



Hasta aquí muy bien, pero hay que darle las fórmulas de triangulación oportunas a nuestra Arduino para la conversión a grados, además la cosa se complica en este punto, puesto que cada cuadrante requiere una fórmula distinta (no es del todo así), para un matemático esto no será complicado, pero este campo no es mi fuerte, además la capacidad de nuestra Arduino esta bastante limitada, con todo esto no me queda otra que aplicar el siguiente principio: " Toda resolución matemática se puede adaptar a una resolución informática" esto ya lo había comentado anteriormente, un programador con una buena base matemática tiene una gran ventaja, y si no la tienes o lo aprendes o adaptas tu programa con las herramientas que te ofrece la programación.

Mi adaptación es la siguiente, creo cuatro cuadrantes en función del valor arrojado, ya mapeado,
 de 0 a 90º Cuadrante 1(C1) En este caso tanto las X como las Y son positivas
de 91 a 180º Cuadrante 2 (C2) Aquí todas las X son positivas y las Y negativas
de 181 a 270º Cuadrante 3(C3) Ahora las X son negativas y las Y también
de 271 a 360º Cuadrante 4 (C4) y por último las X son negativas y las Y positivas

por lo que C1,C2,C3 y C4 las declaro como booleanas y las condiciono según sigue:

if (y >= 0 && x >= 0){c1=true;} // y+ x+
if (y < 0 && x >= 0){c2=true;} // y- x+
if (y < 0 && x < 0){c3=true;} // y- x-
if (y >= 0 && x < 0){c4=true;} // y+ x-


Declaro la formula para el primer cuadrante:

double formula = 180*(double)(atan((double)(x/y))/PI);


y adapto la formula para cada cuadrante:

if (c1){Serial.println(formula);}
if (c2 || c3){Serial.println(180+formula);}
if (c4){Serial.println(360+formula);}


Antes de continuar con el código debemos configurar la veleta físicamente o al menos hacer un prototipo que nos sirva de pruebas.
Los imanes deben ser cilíndricos y perforados en su interior, por donde pasaremos un eje que irá unido al eje de la veleta, deberán girar solidarios, Además los imanes deben tener sus campos magnéticos dispuestos diametralmente NO en sus caras planas.

Yo compré los que muestro en la siguiente imagen en Aliexpress del vendedor que enlazo AQUI MISMO, me costaron 100 unidades unos 13€


¡Ojo que son muy pequeños y su agujero interior es de sólo 2mm.!

Aquí debajo os muestro un prototipo para hacer las mediciones iniciales montado sobre una miniboard:


Como podéis observar la varilla que empleo como eje es de un diámetro inferior, para acabarla de ajustar le puse una funda termo-retráctil.
La distancia que existe entre el imán y los sensores es escasamente de 1 mm.


Y para las pruebas oportunas os adjunto a continuación un Sketch de pruebas, éste nos va a resultar muy útil para determinar varias cosas.



#include<math.h>
boolean c1,c2,c3,c4;
//double YValue,XValue;//si no disponemos de los sensores, descomentar esta linea y sustituir las lineas 13 y 14 por lo comentado
void setup(){
  Serial.begin(9600);
}  
 
void loop(){ 
  c1=false;
  c2=false;
  c3=false;
  c4=false;
  double YValue =analogRead(0);//YValue = random(219, 866);
  double XValue =analogRead(1);//XValue = random(231, 837);
  double y = map(YValue, 219, 866, -100, 100);
  double x = map(XValue, 231, 837, -100, 100);
  if (y >= 0 && x >= 0){c1=true;} // y+ x+
  if (y < 0 && x >= 0 ){c2=true;} // y- x+
  if (y < 0 && x < 0){c3=true;} // y- x-
  if (y >= 0 && x < 0){c4=true;} // y+ x-
   Serial.print("YValue: ");Serial.print(YValue);Serial.print("  Y: ");Serial.println(y);//descomentar estas dos lineas para ver los valores 
   Serial.print("XValue: ");Serial.print(XValue);Serial.print("  X: ");Serial.println(x);//arrojados por el sensor Hall 49E y comentar la siguiente
// descomentar la siguiente linea para ver los valores ya mapeados y comentar las dos anteriores
// Serial.print("X: ");Serial.print(x);Serial.print("  Y: ");Serial.println(y); 
   double formula = 180*(double)(atan((double)(x/y))/PI);
   if (c1){Serial.println(formula);} // imprime grados igual al componente del viento
   if (c2 || c3){Serial.println(180+formula);}// imprime grados igual al componente del viento
   if (c4){Serial.println(360+formula);}// imprime grados igual al componente del viento
   Serial.println();
  delay(200);
}

Observareis que el rango de los valores indicados en el map() no son los ideales (0,1023) que había propuesto en la idea, esto se debe a dos motivos: los valores arrojados por los sensores dependen de la intensidad del imán y de la distancia en que los coloquemos del mismo, además cada sensor arrojara valores similares pero no exactamente los mismos.
De manera que lo primero será leer el rango de valores que nos arroja cada sensor e indicar su máximo y mínimo en el map (y que no tienen por que coincidir con los que yo indico en mi Sketch)

Para ello podemos comentar de la linea 15 a la 30 (desde los map) y descomentar las líneas 21 y 22.
Serial.print("Yvalue: ");.....
Serial.print("Xvalue: ");.....

Con el programa corriendo, el sensorY conectado en A0 y el sensorX en A1, hacer girar el imán sobre su eje y anotar los valores mínimos y máximos de cada uno de ellos; casi es mejor hacerlo uno después del otro comentando la linea de un sensor, e indicar en el map de cada uno de ellos el rango obtenido.

Una vez concluida la fase de pruebas sólo nos queda montar la veleta (físicamente), ¡que rápido se dice, verdad! Bueno os mostraré una imagen de como la tengo yo montada para que os hagáis una idea:


...bueno si, al final le puse un condensador, algún datasheet muestra en el diagrama básico la puesta de un condensador pero no indica característica alguna de éste así que le puse uno de 1uF 50v (el más pequeño que tenía) lo cierto es que funciona bien con o sin él.

Y ya por último quizás nos quede crear el código para trabajar con él de manera definitiva, el broche final de este blog será un código que recoge los valores de todos los sensores, los imprime por consola, por donde Processing los captura muestra y almacena. Pero mientras tanto os dejo uno para el manejo exclusivo de la veleta.
El siguiente código incluye cosas de las que todavía no he hablado en este momento, como el control del tiempo, la impresión de datos a una LCD y el respaldo de datos a una SD, estos temas se tratarán de manera individualizada para su mayor comprensión.
Por otro lado se incluye un sistema visual del componente por cuadrantes a través de leds(¡Ojo! no confundir con los cuadrantes para los sensores).
Para la gestión de la EEPROM y de los datos desde hace bastante tiempo empleo un sistema un tanto particular (que también explicaré) y que requiere cargar un Sketch antes del programa principal, este código la incluiré después del programa principal para el que lo entienda y sepa manejar,

Ahí va el programa principal:



/*  Version con sensores Hall 49E captura la posicion cada 5 minutos y mapping para guardar en EEprom
ESQUEMA DE CONEXIONES
                                          +-----+
              ____[PWR]___________________| USB |__
             |                            +-----+  |
             |         GND/RST2  [ ][ ]            |
             |       MOSI2/SCK2  [ ][ ]  A5/SCL[ ] |<---- SCL del LCD    
             |          5V/MISO2 [ ][ ]  A4/SDA[ ] |<---- SDA del LCD       
             |                             AREF[ ] |
             |                              GND[ ] |
             | [ ]N/C                    SCK/13[ ] |<---- SCK tarjeta SD    
             | [ ]IOREF                 MISO/12[ ] |<---- MISO tarjeta SD   
             | [ ]RST                   MOSI/11[ ]~|<---- MOSI tarjeta SD   
             | [ ]3V3    +---+               10[ ]~|<----- Led Verde, cuadrante PONIENTE (P) de 226º a 315º
             | [ ]5v    -| A |-               9[ ]~|<----- Led Azul, cuadrante NORTE (N) de 316º a 45º
             | [ ]GND   -| R |-               8[ ] |   
             | [ ]GND   -| D |-                    |
             | [ ]Vin   -| U |-               7[ ] |<----- CS tarjeta SD
             |          -| I |-               6[ ]~|<----- Led Naranja, cuadrante LEVANTE (E) de 46º a 135º   
Sensor 0-->  | [ ]A0    -| N |-               5[ ]~|<----- Led rojo, cuadrante SUR (S) de 136º a 225º 
Sensor 1-->  | [ ]A1    -| O |-               4[ ] |   
             | [ ]A2     +---+           INT1/3[ ]~|  
             | [ ]A3                     INT0/2[ ] |   
             | [ ]A4/SDA  RST SCK MISO     TX>1[ ] |   
             | [ ]A5/SCL  [ ] [ ] [ ]      RX<0[ ] |   
             |            [ ] [ ] [ ]              |
             |  UNO_R3    GND MOSI 5V  ____________/
              \_______________________/
     */

#include<math.h>
#include <EEPROM.h>
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,16,2);//0x3F
boolean c1,c2,c3,c4;
const byte ledRojSur=5;
const byte ledNarLev=6;
const byte ledAzuNor=9;
const byte ledVerPon=10;
boolean traspasado;//ErrorTarjeta, 
int componente;//Veleta
File DatComp,CSComp;
//byte errorTarjeta=EEPROM.read(8);
byte CambioDia=EEPROM.read(7);
int PosVeleta=EEPROM.read(9);
byte PosCtrl_V=EEPROM.read(10);
byte dia=EEPROM.read(2);
byte mes=EEPROM.read(3);
byte hora=EEPROM.read(4);
byte minuto=EEPROM.read(5);
byte segundo=EEPROM.read(6);
byte resto=0;
byte multiplo=5;
unsigned long newMilli;
byte Mes[12]={31,28,31,30,31,30,31,31,30,31,30,31};

void setup(){   
  Serial.begin(9600);//Inicializamos el puerto serie. Serial.begin(115200);
//  Serial.print(EEPROM.read(4));Serial.print(":");Serial.print(EEPROM.read(5));
//  Serial.print(" del " );Serial.print(EEPROM.read(2));Serial.print("/");Serial.println(EEPROM.read(3));
  if(PosCtrl_V==0){PosVeleta=EEPROM.read(9);} // recupera la 
  if(PosCtrl_V==1){PosVeleta=(EEPROM.read(9)+256);} // posicion de registros en funcion del 
  //Serial.print("PosVeleta: ");Serial.print(PosVeleta); Serial.println("...Debe ser 11");
  pinMode(ledRojSur, OUTPUT); // Pin 5 Veleta
  pinMode(ledNarLev, OUTPUT); // Pin 6 Veleta
  pinMode(ledAzuNor, OUTPUT); // Pin 9 Veleta
  pinMode(ledVerPon, OUTPUT); // Pin 10 Veleta
  //------------------------------------------------------------------------------------LCD
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0,0);lcd.print("Iniciando...");delay(300);lcd.clear();
  //------------------------------------------------------------------------------------
  InitSD();
}
void loop(){ 

  newMilli=millis()/996.5; //  con 996.2 se adelanta
  while(newMilli+1>millis()/996.5){};// se inicia  seg atrasado
  segundo++;
  
  componente=calculaGrados();
  
  if((componente >= 0 && componente <= 45) || (componente > 315 && componente <= 360)){digitalWrite(ledAzuNor,HIGH);digitalWrite(ledNarLev,LOW);digitalWrite(ledRojSur,LOW);digitalWrite(ledVerPon,LOW);}
  if(componente > 45 && componente <= 135){digitalWrite(ledAzuNor,LOW);digitalWrite(ledNarLev,HIGH);digitalWrite(ledRojSur,LOW);digitalWrite(ledVerPon,LOW);}
  if(componente > 135 && componente <= 225){digitalWrite(ledAzuNor,LOW);digitalWrite(ledNarLev,LOW);digitalWrite(ledRojSur,HIGH);digitalWrite(ledVerPon,LOW);}
  if(componente > 225 && componente <= 315){digitalWrite(ledAzuNor,LOW);digitalWrite(ledNarLev,LOW);digitalWrite(ledRojSur,LOW);digitalWrite(ledVerPon,HIGH);}
  if(componente > 360){digitalWrite(ledAzuNor,HIGH);digitalWrite(ledNarLev,HIGH);digitalWrite(ledRojSur,HIGH);digitalWrite(ledVerPon,HIGH);}//digitalWrite(alarma,HIGH); 
  
  if (segundo == 1 && resto==0){
         lcd.clear();lcd.setCursor(0,0);lcd.print("Grabando: ");lcd.print(componente);
         lcd.setCursor(0,1);lcd.print("en Posicion: ");lcd.print(PosVeleta);delay(500);lcd.clear(); 
         if (PosVeleta <= 255){EEPROM.write(10,0);EEPROM.write(9,PosVeleta);}// guarda en PosCtrl_V; EEPROM(10) el numero
         if (PosVeleta > 255 && PosVeleta <= 511){EEPROM.write(10,1);EEPROM.write(9,PosVeleta);}// en giro de bytes que dara       
         byte x = map(componente, 1, 360, 1, 255);// convierte el valor(componente)a un byte para poder guardarlo en la EEprom
         EEPROM.write(PosVeleta,x);PosVeleta++;
  }
  
  if (segundo > 59){segundo=0;EEPROM.write(6,segundo);minuto++;EEPROM.write(5,minuto);}
  if (minuto > 59){minuto=0;EEPROM.write(5,minuto);
      if (hora < 23){hora++;EEPROM.write(4,hora);
        }else{CambioDia=2;EEPROM.write(7,2);hora=0;EEPROM.write(4,hora);dia++;EEPROM.write(2,dia); 
            for(int i=11;i<=299;i++){EEPROM.write(i+289,EEPROM.read(i));delay(2);} // hace CS de los registros del dia en la misma EEprom    
            InitSD();
            if (CambioDia==2){ 
               traspasado=Traspas_SD();
               EEPROM.write(1023,traspasado);                 
               CSComp=SD.open("CSComp", FILE_WRITE);
               for(int i=300;i<=588;i++){CSComp.println(EEPROM.read(i));delay(2);}
               CSComp.close(); 
               PosVeleta=11;EEPROM.write(9,PosVeleta);
               PosCtrl_V=0;EEPROM.write(10,PosCtrl_V);   
               CambioDia=1;EEPROM.write(7,1);
           }         
      }
  }
 resto=minuto%multiplo;
//---------------------------------------IMPRESION LCD    
 lcd.setCursor(0,0);lcd.clear();if (hora<10){lcd.print("0");}lcd.print(hora);lcd.print(":");if(minuto<10){lcd.print("0");}lcd.print(minuto);lcd.print(":");
 if (segundo<10){lcd.print("0");}lcd.print(segundo);lcd.print(" ");
 lcd.setCursor(11,0);if(dia<10){lcd.print("0");}lcd.print(dia);lcd.print("/");if(mes<10){lcd.print("0");}lcd.print(mes);
 lcd.setCursor(0,1);lcd.print("Componente: ");lcd.print(componente);
//---------------------------------------------

Serial.println(componente);

 if (mes!=EEPROM.read(3)){EEPROM.write(3,mes);}
 if (dia > Mes[mes-1]){dia=1;mes++;EEPROM.write(2,dia);EEPROM.write(3,mes);}
 if (mes > 12 ){mes=1;EEPROM.write(3, mes);}   
}//------------------------------------------------------------------------------------------------------------ cierra LOOP

void InitSD(){
   if (!SD.begin(7)){
      lcd.setCursor(0,0);lcd.print("SD NO conectada");delay(300);lcd.clear();
   }else{lcd.setCursor(0,0);lcd.print("SD en Conexion");delay(300);lcd.clear();} 
  if(!SD.exists("DtComp")){ 
      lcd.setCursor(0,0);lcd.print("Error Lectura SD");delay(400);lcd.clear();
  }else{lcd.setCursor(0,0);lcd.print("Componente existe ");delay(700);lcd.clear();} //   Serial.println("Componente existe ");
}//cierra funcion InitSD

boolean Traspas_SD(){
   InitSD();
   DatComp = SD.open("DtComp",FILE_WRITE);
   for(int i=11;i<=299;i++){
      int x = map(EEPROM.read(i), 1, 255, 1, 360);// convierte el valor(componente) guardado en la EEprom(como Byte) a Int
      DatComp.println(x);delay(2);
   }
   boolean z=SD.exists("DtComp");
   DatComp.close();
   return z;
}//cierra funcion Traspas_SD

int calculaGrados (){ // funcion denominada calculaGrados(), esta funcion determina que potenciometro debe emplear y devuelve su valor.
   c1=false;
   c2=false;
   c3=false;
   c4=false;
   
   double YValue =analogRead(0);
   double XValue =analogRead(1);

   double x = map(XValue, 231, 837, -100, 100);//XValue, 250, 766, -100, 100
   double y = map(YValue, 219, 866, -100, 100);//YValue, 197, 866, -100, 100
  
   if (y >= 0 &&  x >= 0){c1=true;} // y+ x+
   if (y < 0  &&  x >= 0){c2=true;} // y- x+
   if (y < 0  &&  x < 0 ){c3=true;} // y- x-
   if (y >= 0 &&  x < 0) {c4=true;} // y+ x-
  
   double formula = 180*(double)(atan((double)(x/y))/PI);
   
   if (c1){return formula;}
   if (c2 || c3){return (180+formula);
   } else { return (360+formula);}
} // Cierra funcion 

y a continuación os dejo el código para que el programa principal gestione correctamente los datos y la EEPROM.
Recuerdo que el siguiente código debe cargarse antes que el programa principal, en definitiva hay que indicarle hora, fecha y el día juliano, que es 0 para el 1 de enero y 365 para el 31 de diciembre.
PosVeleta, es la variable que almacena la posición de la EEPROM donde debe guardarse el siguiente registro.
PosCtrl_V, es una variable auxiliar para PosVeleta, ésta última se guarda en la EEPROM, cuando el valor de PosVeleta supera el byte,  PosCtrl_V suma uno, y el programa principal determina la posición donde guardar el siguiente registro sumando 255 al valor de PosVeleta.
Si la Arduino se reinicia por algún motivo la posición queda guardada en la EEPROM, pero si el tiempo de parada es elevado habrá que actualizar la hora o minuto y en consecuencia los valores de PosVeleta y PosCtrl_V en su caso, lógicamente cada minuto del día esta asociado a una posición concreta de PosVeleta, este dato será necesario conocerlo para indicárselo.

Sketch previo al programa principal:



// PROGRAMA PREVIO AL PRINCIPAL PARA LA VELETA CON SENSOR HALL 49E
#include <EEPROM.h> // sentecia para EEPROM
int PosVeleta=0;
int PosAnem=0;
void setup() {
Serial.begin(9600);
// *********************************************** BORRADO/ESCRITURA DE DATOS
//  EEPROM.write(0,220);// Jul0
//  EEPROM.write(1,0);// Jul1
  EEPROM.write(2,29);// dia
  EEPROM.write(3,7);// mes
  EEPROM.write(4,10);// hora
  EEPROM.write(5,38);// minuto
  EEPROM.write(6,0);// seg
  EEPROM.write(7,1);// cambioDia
//  EEPROM.write(9,138);// PosVeleta
//  EEPROM.write(10,0);// PosCtrl_V
/*
 for (int i=11;i<=299;i++){ // registro veleta
   EEPROM.write(i,0);
 }
  for (int i=300;i<=588;i++){ // CS registros
   EEPROM.write(i,0);
 }
*/
// *****************************************************************
// ----------------------------------------------------------------- LECTURA
Serial.print("Ultimo Registro a las " );Serial.print(EEPROM.read(4));Serial.print(":");Serial.print(EEPROM.read(5));
Serial.print(" del " );Serial.print(EEPROM.read(2));Serial.print("/");Serial.println(EEPROM.read(3));
//Serial.print("Juliano: " );Serial.println(EEPROM.read(0)+EEPROM.read(1));
Serial.print("CambioDia: "  );Serial.println(EEPROM.read(7));
Serial.print("Error Tarjeta: " );Serial.println(EEPROM.read(8));
Serial.print("Pos Eprom: " );Serial.println(EEPROM.read(9));
Serial.print("PosCtrl: " );Serial.println(EEPROM.read(10));
if(!EEPROM.read(10)){PosVeleta=EEPROM.read(9);} // recupera la posicion del ultimo 
if(EEPROM.read(10)){PosVeleta=(EEPROM.read(9)+255);} //  registro en funcion del valor de PosCtrl (EEPROM (10))
Serial.print("Pos_Veleta: " );Serial.println(EEPROM.read(9));
Serial.print("Valor CambioDia: " );Serial.println(EEPROM.read(1024));
Serial.print("Valor Traspasado: " );Serial.println(EEPROM.read(1023));
/*
Serial.println("Lectura registros Veleta:");
for(int i=11;i<=299;i++){
  Serial.println(EEPROM.read(i));
}
Serial.println("Lectura registros CopiaSeguridad:");
for(int i=300;i<=588;i++){
  Serial.println(EEPROM.read(i));
}
Serial.println("Lectura Indiscriminada:");
for (int i=0;i<=1024;i++){
  Serial.println (EEPROM.read(i));
}
*/
// -------------------------------------------------------

}// CIERRA VOID

void loop() {
}


y con esto y un bizcocho ayer fue día veintiocho.

domingo, 25 de noviembre de 2018

Mapa del Sitio


- Bienvenid@  Saludo de Bienvenida.

- Pagina Principal  Pues eso.

- Lo mas Esencial  Espacio para iniciados.

      - Definiendo Arduino  ¿Que es? ¿Que hacer?.

      - Definiendo el Hardware Los Pines.


- El Entorno  Espacio para iniciados. Para saber un poco más.

      - Definiendo el Sketch  Estructura elemental del programa, Introducción a variables.

      - Funciones Basicas, void setup() y void loop()  Flujo natural del programa.

      - Control de Flujo  Código ASCII.


- Utilidades  Herramientas que nos serán necesarias. Tanto software como de hardware.

      - Introducción a la EEPROM   Manejo básico de la memoria interna no volátil (ROM).

      - La función definida map()   Mapeo y gestión de datos de una veleta simulada.

      - La tarjeta SD  Manejo básico de la tarjeta SD

      - LCD a la Vista  Manejo del LCD 16X2 I2C

      - Calcular el día de la Semana. Dos métodos para determinar el día de la semana


- Casos Prácticos  Programas individuales para los elementos de la Estación Meteorológica.

      - Veleta con Potenciometros FCP22E  Caso experimental para una veleta.

      - Veleta con Sensores de efecto Hall 49E  Proyecto definitivo para la Estación Meteorológica.

      - Anemómetro con sensor efecto Hall 44E402  Introducción.

      - Anemómetro, Programa Final  Proyecto definitivo.

      - Sensor de humedad y temperatura DHT22, DHT11  Proyecto definitivo.

      - Sensor de Presión y temperatura BMP-180  Proyecto definitivo.

      - Pluviómetro, con sistema de cazoletas basculantes --- EN CONSTRUCCIÓN

      - Processing, captura de datos desde la Arduino  ---EN CONSTRUCCIÓN


- Estación Meteorológica

      - Estación Meteorológica exclusivamente con Arduino ---EN CONSTRUCCIÓN

      - Estación Meteorológica Arduino + Processing ---EN CONSTRUCCIÓN


domingo, 11 de noviembre de 2018

La función definida map()

La función map() trabaja con dos rangos de datos, lo que hace es tomar un valor del primer rango y obtener su equivalente del segundo rango.
Su sintaxis es la siguiente:
map(var,valMinRangoEntrada,valMaxRangoEntrada,valMinRangoSalida,valMaxRangoSalida)

donde;
var; es la variable que recibe para ser mapeada
valMinRangoEntrada; es el valor mínimo que le entrará
valMaxRangoEntrada; es el valor máximo que le entrará
valMinRangoSalida; es el valor mínimo que ofrecerá
valMaxRangoSalida; es el valor máximo que ofrecerá

Sería conveniente que uno se instruya acerca de esta función para conocer más a fondo su funcionamiento, bien en el sitio oficial: Reference Language o en el blog de Manuel Delgado Crespo, que lo tiene en cristiano.

Podemos obtener el valor mapeado ingresándolo en una variable, ejemplo:
   int variable= map(var, 1,100,100,1) // esto invertiría el valor obtenido
o incluyéndolo como parámetro de una función o método que lo permita, ejemplo:
  Serial.println(map(var, 1,100,100,1));// imprimiría el valor sin guardarlo
o una función propia que requiera un parámetro para hacer alguna operación con el dato, ejem:
  funcionPropia(map(var, 1,100,100,1));// envía el valor mapeado a nuestra función
  int funcionPropia(int valor){
    valor=valor*PI;
    return valor;
 }

Bueno, una vez aclarado un tanto su funcionamiento vamos a darle una aplicación algo práctica. Para ello nos vamos a imaginar que hemos construido una veleta,  dados nuestros escasos recursos los queremos guardar en la EEPROM de nuestra Arduino para posteriormente almacenarlos y analizarlos, por ejemplo en una hoja de cálculo.
Bien, en nuestro teórico caso tenemos muy presente que en nuestra Arduino no podremos almacenar los datos recogidos minuto a minuto, ya que 24 horas X 60 minutos son 1440 registros (disponemos  de 1024 bytes) y en un futuro próximo ampliaremos nuestra esencial Estación.
Además, también sabemos que  nuestra veleta estará arrojando datos que oscilaran entre 0º y 360º,
a sabiendas que no podemos almacenar un valor superior a 255 en cada byte de nuestra EEPROM.
¡La cosa no pinta nada bien!
En este pésimo momento, quizás sería conveniente recordar una de las aptitudes mas esenciales que debe poseer un programador; La Tenacidad.

Una vez oxigenados, decidimos guardar los registros cada cinco minutos (288 registros), esto nos permitirá guardar en la EEPROM datos de tres sensores (que no es la panacea, pero hasta llegar ahí nos va a servir) y en referencia al valor recogido una posible solución sería mapear a bytes las lecturas antes de almacenarlas en la EEPROM y volver a mapearlas a Int al recuperarlas.

Acto seguido os dejo una captura del sketch que hace realidad esta idea, y como no puedo suministraros una veleta a cada uno de vosotr@s, en el sketch he incluido una nueva función; Random() esta función arroja un valor aleatorio dentro del rango especificado en sus parámetros. En este caso Random (muy útil para experimentar) simulará a la veleta, que arrojará valores comprendidos entre 255 y 360 (precisamente los que no podíamos almacenar).


Antes de colgar el Sketch hago algunas aclaraciones...
La primera columna impresa se corresponde con el valor inicial que ha generado random (o sea nuestra veleta) la segunda columna es el valor mapeado a byte y almacenado en la EEPROM, y la tercera columna es el valor leído de la EEPROM nuevamente mapeado para adaptarlo a nuestra necesidad, en esencia lo que debemos comparar son los datos de la primera y tercer columna, en un golpe de vista rápido detectamos que perdemos resolución (un máximo de 2º) ahora bien, cuando construyamos nuestra veleta descubriremos que el componente del viento oscila enormemente y en función de la intendidad, (unos 50º con viento moderado), con lo que esos 2º grados que podamos perder en las conversiones no van a representar ningún problema.

Y ahora si, ahí va el Sketch:


// By Santy, https://santyarduino.blogspot.com //
#include <EEPROM.h> //incluimos la libreria EEPROM para poder gestionar la ROM de Arduino
int dirVeleta;//creamos una variable de tipo entero capaz de almacenar valores superiores a 255
int valores[288]={};//creamos un array en tamaño equivalente a los datos a almacenar
void setup() {
  Serial.begin(9600);
  Serial.println();//Evita la primera linea impresa que suele salir un churro
  Serial.print("Original\t");Serial.print("guardado\t");Serial.println("Resultado");
  Serial.print("________\t"); Serial.print("________\t");Serial.println("_______");
  for(int i=0;i<288;i++){
    dirVeleta=random(255, 360);// simula los valores lanzados por la veleta y lo almacena en dirVeleta
    valores[i]=dirVeleta;// guardamos los valores en el array para las oportunas comprobaciones
    Serial.print(valores[i]);Serial.print("\t\t");// imprime por consola los valores guardados
    byte x = map(dirVeleta, 1, 360, 1, 255);//mapea el valor de dirVeleta y lo almacena en una variable tipo byte
    EEPROM.write(i,x);// como x es de tipo byte lo podemos guardar en la EEPROM sin problema
    Serial.print(EEPROM.read(i));Serial.print("\t\t");//imprime el valor anterior para las oportunas comprobaciones
    int z = map(EEPROM.read(i), 1, 255, 1, 360);//recupera el valor guardado en la EEPROM mapeandolo a la inversa
    Serial.println(z);//imprime el valor resultante para los oportunas comprobaciones
  }
}

void loop(){}

domingo, 28 de octubre de 2018

Veleta con Potenciometros FCP22E

Hola, a día de hoy(1/1/2018) acabo de sacar del horno un nuevo programa, en esta ocasión se trata de una veleta (la mas sofisticada que he parido) los sensores con los que vamos a trabajar son dos potenciómetros un tanto especiales. El FCP22E
Los potenciómetros son resistencias variables y son muy fiables para convertirlos en sensores de posición, ya que arrojan un valor fijo para una misma posición.
Tiene dos inconvenientes: una limitación mecánica (unos 300º) y su desgaste debido al contacto físico de su patilla interna con la espiral interna (la propia resistencia).
El potenciómetro FCP22E elimina el primer inconveniente ya que permite girar 360º (no tiene limitación física) y respecto al desgaste queda por demostrar cuanto puede durar.
Por la característica de rotación continua podemos confundirlo con un encoder, pero no lo es, un encoder además de poseer una resolución bajísima (los económicos) tienen el inconveniente de ponerse a cero al dejarlo sin suministro eléctrico. Además de esto su posición debe estar controlada continuamente por un programa, si el programa se cierra, la posición del encoder se inicia a cero.
Mi veleta actual funciona con un encoder, y como ya podeís imaginar, había que mejorar el sistema.
Aunque no todo lo que reluce es oro!, los potenciómetros de giro continuo tienen una franja de unos 30º-40º (según el modelo) en los que no lee nada, y ojalá esto fuera así, cuando el FCP22E se encuentra en esa zona muerta arroja valores aleatorios que nada tienen que ver con la realidad. Si bien cabe señalar que este problema se origine en la misma Arduino. Y para comprobar esto podemos hacer una simple prueba, que trataría de cargar un programa que lea un potenciómetro conectado por ejemplo al puerto A0 y no le conectamos nada al mismo, si imprimimos por el monitor Serial veremos que imprime valores aleatorios que cambian a cada momento, lo mismo que sucede cuando el potenciómetro se encuentra en su zona muerta.

Bueno entrando en materia... para poder tener una posición sea cual sea en torno a los 360º grados con estos potenciómetros, mi solución pasa por conectar dos de ellos, el primero leerá de 0º a 180º y el segundo de 180º a 360º.
La configuración física es un tanto entretenida, yo he puesto 3 poleas a 120º una para cada potenciómetro y otra para la veleta. El sistema de poleas simplifica la configuracion ya que al girar la polea de la veleta las otras dos giran solidariamente y en el mismo sentido, si se emplearan engranajes el segundo giraría en sentido contrario al primero y el tercero en sentido contrario al segundo. Lo mas entrenido es posicionar cada potenciómetro en su lugar correcto respecto a la polea de la veleta. (los gráficos ayudan a visualizar esto) Por su parte el programa no esta exento de complicaciones, aunque al final lo he resuelto con tres sencillas condicionales IF, de manera que se ha quedado bastante escueto.
Os dejo unas imágenes del proyecto, y el programa comentado hasta los ojos.
Por cierto las poleas y correa son de maquinas 3D, son económicas y fáciles de encontrar, aunque sería mejor emplear poleas no dentadas como una correa en V por ejemplo, las poleas dentadas me han entretenido bastante a la hora de posicionar los potenciómetros ya que el sistema dentado te obliga a que todas sus poleas coincidan en posición, esto lo he resuelto liberando las poleas de los ejes de los potenciómetros, para después situar el potenciómetro el lugar correcto y entonces fijar las poleas.
Acto seguido dejo un gráfico con cuatro posiciones críticas. La figura 1 muestra la configuración inicial; El potenciómetro CERO debe estar en su posición cero ó inicial y ésta debe coincidir con la veleta apuntando al Norte, el pot UNO debe tener su posición invertida, es decir su posición cero o de inicio debe comenzar cuando el pot CERO se encuentre a 180º. La segunda figura muestra el momento en que el pot UNO entra en su zona muerta, este momento debe estar gestionado en el programa, y en concreto se encuentra en las lineas 18,19 y 20 del mismo. En la figura 3 el pot UNO entra en funcionamiento, mientras que pot CERO deja de estarlo al superar los 180º. Y en la figura 3 se muestra el momento en que el pot UNO entra en su zona muerta, solo que en este caso al estar éste desactivado no es necesaria gestión alguna.




En la siguiente imagen os dejo la conexión eléctrica. es de lo mas sencillito, la patilla izquierda de los pots van a 5v. la derecha a GND y la central a su respectivo puerto analógico. A0 y A1 según el programa. Si invertimos las patillas de positivo a negativo lo que conseguimos es una lectura inversa, es decir al girar su perilla hacia la izquierda su resistencia se incrementará. Así que podemos conectarlas según nos convenga al instalar el grupo de sensores en la veleta.









Por cierto, estos diagramas los hago con Fritzing: http://fritzing.org/home/



Aquí abajo os dejo un foto-montaje para que tengáis una referencia real de como ha quedado. en la cuarta imagen se ve un circulo negro con una chapa dorada, que es el asiento del cojinete, para que la veleta gire lo mas cómoda posible. 



y más abajo el montaje definitivo...



A continuación os adjunto la última versión del sketch, fuertemente comentada:

/*    Central A0 (señal), Izquierdo 5V (+), Derecho 0V (-)   */
void setup() {
  Serial.begin(9600); // Inicia Serial
} // cierra setup

void loop() { // el programa cuenta con una unica instruccion
  Serial.println(calculaGrados()); // Imprime el resultado de la funcion calculaGrados()
} // cierra loop

// La siguiente funcion devuelve un valor de tipo entero a quien la llame, en este caso Serial.println linea 8
int calculaGrados (){ // funcion denominada calculaGrados(), esta funcion determina que potenciometro debe emplear y devuelve su valor.
  int vp0 = analogRead(A0); // introduce en la variable "vp0" el valor de la lectura del pin A0
  int vp1 = analogRead(A1);// introduce en la variable "vp1" el valor de la lectura del pin A1
 // La lectura del potenciometro nos arroja valores comprendidos entre 0 y 1023
 // y a nosotros nos interesan convertirlos de 0 a 360  
  vp0 = map(vp0, 0, 578, 1, 180); // pot CERO arroja el valor 578 en la mitad de su recorrido 
  vp1 = map(vp1, 453, 1000, 180, 360); // pot UNO, tiene su posicion invertida respecto a pot CERO
  // Es necesario leer los valores que arrojan nuestros potenciometros, y colocarlos y en la posicion idonea
  // para indicarle a la funcion Map los limites.    
  if (vp0 >= 1 && vp0 <= 180) { // si los valores leidos del pot CERO se encuentran entre
    return vp0; // el rango 1 y 180, se imprimiran los valores de pot CERO
  }  else {return vp1;} // en caso contrario, nos interesa imprimir los valores de pot UNO 
} // Cierra funcion 
He de confesar que tras unos días de trabajar con él he detectado que se cometían algunos errores en la lectura de los potenciómetros...posiblemente el Sketch está poco currado. Los errores se dan en dos pequeñas franjas justo antes de entrar en las zonas críticas correspondientes de cada potenciómetro. En cualquier caso este proyecto no es el mas indicado para una veleta, como he comentado con anterioridad. La Veleta definitiva a día de hoy esta funcionando con dos sensores de efecto Hall, pero esto es otra historia, que publicaré mas adelante.


domingo, 14 de octubre de 2018

Introducción a la EEPROM, memoria interna de Arduino



En esta entrada vamos a introducirnos en un espacio poco empleado de nuestra arduino, su ROM; la ROM es una memoria interna no volatil ó permanente; esto puede resultar muy útil para guardar información y no perderla en caso de reinicio ó apagado y así poder acceder a ella en cualquier momento. Pero esta característica tan interesante tiene limitaciones; y es que disponemos de tan sólo 1024 bytes de espacio, y nótese que NO he dicho 1Kbyte, aunque sea lo mismo. La explicación está en la profundidad, resolución ó amplitud; Me explico, El Atmega 328 de Arduino dispone de 1024 espacios de un byte cada uno; esto significa que en cada uno de los 1024 espacios solo podemos guardar valores iguales ó inferiores a un byte; (valores de hasta 255 en cualquiera de sus representaciones; números decimales, binarios, Hexa, ASCII, etc.. ) Las aplicaciones del uso de la ROM ó EEPROM (para Arduino) pueden ser muy diversas, en mi caso concreto hago uso de ella en mi estación metereológica para guardar los datos recogidos así como la fecha y hora. Debido a las limitaciones comentadas se requiere cierto ingenio para poder guardar una fecha ó una hora, y este es uno de los motivos para introducir este tema ahora, junto con el posterior (Función definida MAP) puesto que mi objetivo final es mostraros como he construido mi estación metereológica (lectura de Temperatura, Humedad, Presión Atmosférica, Componente del viento, Intensidad del Viento y Pluviometría). Bueno, regresando al asunto, podemos observar alguna curiosidad, a partir de la posición del array 255, donde su valor es 256 y el valor escrito en la EEPROM es 0; como he comentado anteriormente la EEPROM solo puede almacenar valores de hasta 255 (1 byte). Cuando superamos este valor nótese que el programa le da la vuelta al valor y comienza desde cero y de ahí hacia arriba, este comportamiento es muy importante tenerlo en cuenta. Si pretendemos guardar en la EEPROM un valor superior a un byte tenemos varios caminos, os propondré dos (en posteriores entradas) que requerirán de una gestión adicional (usar tantos bytes como sean necesarios ) y otro mas elegante pero con pérdida de resolución (con la función MAP).

El uso ó manejo de la EEPROM requiere incluir la librería EEPROM al principio del Sktech (Linea 2 del adjunto). La gestión de la EEPROM se hace con dos instrucciones:
- PARA ESCRIBIR; EEPROM.write(posición,valor); línea 32. Requiere dos parámetros; el primero es la posición en la cual queremos escribir, va de 0 a 1023, si indicamos 1024 escribirá en la posición 0 y así sucesivamente. El segundo parámetro es el valor que pretendemos escribir o su ubicación.
- PARA LEER; EEPROM.read(posición); lógicamente sólo requiere un parámetro, la posición que queremos leer.

Y ya, ahí va el Sketch:

 #include <EEPROM.h>  // incluye la libreria EEPROM para poder hacer uso de la ROM en nuestro Sketch
// A continuacion se declara un array de tipo "int" con 360 posiciones y se da un valor a cada una de ellas
int valores[360]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,
                  34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
                  64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,
                  94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,
                  118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,
                  141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,
                  164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,
                  187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,
                  210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,
                  233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,
                  256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,
                  279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,
                  302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,
                  325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,
                  348,349,350,351,352,353,354,355,356,357,358,359,360};                

void setup() {
   Serial.begin(115200);// indicamos una velocidad de comunicacion superior a la habitual(9600), para que imprima mas rapido
   Serial.println(); 
   for(int i=0;i<360;i++){  // bucle para el control de la impresion
      Serial.print("Pos. array n");Serial.write(186);Serial.print(" ");//Serial.write()imprime caracteres especiales
      if(i<10){Serial.print("  ");}// rellena dos espacios en blanco si el valor leido tiene solo un caracter
      if(i<100 && i > 9){Serial.print(" ");}// rellena un espacio en blanco si el valor leido tiene solo dos caracteres
      Serial.print(i);Serial.print(" ");// imprime el valor de "i", dado por el bucle y usado como indice
      Serial.print(" valor ->  [ ");
      if(valores[i]<10){Serial.print("  ");}// rellena dos espacios en blanco si el valor leido tiene solo un caracter
      if(valores[i]<100 && valores[i] > 9){Serial.print(" ");}// rellena un espacio en blanco si el valor leido tiene solo dos caracteres
      Serial.print(valores[i]);Serial.print(" ] ");// imprime el valor del array en la posicion "i" dada por el bucle
      EEPROM.write(i,valores[i]);// Escribe en posicion "i" (dada por el bucle)
      Serial.print(" valor en EEPROM ->  | ");
      if(EEPROM.read(i)<10){Serial.print("  ");}// rellena dos espacios en blanco si el valor leido tiene solo un caracter
      if(EEPROM.read(i)<100 && EEPROM.read(i) > 9){Serial.print(" ");}// rellena un espacio en blanco si el valor leido tiene solo dos caracteres
      Serial.print(EEPROM.read(i));Serial.println(" |");// lee e imprime en la posicion "i" (dada por el bucle) de la EEPROM
   }
}

for(int i=0;i<102;i++){  // bucle para el control de la impresion
void loop() {}