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 datos | Tiempo de ejecución, μs | ||
Adición y sustracción | Multiplicación | División, resto | |
int8_t | 0.44 | 0,625 | 14.25 |
uint8_t | 0.44 | 0,625 | 5.38 |
int16_t | 0,89 | 1.375 | 14.25 |
uint16_t | 0,89 | 1.375 | 13.12 |
int32_t | 1,75 | 6.06 | 38,3 |
uint32_t | 1,75 | 6.06 | 37,5 |
float | 8.125 | 10 | 31,5 |
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 Dato | Ejemplo | Lo que es igual |
Decimal | 20,5 | 20,5 |
Científico | 2.34E5 | 2,34 * 10 ^ 5 o 234000 |
Ingenieria | 67e-12 | 67 * 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 Arduino.
Función | Valor |
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 |
Constantes Matemáticas.
Constante | Valor | Descripción |
INT8_MAX | 127 | Max. valor char, int8_t |
UINT8_MAX | 255 | Max. valor de byte, uint8_t |
INT16_MAX | 32767 | Max. valor int, int16_t |
UINT16_MAX | 65535 | Max. valor int sin signo, uint16_t |
INT32_MAX | 2147483647 | Max. valor largo, int32_t |
UINT32_MAX | 4294967295 | Max. valor largo sin signo, uint32_t |
M_E | 2.718281828 | Número e |
M_LOG2E | 1.442695041 | log_2 e |
M_LOG10E | 0,434294482 | log_10 e |
M_LN2 | 0.693147181 | log_e 2 |
M_LN10 | 2.302585093 | log_e 10 |
M_PI | 3.141592654 | Pi |
M_PI_2 | 1.570796327 | pi / 2 |
M_PI_4 | 0,785398163 | pi / 4 |
M_1_PI | 0.318309886 | 1 / pi |
M_2_PI | 0,636619772 | 2 / pi |
M_2_SQRTPI | 1.128379167 | 2 / raíz (pi) |
M_SQRT2 | 1.414213562 | raíz (2) |
M_SQRT1_2 | 0,707106781 | 1 / raíz (2) |
NAN | __builtin_nan («») | NAN |
INFINITY | __builtin_inf () | infinito |
Pi | 3.141592654 | Pi |
HALF_PI | 1.570796326 | medio Pi |
TWO_PI | 6.283185307 | dos pi |
EULER | 2.718281828 | Número e de Euler |
DEG_TO_RAD | 0,01745329 | Conversión grados constante a rad |
RAD_TO_DEG | 57.2957786 | Conversión radianes a grados |