Acelerometro MMA7260QT de 3 ejes.
Gracias a un préstamo de Alex, tuve durante unos dÃas este sensor en mi poder.
Este sensor tiene 8 patillas:
- GND
- VCC
- G1, G2 - controlan las cuatro modos de medir del sensor.
- X, Y, Z - Salida analógica correspondiente a cada eje.
- SL - Permite encender y apagar el sensor.
Este sensor puede trabajar con cuatro rangos de valores: 1'5G, 2G, 4G y 6G. Lo que pasa es que la resolución del sensor es mayor cuanto menor es el rango que mide. Me explico, para 1'5G permite medir mas fino que si lo ponemos a 6G, lo que implica multiples usos para un mismo sensor: desde intentar definir un ángulo (ya que 1G equivale a la gravedad), hasta poder anular cual es la aceleración con que lanzamos un objeto (no muy recomendable, pero es posible). A nivel de voltaje, en 1'5G, 1G equivale a 800 mV, mientras que si nos encontramos a 6G, equivaldrÃa a 200 mV.
El sensor está calibrado para trabajar con valores de entre 2'2V y 3'6V, pero recomiendan 3'3V. No es que no funcione a más voltaje, que habrá un lÃmite, sino que ya no esta calibrado y no te asuguran que se comporte de manera lineal.
Dado que el sensor nos obliga a tomar 0 grados como la mitad de la tensión de alimentación, y a partir de ahi suma o resta a este valor según la dirección del movimiento. Por ejemplo para 1'5G y 3'3V los valores para -90º, 0º y 90º son, respectivamente, 850 mV, 1'65V y 2'45V.
Esto nos crea un problema. Las entradas analógicas de arduino tienen 10 bits de resolución para 5v, de manera predeterminada. Y si vamos a medir ángulos vemos que los valores teóricos van de 0'85V a 2'45V, que en la entrada analógica corresponderÃan a los valores comprendidos entre 173 y 501. 328 valores de los 1024 de partida.
Para esto arduino tiene una entrada que se llama AREF y que nos permite configurar la tensión sobre la que se aplica los 10 bits. En nuestro caso conectamos la entrada AREF a 3.3V y ahora el rango de antes 0'85V a 2'45V corresponderÃan a los valores 263 a 760. Y pasamos de 328 valores a cerca de 500. Realmente podriamos acotarlo mas y aumentar los valores... pero a mi ya me valÃa.
Tengo que hacer notar que el valor de 3'3V que conecte a la entrada AREF no lo hice de la salida de 3'3V del arduino... y no porque no lo intentara. Ya me dieron alguna explicación al respeto y es posible que la salida de 3'3V tenga una componente alterna de alta frecuencia que, en mi caso, llevaba a corto el arduino y dejaba de acerme caso. Asi que me fabrique un divisor de tensión entre 5 y 0, y 3.3 corresponde a 2 tercios. Más o menos lo que muestra es esquema siguiente.
Avisar, por ultimo, que si vamos a usar la entrada AREF debemos conocer la función analogRefererence. Esta función tiene tres estados DEFAULT, INTERNAL y EXTERNAL. El primero de los casos, default, no hace falta añadirla ya que el la que hace el ADC entre 0 y 5V. Internal se usa para cuando queremso que las medidas sean entre 0 y 1.1V. Y por ultimo, y la que usamos en nuestro caso, External, sirve para decirle cual es el valor máximo. Nunca debemos suministrar más de 5V a esta entrada, ni usarla sin la linea analogReference (EXTERNAL) en nuestro código.
Estas son unas imagenes de como quedo y como aparece en fritz.
Para calibrar las medidas, en vez de partir de las teóricas, me cree un pequeño programita que te dice la media de 1000 valores, y colocando el conjunto en posiciones concretas (por cada posición dos ángulos de 0º y uno de |90º|) sacas los valores máximo y mÃnimo para cada eje, asà como el valor para 0º.
long ac[3]={0,0,0}; void setup () { Serial.begin (9600); analogReference (EXTERNAL); } void loop () { long media[3]={0,0,0}; for (int j=0; j<1000; j++) { for (int i=0; i<3; i++) { analogRead (i); delay(30); ac[i] = analogRead (i); media[i] = ac[i] + media[i]; } } for (int i=0; i<3; i++) { ac[i] = media[i] /1000; } Serial.print (" "); Serial.print (ac[0]); Serial.print (" "); Serial.print (ac[1]); Serial.print (" "); Serial.println (ac[2]); delay(500); }
Y ahora ya os dejo con el video y el código del arduino.
#include <math .h>
#include < LiquidCrystal.h>
LiquidCrystal lcd(12, 13, 11, 5, 4, 3, 2);
float sen[19]={0.0,0.09,0.17,0.26,0.34,0.42,\
0.5,0.57,0.64,0.70,0.77,0.82,0.87,0.91,0.94,\
0.96,0.98,0.99,1}; //Tabla de senos para evitar andar a calcular angulos
int ac[3]={0,0,0}, media[3]={535,542,531}, \
rango[3]={235,238,249}; //Tomamos unos valores medios de pruebas anteriores.
void setup ()
{
Serial.begin(9600); //El serial para
//pruebas propias.
lcd.begin(20, 4);
pinMode(6, OUTPUT); //Controla el contraste del lcd
analogReference (EXTERNAL); //Punto importante. Si vamos a usar
//La patilla aref tenemos que poner
//esta linea.
analogWrite (6,32);
lcd.setCursor(0,0);
lcd.print ("Prueba acelerometro");
lcd.setCursor(0,1);
lcd.print (" X Y Z");
}
void tenval ( int *eje, float *raz) //Pasa el valor medido en la entrada
{ //analogica a un valor a partir del cual
for (int i=0; i<3;i++) //podemos calcular el angulo.
{
eje[i] = eje[i] - media[i];
raz[i]=(float)eje[i]/(float)rango[i];
}
}
void senang (float *rel, int *ang) //PodrÃamos calcular el arcsin(rel[i]
{ //pero al parecer hace perder muchos
for (int i=0; i<3; i++) //ciclos al microprocesador, entonces,
{ //aprovechando que vamos a usar medidas
for (int j=0; j<19; j++) //de 5 en 5 grados, partimos de un array
{ //con los valores de los senos y comparamos.
if ( abs(rel[i]) < sen[j] ) //Una simple multiplicacion con el indice
{ //del array nos da el angulo.
if (rel[i] < 0)
ang[i]=(1-j)*5; //El angulo asi es negativo
else
ang[i]=(j-1)*5;
if (ang[i] > 45) //Esto es un apaño, para ajustar los valores
ang[i]=ang[i]+5; //a los que debieran.
break;
}
}
}
}
void imprime (int *rad) //Saca los valores por la LCD
{
lcd.setCursor(0,2);
lcd.print (" ");
for (int i=0; i<3; i++)
{
lcd.setCursor (7*i+1,2);
lcd.print (rad[i]);
lcd.print (" ");
}
}
void loop ()
{
float valor[3];
int ang[3];
for (int i=0; i<3; i++) //Despues de leer en varios foros
{ //esta es la forma mas correcta
analogRead (i); //de leer por la entrada analógica
delay (30); //evitando errores en lecturas
ac[i]=analogRead (i); //por influencia de otras entradas
} //analogicas
/*Serial.print (" ");
Serial.print (ac[0]);
Serial.print (" ");
Serial.print (ac[1]);
Serial.print (" ");
Serial.println (ac[2]);*/
tenval (ac,valor);
tercersin(valor);
senang (valor,ang);
/*for (int i=0; i<3; i++)
{
Serial.print (valor[i]);
Serial.print (" ");
}
Serial.println ("");*/
imprime (ang);
}
//Despues de hacer un poco de "estudio" me decidÃ
//por descartar el ángulo mayor, ya que esta medida
//era mas susceptible de error a la hora de leer de
//la entrada analogica. Tened en cuenta que el sin(70º)
//es 0'94, el sin(75º) es 0'96, el sin(80º) es 0'98 y
//el sin(85º) es 0'99, tenemos un rango de 20 grados
//donde los valores estan muy proximos.
//En vez de usar esta medida del acelerómetro, lo que hacemos
//es partir de que sin(ejex)^2 + sin(ejey)^2 + sin(ejez)^2 = 1
//y obtenemos un valor del sin del angulo mayor mas proximo
//a la realidad que usando la entrada analogica.
void tercersin ( float *sen)
{
if ((sen[0] > sen[1]) && (sen[0] > sen[2]))
sen[0]= sqrt(1 - (sen[1])*sen[1] - (sen[2])*sen[2]);
else if ((sen[1] > sen[0]) && (sen[1] > sen[2]))
sen[1]= sqrt(1 - (sen[0])*sen[0] - (sen[2])*sen[2]);
else if ((sen[2] > sen[1]) && (sen[2] > sen[0]))
sen[2]= sqrt(1 - (sen[1])*sen[1] - (sen[0])*sen[0]);
}
Aún no hay trackbacks.





7 marzo, 2011 - 23:45
Buena explicación del acelerómetro, algo me dice que me va a venir bastante bien este post. ¿Qué será lo siguiente?¿Toca un giroscopio?
7 marzo, 2011 - 23:49
Ya me dijeron… A ver si cae la semana que viene, pero me dijeron que querias tener el robot para antes de San Pepe…
De todas, gracias por el sensor.
7 marzo, 2011 - 23:45
Muy buen post, ya no sólo por lo bien documentado que está sino por el trabajo de investigación que hubo detrás para hacer que funcionase de esta manera.
Un saludo
7 marzo, 2011 - 23:50
Y la vuelta de tuerca con los senos… Entretenimiento tuve….
28 abril, 2011 - 18:15
Hola estoy liado con el mismo acelerometro que tu pero tengo problemas. Aunque no mueva el acelerometro el resultado de la lectura es siempre diferente. He hecho diferentes pruebas con Aref y sin Aref pero de igual siguen saliendo diferentes números aunque no mueva el acelerometro. Inspirado en el tuyo he hecho un pequeño programa que te lo pongo a continuación:
long MatrizAngulo[3]={0,0,0};
int i;
void setup ()
{
Serial.begin (9600);
}
void loop ()
{
for (i=0;i<3;i++){
analogRead (i);
delay(30);
MatrizAngulo[i] =analogRead (i);
}
Serial.print ("El angulo es x:");
Serial.println (MatrizAngulo[0]);
}
Los resultados que me da son estos que no tienen logica si no vuevo el acelerometro.
El angulo es x:364
El angulo es x:329
El angulo es x:282
El angulo es x:365
El angulo es x:222
El angulo es x:396
El angulo es x:209
El angulo es x:404
El angulo es x:198
El angulo es x:410
El angulo es x:190
El angulo es x:414
El angulo es x:182
Sin a Ref los resultados tienen que estar entre 173 y 501 supongo que si me da 337 singnifica que esta a 0 grados y si da 501 90º y 173 -90º. Haber si se te ocurre que puede ser. SAlu2
15 mayo, 2011 - 12:47
Hola, perdón por el retraso en la respuesta.
A mà me pasaba algo parecido. Al parecer el arduino tiene un problema a la hora de leer entradas analogicas: La lectura se ve influenciada por el cambio de puerto.
Según entendÃ, al cambiar de entrada, en la lectura, tienes que leerla dos veces… Te reescribo el codigo de arriba, la parte que viene a cuento:
for (int i=0; i<3; i++) { analogRead (i); delay (30); ac[i]=analogRead (i); }No pude pegarlo como esta arriba asi que pongo aquà el comentario:
Despues de leer en varios foros esta es la forma mas correcta de leer por la entrada analógica evitando errores en lecturas por influencia de otras entradas analogicas
Ademas, suelo poner unos condensadores de lenteja para estabilizar la señal. Los conecto de la entrada analogica a masa.
Aún con todo esto, vas a notar que los valores oscilan, pero los cambios son menores.
15 mayo, 2011 - 18:39
Al final el problema era que el pin SL (sleep) si no lo ponÃa a “HIGH” no respondÃa correctamente. Haciendo pruebas si que vi que la primera lectura siempre la hace mal y la segunda la hace bien. Para solucionar ese problema hace falta el código que has puesto. Salu2