martes, 6 de octubre de 2015

[Lenguaje Arduino 10] Instrucciones matemáticas, trigonométricas y de pseudoaleatoriedad

El lenguaje Arduino tiene de una serie de instrucciones matemáticas y de pseudoaleatoriedad que nos pueden venir bien en algún proyecto. Son las siguientes:
  • abs(): devuelve el valor absoluto de un número ingresado como parámetro (el cual puede ser entero o decimal). O sea, si ese número es positivo (o 0), lo devuelve sin alterar su valor; si es negativo, lo devuelve “convertido en positivo”. Por ejemplo, 3 es el valor absoluto de 3 también de -3.
  • min(): devuelve el mínimo de dos números ingresados como parámetros (los cuales pueden ser tanto enteros o decimales).
  • max(): devuelve el máximo de dos números ingresados como parámetros (los cuales pueden ser tanto enteros como decimales).
Muchas veces se utilizan las funciones min() y max() para restringir el valor mínimo o máximo que puede tener una variable cuyo valor proviene de un sensor. Por ejemplo, si tenemos una variable llamada “sensVal” que obtiene su valor de un sensor, la línea sensVal=min(sensVal, 100); se asegura que dicha variable no va a tener nunca un valor más pequeño de 100, a pesar de que ese sensor reciba en un momento dado valores más pequeños. En este sentido, nos puede ser de mayor utilidad usar otra instrucción más específica llamada constrain(), la cual sirve para contener un valor determinado entre dos extremos mínimo y máximo:
  • constrain(): Se ingresa un valor o variable en el primer parametro y recalcula su valor (llamémosle “x”) dependiendo de si está dentro o fuera del rango delimitado por los valores ingresados en el segundo y tercer parámetro (llamémoslos “a” y “b” respectivamente, donde “a” siempre ha de ser menor que “b”). Los tres parámetros pueden ser tanto enteros como decimales. En otras palabras:
En este caso deberiamos ingresar constain(x,a,b):
Si “x” está entre “a” y “b”, constrain() devolverá “x” sin modificar.
Si “x” es menor que “a”, constrain() devolverá “a”
Si “x” es mayor que “b”, constrain() devolverá “b”

Una instrucción un poco más compleja que las anteriores, es la instrucción map(). Esta instrucción es utilizada en muchos proyectos para adecuar las señales de entrada obtenidas por diferentes sensores a un rango numérico óptimo para trabajar. Veremos varios ejemplos de su uso en algunos tutoriales arduino.
  • map(): modifica un valor ingresado como primer parámetro, el cual inicialmente está dentro de un rango (delimitado con su mínimo –segundo parámetro– y su máximo –tercer parámetro–) para que esté dentro de otro rango (con otro mínimo –cuarto parámetro– y otro máximo –quinto parámetro–) de forma que la transformación del valor sea lo más proporcional posible. Esto es lo que se llama “mapear” un valor: los mínimos y los máximos del rango cambian y por tanto los valores intermedios se adecúan a ese cambio. Todos los parámetros son de tipo “long”, por lo que se admiten también números enteros negativos, pero no números decimales: si aparece alguno en los cálculos internos de la instrucción, éste será truncado. El valor devuelto por esta instrucción es precisamente el valor mapeado.
Ejemplo 13
El siguiente código muestra el uso de la instrucción map().

void setup(){
        Serial.begin(9600);
        Serial.println(map(0,0,100,200,400));
        Serial.println(map(25,0,100,200,400));
        Serial.println(map(50,0,100,200,400));
        Serial.println(map(75,0,100,200,400));
        Serial.println(map(100,0,100,200,400));
/*El valor puede estar fuera de los rangos,pero igualmente se mapea de la forma conveniente */
        Serial.println(map(500,0,100,200,400));
        Serial.println(map(200,0,1023.0,0,255.0));
} void loop(){}

Se puede observar a la salida del “Monitor serial”, se comprueba cómo el valor mínimo del rango inicial (0) se mapea al valor mínimo del rango final (200), que el valor 25 (una cuarta parte del rango total inicial) se mapea al valor 250 (una cuarta parte del rango total final), que el valor 50 (la mitad del rango total inicial) se mapea al valor 300 (la mitad del rango total final), que el valor 75 (tres cuartas partes del rango total inicial) se mapea al valor 350 (tres cuartas partes del rango total final), que el valor máximo del rango inicial (100) se mapea al valor máximo del rango final (400) y que un valor fuera del rango inicial (500) se mapea proporcionalmente a otro valor fuera del rango final (1200).

Observar en el segundo caso que los valores mapeados no se restringen dentro del nuevo rango porque a veces puede ser útil trabajar con valores fuera de rango. Si se desea acotarlos, se debe usar la instrucción constrain() o antes o después de map().

Ver también que los mínimos de los dos rangos (el actual y el modificado) pueden tener en realidad un valor mayor que los máximos. De esta forma, se pueden revertir rangos de números. Por ejemplo, map(x,1, 50, 50 ,1); invertiría los valores originales: el 1 pasaría a ser el 50, el 2 el 49… y el 50 pasaría a ser el 1, el 49 el 2…

Para los interesados en saber que calculos realizar matemáticamente, la instrucción map()realiza este cálculo: xmapeado =(x– minactual)*(maxfinal – minfinal) / (maxactual – minactual) + minfinal donde ”x” es el valor a mapear,”minactual” es el valor mínimo del rango actual, “maxactual” es el valor máximo del rango actual, “minfinal” es el valor mínimo del rango final y “maxfinal” es el valor máximo del rango final.

El lenguaje Arduino también dispone de instrucciones relacionadas con potencias:
  • pow(): devuelve el valor resultante de elevar el número ingresado como primer parámetro (la “base”) al número ingresado como segundo parámetro (el “exponente”, el cual puede ser incluso una fracción). Por ejemplo, si ejecutamos resultado = pow (2,5); la variable “resultado” valdrá 32 (25). Ambos parámetros son de tipo “float”, y el valor devuelto es de tipo “double”.
  • sq(): Si se quiere calcular el cuadrado de un número (es decir, el resultado de multiplicar ese número por sí mismo, además de utilizar la instrucción pow() poniendo como exponente el número 2, se puede utilizar una instrucción específica que es sq(), cuyo único parámetro es el número (de cualquier tipo) que se desea elevar al cuadrado y cuyo resultado se devuelve en forma de número de tipo double”.
  • sqrt(): devuelve la raíz cuadrada del número ingresado como parámetro (que puede ser tanto entero como decimal). El valor devuelto es de tipo “double”.
También podemos utilizar instrucciones trigonométricas. Si no sabes que son las funciones trigonométricas las puedes estudiar en “Función trigonométrica”.
  • sin(): devuelve el seno de un ángulo especificado como parámetro en radianes. Este parámetro es de tipo “float”. Su retorno puede ser un valor entre -1 y 1 y es de tipo “double”.
  • cos(): devuelve el coseno de un ángulo especificado como parámetro en radianes. Este parámetro es de tipo “float”. Su retorno puede ser un valor entre -1 y 1 y es de tipo “double”.
  • tan(): devuelve la tangente de un ángulo especificado como parámetro en radianes. Este parámetro es de tipo “float”. Su retorno puede ser un valor entre -∞ y ∞ y es de tipo “double”.
También podemos utilizar números pseudoaleatorios en nuestros sketches Arduino.
  • randomSeed(): inicializa el generador de números pseudoaleatorios. Se suele ejecutar en la sección “setup()” para poder utilizar a partir de entonces números pseudoaleatorios en nuestro sketch. Esta instrucción tiene un parámetro de tipo “int” o “long” llamado “semilla” que indica el valor a partir del cual empezará la secuencia de números. Semillas iguales generan secuencias iguales, así que interesará en múltiples ejecuciones de randomSeed() utilizar valores diferentes de semilla para aumentar la aleatoriedad. También nos puede interesar a veces lo contrario: fijar la semilla para que la secuencia de números aleatorios se repita exactamente. No tiene ningún valor de retorno.
  • random(): una vez inicializado el generador de números pseudoaleatorios con randomSeed(), esta instrucción retorna un número pseudoaleatorio de tipo “long” comprendido entre un valor mínimo (especificado como primer parámetro –opcional–) y un valor máximo (especificado como segundo parámetro) menos uno. Si no se especifica el primer parámetro, el valor mínimo por defecto es 0. El tipo de ambos parámetros puede ser cualquiera mientras sea entero.
Ejemplo 14
Un ejemplo de uso de las instrucciones pseudoaleatorias sería el siguiente sketch. Se puede observar que se tiene una semilla fija y que se generarán números pseudoaleatorios entre 1 y 9. Observar que, al tener la semilla fija, si se abre el “Serial monitor” en diferentes ocasiones (suponiendo que usamos una placa UNO), en todas ellas la secuencia de números será exactamente la misma:

void setup(){
        Serial.begin(9600);
        randomSeed(100);
}

void loop(){
        Serial.println(random(1,10));
        delay(1000);
}


Además de las instrucciones matemáticas anteriores, el lenguaje Arduino dispone de varios operadores aritméticos, algunos de los cuales ya han ido apareciendo en algunos Sketchs. Estos operadores funcionan tanto para números enteros como decimales y son los siguientes:

Operadores aritméticos
+ Operador suma
- Operador resta
* Operador multiplicación
/ Operador división
% Operador módulo

El operador módulo sirve para obtener el resto de una división. Por ejemplo: 27%5=2 . Es el único operador que no funciona con “floats”.

Comentario final: posiblemente, las funciones matemáticas del lenguaje Arduino puede parecer escasas comparadas con las de otros lenguajes de programación. Pero esta un poco alejado de la realidad: esto se debe a que el lenguaje Arduino no deja de ser un “maquillaje” del lenguaje C/C++, en realidad tenemos a nuestra disposición la mayoría de funciones matemáticas (y de hecho, prácticamente cualquier tipo de función) que ofrece el lenguaje C/C++. Concretamente, podemos utilizar todas las funciones listadas en la referencia online de “avr-libc” (http://www.nongnu.org/avr-libc/user-manual) . Asi que, si necesitamos calcular el exponencial de un número decimal “x”, podemos usar exp(x);. Si queremos calcular su logaritmo natural, podemos usar log(x);. Si queremos calcular su logaritmo en base 10, podemos usar log10(x);. Si queremos calcular el módulo de una división de dos números decimales, podemos usar fmod(x,y); etc.




No olvides visitar la pestaña Tutoriales arduino y Lenguaje arduino, donde podrás encontrar la lista de tutoriales que he hecho hasta el momento. Como siempre muchas gracias por visitar mi blog y si tienen alguna consulta o consejo puede comentarlo por medio de este mismo blog.

1 comentario:

  1. en el caso de las funciones trigonometricas que el resultado me lo da en radianes ,como lo puedo transformar en decimales?

    ResponderBorrar