11- Las Matemáticas en Arduino

A la hora de programar en entorno Arduino, a menudo tenemos que hacer operaciones matemáticas. Por ejemplo, calcular la distancia a la que se encuentra un objeto gracias a la fórmula de la velocidad del sonido en el aire. Vamos a explicar en esta lección algunas nociones de Aritmética en Arduino.


Las operaciones matemáticas en Arduino.

Una de las principales funciones del microcontrolador es realizar cálculos, tanto con números directamente como con los valores de variables. Comencemos nuestra inmersión en el mundo de las matemáticas con las acciones más simples:

  • = asignación
  • % resto de la división
  • * multiplicación
  • / division
  • + adición
  • – resta

Veamos un ejemplo simple:

int a = 10;
int b = 20;
int c = a + b; // c = 30
int d = a * b; // d = 200
// esto también es posible
d = d / a;     // d = 20
c = c * d;     // c = 600

Con respecto a las dos últimas líneas del ejemplo, cuando una variable está involucrada en el cálculo de su propio valor: también hay operadores compuestos que acortan el registro:

  • + =  adición compuesta: a + = 10 equivalente a: a = a + 10
  • – =  resta compuesta: a – = 10 equivalente a: a = a – 10
  • * = multiplicación compuesta: a * = 10 equivalente a: a = a * 10
  • / =  división compuesta: a / = 10 equivalente a: a = a / 10
  • % = suma el resto de la división: a% = 10 equivalente a: a = a + a% 10

Utilizándolos, puede acortar la grabación de las dos últimas líneas del ejemplo anterior:

d / = a;     // (equivalente a d = d / a) d = 20
c * = d;     // (equivalente a c = c * d) c = 600

Muy a menudo en programación, se usa la suma o resta de uno, (incremento) para lo cual también hay una breve notación:

  • ++ (más más) incremento: a ++ equivalente a: a = a + 1
  • – – (menos menos) decremento: a– equivalente a: a = a – 1

El orden en el que se escribe el incremento es muy importante: post-incremento var ++ devuelve el valor de var antes de que se ejecutara el incremento. La operación de preincremento ++ var devuelve el valor de una variable ya modificada. Ejemplo:

byte a, b;
a = 10;
b = a ++;
// a obtendrá el valor 11
// b se establecerá en 10
a = 10;
b = ++ a;
// a obtendrá el valor 11
// ¡b se establecerá en 11!

Como se mencionó en la lección anterior, es recomendable inicializar las variables, de lo contrario pueden tener un valor aleatorio y se obtendrá un resultado impredecible en las operaciones matemáticas. Si una variable al inicio de la ejecución del programa, o después de una llamada de función (variable local) debe tener un valor de 0, inicialícela como 0.

byte a;     // declarar sin inicialización
byte b = 0; // declarar y asignar 0
a ++; // resultado impredecible
b ++; // definitivamente resultará en 1

Orden de Calculo.

El orden de evaluación de las expresiones obedece a las reglas matemáticas habituales: primero se realizan las acciones entre paréntesis, luego la multiplicación y la división, y finalmente la suma y la resta.


Velocidad de computación.

Los cálculos realizados toman algún tiempo del procesador, depende del tipo de acción y del tipo de datos con los que se realiza la acción. Debe comprender que no todas las acciones en todos los casos pasan tanto tiempo como se describirá más adelante: el compilador intenta optimizar los cálculos tanto como sea posible, ya que lo hace, puede intentar buscar en Internet. Los cálculos optimizados toman un tiempo insignificante en comparación con los no optimizados. Es difícil decir si se optimizará un solo cálculo en su código, por lo que siempre debe prepararse para lo peor y saber cómo hacerlo mejor. A saber:

  • Arduino (en AVR) no tiene soporte de «hardware» para cálculos de punto flotante (float), y estos cálculos se realizan usando herramientas separadas por software y toman mucho más tiempo que con tipos enteros
  • Cuanto más «masivo» sea el tipo de datos, más tiempo se tarda en calcular; las acciones con variables de 1 byte se realizan mucho más rápido que con las de 4 bytes
  • La división (y la búsqueda del resto de la división) se realiza en módulos separados (como operaciones flotantes), por lo que esta operación lleva más tiempo que la suma / resta / multiplicación. Para optimizar la velocidad de los cálculos, tiene sentido reemplazar la división por multiplicación por el número recíproco (incluso un float)

Resumiendo todo lo anterior, quiero mostrarles una tabla de este tipo, que muestra el tiempo de los cálculos de varios tipos de datos que no están optimizados por el compilador, el tiempo se indica en microsegundos (μs) para una frecuencia de reloj de 16 MHz.. La operación de división también incluye al resto de la división, %:

Tipo de datosTiempo de ejecución, μs
Adición y sustracciónMultiplicaciónDivisión, resto
int8_t0.440,62514.25
uint8_t0.440,6255.38
    
int16_t0,891.37514.25
uint16_t0,891.37513.12
int32_t1,756.0638,3
uint32_t1,756.0637,5
    
float8.1251031,5
Tiempo de los cálculos de varios tipos de datos.

Esta información se proporciona únicamente con fines informativos y no necesita preocuparse por la velocidad de los cálculos, ya que la mayoría de ellos estarán optimizados.  Para proyectos simples sin miles de cálculos, honestamente, no se preocupe =)


Desbordamiento de variable.

Desde que comenzamos a hablar de acciones que aumentan o disminuyen el valor de una variable, ¿vale la pena pensar en qué pasará con la variable si su valor se sale del rango aceptable? Aquí todo es bastante simple: en caso de un desbordamiento hacia arriba, el valor máximo de la variable se recorta del nuevo valor grande y solo tiene el resto. A modo de comparación, imaginemos una variable como un cubo. Asumiremos que al verter agua y llenar en exceso el balde, diremos detenerse, verter toda el agua y agregar el resto. Entonces, con la variable, lo que queda permanecerá. Si el desbordamiento ocurre varias veces, vaciaremos nuestro “balde” varias veces y aún dejaremos el resto. Otro buen ejemplo es la taza pitagórica…. El desbordamiento en la dirección opuesta, es decir en caso negativo, vierta el agua, asumiremos que el cubo está completamente lleno. Sí, es cierto =) Veamos un ejemplo:

/  tipo de datos byte
// min. valor 0
// máx. valor 255
byte val = 255;
// val se convierte en 0 aquí
val ++;
// y luego de cero se convierte en 246
val - = 10;
// ¡Desbordamiento! Restos 13
val = 525;
// y viceversa: val es 236
val = -20;

Números mayores, int y long.

Para la suma y la resta, el tipo predeterminado es long (4 bytes), pero para la multiplicación y la división se usa int(2 bytes), lo que puede dar lugar a resultados impredecibles. Si, al multiplicar números, el resultado excede 32’768, se calculará incorrectamente. Para corregir la situación, debe escribir (tipo de datos) antes de la multiplicación, lo que obligará al MC a asignar memoria adicional para el cálculo (por ejemplo( long ) 35 * 1000). También hay modificadores que hacen aproximadamente lo mismo.

  • u o U – convertirá a formato unsigned int (de 0 a 65’535). Ejemplo:36000u
  • l o L – convertirá a formato long (-2 147 483 648 … 2 147 483 647). Ejemplo: 325646L
  • ul o UL – convertirá a formato unsigned long (de 0 a 4.294.967.295). Ejemplo: 361341ul

Veamos cómo funciona esto en la práctica:

val long ;
val = 2.000.000.000 + 6.000.000;        // calcular correctamente (desde la suma)
val = 25 * 1000;                   // calcular correctamente (multiplicación, menos de 32'768)
val = 35 * 1000;                   // calculará INCORRECTO! (la multiplicación es mayor que 32'768)
val = ( long ) 35 * 1000;             // calculará correctamente (asignará memoria (larga))
val = 35 * 1000L;                  // calcular correctamente (modificador L)
val = 35 * 1000u;                  // calculará correctamente (modificador u)
val = 70 * 1000u;                  // calcula INCORRECTO (modificador u, resultado> 65535)
val = 1000 + 35 * 10 * 100;        // calculará INCORRECTO! (en multiplicación por 32'768)
val = 1000 + 35 * 10 * 100L;       // ¡calcula correctamente! (modificador L)
val = ( long ) 35 * 1000 + 35 * 1000; // calculará INCORRECTO! La segunda multiplicación lo estropea todo
val = ( long ) 35 * 1000 + ( long ) 35 * 1000; // calculará correctamente (asignará memoria (larga))
val = 35 * 1000L + 35 * 1000L;     // calcular correctamente (modificador L)

Trabajando en coma flotante.

Arduino admite números de punto flotante (decimales). Este tipo de datos no tiene soporte de hardware, pero está implementado en software, por lo que los cálculos con él toman varias veces más tiempo que con un tipo entero, puede ver esto en la tabla anterior. Además de la computación lenta, el soporte para trabajar con flotantes ocupa memoria, porque se implementa como una «biblioteca». El uso de operaciones matemáticas con float ( * / + – ) agrega aproximadamente 1000 bytes a la memoria flash, de una vez, simplemente conecte la herramienta para realizar la acción.

Arduino admite tres tipos de entrada de punto flotante:

Tipo de DatoEjemploLo que es igual
Decimal20,520,5
Científico2.34E52,34 * 10 ^ 5 o 234000
Ingenieria67e-1267 * 10 ^ -12 o 0,000000000067

Existe una peculiaridad con los cálculos: si la expresión no contiene números float, entonces los cálculos tendrán un resultado entero (la parte fraccionaria se corta). Para obtener el resultado correcto, debe escribir la transformación (float) antes de la acción, use números float o variables float. También hay un modificador f que solo se puede aplicar a números float. No tiene sentido, pero se puede encontrar tal registro. Veamos:

float val;             // además asignaremos 100/3, esperamos el resultado 33.3333
val = 100/3;         // calcula INCORRECTO (resultado 33.0)
int val1 = 100;        // variable entera
val = val1 / 3;        // calcula INCORRECTO (resultado 33.0)
float val2 = 100;      // variable flotante
val = val2 / 3;        // contará correctamente (hay una variable float)
val = ( float ) 100/3 ;  // calcular correctamente (especificar (float))
val = 100,0 / 3;       // cuenta correctamente (hay un float)
val = 100 / 3,0 f;      // calculará correctamente (hay un float y un modificador)

En el asignamiento de números float a un tipo de datos entero, la parte fraccionaria se trunca. Si desea redondeo matemático, debe usarlo aparte:

int val;
val = 3,25 ;        // val se convierte en 3
val = 3,92 ;        // val se convierte en 3
val = round ( 3,25 ) ; // val se convierte en 3
val = round ( 3,92 ) ; // val se convierte en 4

El siguiente punto importante: debido a la peculiaridad del modelo de «números de punto flotante», los cálculos a veces se realizan con un pequeño error.

float val2 = 1 .1 - 1.0 ;
// val2 == 0.100000023 !!!
float val4 = 1,5 - 1,0 ;
// val4 == 0.500000000

Parece que, val2 debería volverse parejo 0,1 después de la resta, pero en el octavo dígito hubo un error. Tenga mucho cuidado al comparar números float, especialmente con operaciones estrictas == , > = y <= : el resultado puede ser incorrecto e ilógico.


Funciones matemáticas en math.h

Función Descripción
cos ( x ) Coseno (radianes)
sin( x ) Seno (radianes)
tan( x )Tangente (radianes)
fabs ( x )Módulo para números flotantes
fmod ( x, y ) Resto de x por y para float
modf ( x, * iptr ) Devuelve la parte fraccionaria, almacena el entero en iptr
modff ( x, * iptr ) Lo mismo, pero para float
sqrt ( x ) Raíz cuadrada
sqrtf ( x ) Raíz cuadrada para números flotantes
cbrt ( x ) Raíz cúbica
hypot ( x, y )Hipotenusa (raíz (x * x + y * y))
square( x )Cuadrado (x * x)
floor( x )Redondea al número entero más cercano
ceil( x )Redondea al número entero más cercano
frexp ( x, * pexp )
ldexp ( x, exp ) x * 2 ^ exp
exp ( x ) Exponente (e ^ x)
cosh ( x ) Coseno hiperbólico (radianes)
sinh ( x ) Seno hiperbólico (radianes)
tanh ( x ) Tangente hiperbólica (radianes)
acos ( x ) Arccosine (radianes)
asin ( x ) Arcoseno (radianes)
atan ( x ) Arco tangente (radianes)
atan2 ( y, x ) Arco tangente (y / x) (encuentra el cuadrante en el que se encuentra el punto)
log( x )Logaritmo natural de x (ln (x))
log10 ( x ) Logaritmo decimal de x (log_10 x)
pow ( x, y ) Grado (x ^ y)
isnan ( x ) Comprobando nan (1 sí, 0 no)
isinf ( x ) Regreso 1 si x + infinito, 0 si no
isfinite ( x )Devuelve distinto de cero solo si el argumento es finito
copysign ( x, y ) Devuelve con signo x y (el signo es + -)
signbit ( x )Devuelve distinto de cero solo si _X es negativo
fdim ( x, y ) Devuelve la diferencia entre xey si x es mayor que y, 0 en caso contrario
fma ( x, y, z ) Devuelve x * y + z
fmax ( x, y ) Devuelve el mayor de los números
fmin ( x, y ) Devuelve el menor de los números
trunc ( x ) Devuelve la parte entera de un número con punto decimal.
round( x )Redondeo matemático
lround( x )Redondeo matemático (para números grandes)
lrint( x )Redondea el valor de punto flotante especificado al valor entero más cercano utilizando el modo y la dirección de redondeo actuales
Funciones matemáticas de la librería math.h

Funciones Arduino.

FunciónValor
min ( a, b )Devuelve el menor de los números a y b
max ( a, b )Devuelve el mayor de los números
abs ( x )Valor absoluto de un número
constrain ( val, min, max )Límite de rango de números val Entre min y max
map ( val, min, max, newMin, newMax )Convertir rango de números val (de min antes de max) a un nuevo rango (desde newMin antes de newMax). val = map ( analogRead ( 0 ) , 0, 1023, 0, 100 ) ;– obtenemos los valores 0-100 en lugar de 0-1023 de la entrada analógica. ¡Solo funciona con números enteros!
round( x )Redondeo matemático
radians (deg)Convertir grados a radianes
degrees( rad )Conversión de radianes a grados
sq( x )Número cuadrado
Funciones Arduino

Constantes Matemáticas.

ConstanteValorDescripción
INT8_MAX127Max. valor char, int8_t
UINT8_MAX255Max. valor de byte, uint8_t
INT16_MAX32767Max. valor int, int16_t
UINT16_MAX65535Max. valor int sin signo, uint16_t
INT32_MAX2147483647Max. valor largo, int32_t
UINT32_MAX4294967295Max. valor largo sin signo, uint32_t
M_E2.718281828Número e
M_LOG2E1.442695041log_2 e
M_LOG10E0,434294482log_10 e
M_LN20.693147181log_e 2
M_LN102.302585093log_e 10
M_PI3.141592654Pi
M_PI_21.570796327pi / 2
M_PI_40,785398163pi / 4
M_1_PI0.3183098861 / pi
M_2_PI0,6366197722 / pi
M_2_SQRTPI1.1283791672 / raíz (pi)
M_SQRT21.414213562raíz (2)
M_SQRT1_20,7071067811 / raíz (2)
NAN__builtin_nan («»)NAN
INFINITY__builtin_inf ()infinito
Pi3.141592654Pi
HALF_PI1.570796326medio Pi
TWO_PI6.283185307dos pi
EULER2.718281828Número e de Euler
DEG_TO_RAD0,01745329Conversión grados constante a rad
RAD_TO_DEG57.2957786Conversión radianes a grados
Constantes matemáticas.

Deja un comentario