Читать книгу: «Arduino. Trucos y secretos.», страница 7

Шрифт:

25. Rellenar un array de números aleatorios

Para generar un array de números aleatorios basta con definir el array que hay que rellenar y, después, asignar los valores, uno tras otro, llamando a la función random(). Este es el fragmento de código que hay que utilizar para rellenar un array de diez posiciones con números aleatorios entre 0 y 99:

//defino un array con 10 posiciones

int seq[10];

//inicializo random

randomSeed(analogRead(A0));

//recorro el array

for (int i = 0; i < sizeof(seq)/sizof(int); i++){

//asigno los valores aleatorios

seq[i] = random(0,100);

}

Al detalle

Arduino dispone de la función random() con la cual se pueden generar números enteros pseudoaleatorios: random() utiliza un algoritmo para sortear los números, razón por la cual se denominan pseudoaleatorios. ¡Si no se inicializa el algoritmo, los números se presentarán siempre en el mismo orden! El mejor método para inicializar el algoritmo es utilizar randomSeed(analogRead(A0)). Llamando a analogRead() sobre un pin analógico «desconectado», se obtendrá un número aleatorio, suficiente para inicializar el algoritmo de random().

//inicializo random

randomSeed(analogRead(A0));

Para insertar los valores dentro del array se puede utilizar un bucle for que empieza por el valor «0» y llega hasta el tamaño máximo del array, que puede ser calculado cada vez con sizeof(). Recuerda que sizeof() devuelve el tamaño del array en bytes y que el número de «posiciones» se corrige dividiendo el valor proporcionado entre el tamaño de una celda: sizeof(int). Por tanto, el bucle for será así:

for (int i = 0; i < sizeof(seq)/sizeof(int); i++){

//asigno los valores aleatorios

seq[i] = random(0,100);

}

La función random() genera solo números de tipo entero pero, a veces, podría ser necesario obtener números decimales. ¿Qué podemos hacer en estos casos? Una solución puede ser dividir el resultado proporcionado por random entre 10, 100 o 1000, según las necesidades. El siguiente sketch asigna diez números decimales aleatorios al array seq[], que en esta ocasión es de tipo float:

void setup(){

Serial.begin(9600);

randomSeed(analogRead(A0));

float seq[10];

for (int i = 0; i < sizeof(seq)/sizeof(float); i++) {

float r = random(0,101)/100.0f;

seq[i] = r;

}

//muestra valores para su comprobación

for (int i = 0; i < sizeof(seq)/sizeof(float); i++) {

Serial.println(seq[i]);

}

}

void loop(){}

El sketch no presenta ninguna dificultad en concreto, pero es muy importante calcular correctamente el tamaño del array. En el bucle for recuerda dividir sizeof(seq) entre sizeof(float); en caso contrario, el sketch podría producir algún comportamiento inesperado. El número aleatorio generado dentro del cuerpo del bucle for está comprendido entre 0 y 100, aunque después se divide entre 100 para llevarlo al intervalo entre 0 y 1. Esta división también es muy importante puesto que hay que dividir el número entre 100.0f y no simplemente entre 100. Si escribimos 100.0f estamos especificando a Arduino que utilice para la división un número de tipo float y, por tanto, con coma. Así, no se pierden los decimales, que, de otro modo, se cortarían.

26. Transformar una cadena en una matriz (tokenizar)

Para transformar una cadena compuesta por elementos separados por comas u otro carácter especial, es preciso recorrer el texto con dos índices, identificando cada vez la posición del carácter separador.

Al detalle

A veces es necesario conectarse con sistemas o shields que proporcionan información en un formato de texto. No es extraño el caso en el cual se reciba una larga lista de datos, separados por comas o por otro carácter especial, y es necesario separarlos para poder analizarlos. Esta operación se denomina «tokenización». El token es el elemento que compone la cadena y, habitualmente, se desea tener en una forma más accesible, por ejemplo, en un array.

Los datos recibidos en forma de cadena pueden tener también irregularidades, por ejemplo, espacios situados antes o después del token. Una vez separado el token, resulta sencillo corregir estos problemas, por ejemplo, aplicando el método trim() de los objetos String. Imaginemos que recibimos un mensaje de este tipo:

String msg = "manzanas,peras , fresas, bananas";

Queremos separar todos sus elementos y guardarlos en una lista:

String lista[4];

En este caso, ya indicamos el tamaño, pero no es difícil utilizar un array dinámico.

Para recortar un token, se puede utilizar el método indexOf() de los objetos String, que devuelve el índice de la cadena proporcionada, a partir de una posición determinada. Empezaremos desde «0» y buscaremos la cadena «,»:

int pos = msg.indexOf(",", 0);

Como conocemos la posición de la coma, podemos recortar el token con:

token = msg.substring(0, posición_coma);

El token también podría contener espacios vacíos que podemos eliminar fácilmente con:

token.trim();

Tras haber recortado el primer token, podemos guardarlo dentro de un array y buscar el siguiente. Esta vez no empezaremos desde «0» sino desde la última posición donde hemos encontrado el carácter separador. Para ello, nos situamos en la casilla después de pos. Después de identificar un nuevo token, debemos actualizar la posición de inicio. Cuando lleguemos al último token, el programa no encontrará más comas y pos valdrá «-1». Podemos utilizar este valor para controlar un bucle while e interrumpirlo cuando hayamos identificado también el último token. El sketch completo es este:

String msg = "manzanas,peras , fresas, bananas";

String lista[4];

void setup(){

Serial.begin(9600);

int ini = 0; //punto de inicio

int pos = 0;

String token = "";

int i = 0;

while (pos >= 0) {

//posición de la coma

pos = msg.indexOf(",", ini);

token = msg.substring(ini, pos);

token.trim();

lista[i] = token;

i++;

Serial.println(token);

ini = pos + 1;

}

//muestro la lista

for (int i = 0; i < 4; i++) {

Serial.print(lista[i]);

Serial.print(" - ");

}

}

void loop(){}

Los tokens se guardan en la «lista» que después, al final del setup(), se muestra para su comprobación en el Serial Monitor.

27. Gestionar índices múltiples y periódicos

Para gestionar más de un índice dentro de un bucle for o while basta con definir una segunda variable que sea calculada en función de la primera. En cambio, para crear índices secundarios y repetitivos, es preferible utilizar el operador módulo.

Al detalle

Hace un tiempo, Paolo Grisanti, uno de mis seguidores en YouTube, me preguntó, tras haber realizado una videolección sobre el bucle for, qué debía hacer para obtener un segundo índice, dentro del bucle for, que decreciera mientras el principal aumentaba de valor. La solución a este problema es sencilla: basta con definir un segundo índice, memorizado en una variable, que se calcule en cada iteración. Imagina que tienes un bucle for que va de 0 a 255 y que quieres añadir un índice j que, al mismo tiempo, pase de 255 a 0. Así es como se hace:

void setup(){

Serial.begin(9600);

for (int i = 0; i < 255; i++) {

int j = 255-1-i;

Serial.print(i);

Serial.print("\t");

Serial.println(j);

delay(10);

}

}

void loop(){}

El bucle for principal utiliza la variable i como índice. La variable va de 0 a 254. En el cuerpo del bucle añadimos una segunda variable j cuyo valor se calcula a cada paso. Para que su valor decrezca cuando i aumente, bastará con extraer i al máximo valor menos uno que i puede alcanzar. Los dos índices se muestran en el Serial Monitor para ser comprobados.

Otras veces podemos necesitar un índice secundario para identificar iteraciones pares o impares. Existen distintos métodos para identificar un número par o impar, pero el más eficaz y elegante es utilizar el resto de la división entre dos. En C, existe un operador denominado «módulo» que devuelve al instante dicho resto. Para identificar un número par, se dividirá el número entre «2» y se comprobará que el valor del módulo, es decir, el resto de la división, sea igual a «0». Para los números impares, el resto será «1». Este es un ejemplo realizado con un bucle for que muestra en cada línea «pares» o «impares»:

for (int i = 0; i < 10; i++) {

int pd = i%2;

Serial.print(i);

if (pd == 0) {

Serial.println(": pares");

} else {

Serial.println(": impares");

}

}

El módulo también se puede utilizar para crear índices repetitivos. Imagina que deseas gestionar un contador periódico dentro del loop() de un sketch que va de 0 a 4. De forma intuitiva, lo realizarías con una variable que incrementaría a cada paso. El problema de esta solución es que cada cuatro pasos tendrías que restablecer la variable para que empezara desde cero. Utilizando el módulo «cuatro», el sketch sería del siguiente modo:

int c = 0;

void setup(){

Serial.begin(9600);

}

void loop(){

int step = c%4;

Serial.print("step: ");

Serial.println(step);

delay(100);

c++;

}

Para activar una acción específica, por ejemplo, el último de los cuatro incrementos periódicos, se puede insertar un if que «salte» cuando el valor del módulo sea igual a 3:

int c = 0;

void setup(){

Serial.begin(9600);

}

void loop(){

if ((c%4) == 3){

Serial.println("¡4!");

} else {

Serial.print(".");

}

c++;

}

28. Trabajar con bits

Si bien el lenguaje C dispone de operadores para trabajar con bits, Arduino proporciona una serie de funciones que simplifican las operaciones a «bajo nivel»:

highByte(n): devuelve el byte más alto de un número.

lowByte(n): extrae el byte más alto del número proporcionado.

bitRead(número, bit_a_leer): lee un único bit del número proporcionado.

bitWrite(número, bit_a_leer, bit): escribe el bit indicado.

bitSet(número, bit_a_configurar): configura a 1 el bit indicado.

bitClear(número, bit_a_eliminar): elimina el bit indicado.

C ofrece también operadores para intervenir directamente sobre bits individuales:

& es el operador para AND.

| es el operador para OR.

^ es el operador para ExOR.

! es el operador de negación.

Al detalle

Si trabajas con microcontroladores, de vez en cuando puede que tengas que tratar directamente con bits y bytes. Las configuraciones de los pines para el ATMega328, el microcontrolador que utiliza Arduino, se realiza ajustando los bits de algunos registros especiales. Un registro es una variable especial situada dentro de una CPU. Por suerte, Arduino cuenta con muchas funciones de alto nivel que enmascaran el uso de bits y bytes y, para configurar los puertos de entrada, de salida y el estado de un pin, existen métodos adecuados que evitan actuar directamente sobre los registros de los microcontroladores: operación inútilmente complicada y bastante críptica para un principiante. Sin embargo, en algunas ocasiones podría ser necesario actuar sobre los bits, por ejemplo, cuando se utilizan registros deslizantes o determinados chips externos.

Antes de continuar, es mejor que hagamos alguna aclaración sobre los bits y los bytes: un bit es una pequeña unidad de memoria que puede asumir solo dos estados: «0» o «1». Un conjunto de ocho bits se denomina byte. Contar en bits no es muy difícil: se empieza desde cero y, añadiendo una unidad, tenemos «1». Si añadimos otra unidad tendremos diez y no dos. Esto es así porque los números que utilizamos habitualmente utilizan cifras del «0» al «9» y cuando, contando, llegamos al «9», eliminamos las unidades, añadimos una decena y tenemos un «10». Los bits normalmente se escriben de derecha a izquierda.

Este sería un recuento binario:

0 vale 0, 1 vale 1, 10 vale 2, 11 vale 3, 100 vale 4, 101 vale 5, 110 vale 6, etc.

Para convertir un número de binario a decimal, se puede utilizar una tabla donde cada posición tiene un peso. Escribiendo el número que deseamos convertir en las celdas de la tabla y sumando los correspondientes pesos se puede convertir en decimal.

Tabla 2.1 – Conversión de binario a decimal.


Pesos1286432168421Número
0100100173
0000101010

Un número compuesto por ocho bits forma un byte que, después, puede asumir todos los valores desde 00000000 hasta 11111111, es decir, de 0 a 255. Para evitar tener que escribir todos estos «1» y «0», muchas veces se utiliza la notación hexadecimal, otro sistema de numeración que emplea 16 símbolos en lugar de las habituales diez cifras del 0 al 9. En el sistema hexadecimal, las cifras posibles son: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Los símbolos de la A a la F valen 10, 11, 12, 13, 14 y 15. Contando en hexadecimal, al llegar a F, se sigue eliminando las unidades y añadiendo una decena: tras la F está el «10», que vale «16».

Cada símbolo hexadecimal puede ser escrito con cuatro bits, por lo que:

0 corresponderá a 0000, 1 corresponderá a 0001, 2 corresponderá a 0010, 3 corresponderá a 0011, hasta llegar a F, que será igual a 1111.

Dado que un byte está formado por ocho bits, puedes indicarlo con dos cifras hexadecimales que equivalen exactamente a ocho bits:

•01 en hexadecimal es igual a 0000 0001, es decir, a 1 en decimal.

•0A en hexadecimal es igual a 0000 1010, es decir, a 10 en decimal.

•0F en hexadecimal es igual a 0000 1111, es decir, a 15 en decimal.

•10 en hexadecimal es igual a 0001 0000, es decir, a 16 en decimal.

•A0 en hexadecimal es igual a 1010 0000, es decir, a 160 en decimal.

•FF en hexadecimal es igual a 1111 1111, es decir, a 255 en decimal.

Arduino puede tratar directamente números binarios anteponiendo la letra «B» (en mayúsculas) a la secuencia de 1 y 0: B00110101. En C también puedes trabajar con valores hexadecimales. Para inicializar una variable con un número hexadecimal haces lo siguiente:

int n = 0x0BAF;

Un número entero en Arduino está formado por dos bytes. Por ejemplo, el número 6660 está formado por dos bytes 1A04, es decir, por los bits 0001 1010 0000 0100. El byte «alto» de este número es igual a los primeros cuatro bits empezando por la izquierda: 0001 1010 y, por tanto, «26» (en decimal).

Con Arduino, se pueden utilizar las funciones lowByte() y highByte(), las cuales extraen el byte más alto o más bajo de un número o de una variable. Puedes extraer el byte más alto utilizando la función highByte():

byte val = highByte(n);

El byte más bajo es, en cambio, 0000 0100, que vale 4 (decimal) y que puedes extraer con:

byte val = lowByte(n);

A continuación, puedes ver un sketch que lo resume todo:

void setup() {

Serial.begin(9600);

int n = 6660;

Serial.print("número: ");

Serial.println(n);

byte val = highByte(n);

Serial.print("high byte: ");

Serial.println(val);

val = lowByte(n);

Serial.print("low byte: ");

Serial.println(val);

}

void loop(){}

Arduino proporciona también funciones para leer los bits individuales de un número. Con:

byte val = bitRead(número, bit_a_leer);

Puedes leer el bit en la posición n de la variable x. La posición se lee a partir del bit menos importante, el situado a la derecha de la secuencia de bits. El valor de n empieza de cero.

Para configurar un bit individual, en cambio, puedes utilizar:

bitWrite(número, bit_a_leer, bit);

La posición del bit dentro del número está indicada por bit_a_leer. El valor que se debe configurar es especificado por bit y puede valer 1 o 0.

La función:

bitSet(número, bit_a_configurar);

Es igual a bitWrite(), con la diferencia que configura en 1 el bit indicado. En cambio, la función:

bitClear(número, bit_a_restablecer);

Restablece el bit indicado, llevándolo a 0.

Este sería un sketch de resumen con el cual probar estas funciones.

void setup() {

Serial.begin(9600);

int n = 42258; //A512

byte lo = lowByte(n);

Serial.println(lo); //muestra 18... es decir 12H

byte hi = highByte(n);

Serial.println(hi); //muestra 165 es decir A5H

for (int i = 15; i >= 0; i = i - 1) {

byte b = bitRead(n, i);

Serial.print(b);

} //muestra 1010010100010010

}

void loop() {

}

Vuelve a copiar el sketch y cárgalo en Arduino. Para este ejemplo no necesitas ningún tipo de hardware. Después de haber cargado el sketch, abre el Serial Monitor para observar los resultados.

La función bit, en cambio, calcula el valor de un bit y es un atajo para ayudarte a transformar un bit en un valor decimal. La función equivale a multiplicar el número «2» tantas veces como indica el parámetro proporcionado.

int val = bit(n);

bit(0) es igual a 1, bit(1) vale 2, bit(3) vale 8, es decir, es como realizar la siguiente multiplicación: 2 x 2 x 2, o bien 23. Con esta función puedes convertir un número binario en formato decimal. Si tienes el número 1010, la conversión se realizará con este cálculo:

1 x 23 + 0 x 22 + 1 x 21 + 0 x 20 = 1 x 8 + 0 + 1 x 2 + 0 = 10

En un sketch deberás escribir:

int val = 1 * bit(3) + 0 * bit(2) + 1 * bit(1) + 0 * bit(0);

Operaciones bit a bit de C

El lenguaje C utiliza operadores de bajo nivel para trabajar directamente con bits. La sintaxis de estos operadores puede parecer un poco más complicada que las funciones proporcionadas por Arduino pero, en cambio, ganan en rapidez. Una operación utilizada con mucha frecuencia, sobre todo cuando se utilizan registros o búfers externos, es el desplazamiento de bits. Los pines y muchas de las funciones de Arduino también están controlados por registros que se pueden activar configurando los bits oportunos. El desplazamiento de un valor dentro de un byte se obtiene con los operadores >> y << seguidos del número de posiciones que debe saltar. Imagina que tienes un simple bit configurado al inicio de un byte:

0000 0001

Si llamas a << 1 es posible que se desplace una posición hacia la izquierda, obteniendo:

0000 0010

Este es un fragmento de código para desplazar un bit primero hacia la izquierda y después, hacia la derecha:

byte n = 1;

String str = "";

//desplazamiento hacia la izquierda

for (int i = 0; i < 7; i++){

n = n << 1;

str = String(n, BIN);

Serial.println(str);

}

//desplazamiento hacia la derecha

for (int i = 0; i < 7; i++){

n = n >> 1;

str = String(n, BIN);

Serial.println(str);

}

El bit se configura dentro de la variable n, de tipo byte. Asignar directamente el valor «1» equivale a escribir: 0000 0001. La operación de desplazamiento se repite siete veces para que el bit se desplace hasta la última posición del byte: 1000 0000. El segundo bucle for sitúa el bit a la posición inicial.

El lenguaje C es capaz de realizar operaciones lógicas bit a bit. Las lógicas fundamentales son: AND, OR, ExOR y NOT. AND es una especie de suma que devuelve 1 si ambos operandos son 1. Su comportamiento se muestra en la Tabla 2.2.

Tabla 2.2 – El operador AND.


ABResultado
111
100
010
000

En el lenguaje C se puede llamar utilizando el operador &. El operador AND actúa, no solo sobre los bits contenidos en un tipo byte, sino también sobre números enteros y float. Este es un ejemplo de uso del operador:

0000 1010 &

0000 1100 =

---------

0000 1000

El código equivalente se muestra a continuación:

int a = 1 * bit(3) + 0 * bit(2) + 1 * bit(1) + 0 * bit(0);

int b = 1 * bit(3) + 1 * bit(2) + 0 * bit(1) + 0 * bit(0);

Serial.println(a);

Serial.println(b);

int c = a & b;

String str = String(c, BIN);

Serial.println("AND");

Serial.println(str);

Los dos operandos han sido calculados sumando los bits con la función que calcula su peso según la posición indicada. Es posible indicar su valor directamente anteponiendo la letra «B» a la secuencia de bits:

int a = B00001010; //en hexadecimal 0x10

int b = B00001100; // en hexadecimal 0x12;

El operador AND se puede utilizar también para extraer y evaluar bits individuales dentro de un byte. De hecho, gracias a sus propiedades, permite eliminar, mediante una máscara concreta, todos los bits que no interesan. Imagina que tienes un número (1010 1010) y deseas evaluar el bit situado en cuarta posición. Para eliminar todos los bits, excepto el cuarto, le aplicaremos la siguiente máscara de bits:

0000 1000

Por tanto, la operación de «extracción» será:

1010 1010 & (el número)

0000 1000 = (la máscara)

---------

0000 1000

Ya hemos aislado el bit. Si el bit situado en la cuarta posición fuera un «0», obtendríamos igualmente «0». Aquí tienes un segundo ejemplo:

0110 0010 & (el número)

0000 1000 = (la máscara)

---------

0000 0000

Y este es un ejemplo escrito en C:

//extracción de un bit

int a = 255; //equivale a 1111 1111

int b = 1 * bit(1) + 0 * bit(0); //la máscara 0000 0010

Serial.println(a);

Serial.println(b);

int c = a & b;

String str = String(c, BIN);

Serial.println("Máscara 0000 0010");

Serial.println(str);

El operador OR devuelve 1 si, como mínimo, uno de sus operandos vale «1». Su comportamiento se muestra en la Tabla 2.3.

Tabla 2.3 – El operador OR.


ABResultado
111
101
011
000

En el lenguaje C se puede llamar con el operador |. El operador OR actúa, no solo sobre los bits contenidos en un tipo byte, sino también sobre números enteros y float. Este es un ejemplo de uso del operador:

0000 1010 |

0000 1100 =

---------

0000 1110

El código equivalente se muestra a continuación:

int a = 1 * bit(3) + 0 * bit(2) + 1 * bit(1) + 0 * bit(0);

int b = 1 * bit(3) + 1 * bit(2) + 0 * bit(1) + 0 * bit(0);

Serial.println(a);

Serial.println(b);

int c = a | b;

String str = String(c, BIN);

Serial.println("OR");

Serial.println(str);

Otro operador muy utilizado es la negación (NOT), que devuelve siempre el bit contrario al proporcionado. Su comportamiento se muestra en la Tabla 2.4.

Tabla 2.4 – El operador NOT.


AResultado
10
01

En el lenguaje C se puede llamar con el operador «!». El operador NOT actúa no solo sobre los bits contenidos en un tipo byte, sino también sobre números enteros y float. Este es un ejemplo de uso del operador:

! 0000 1010 =

---------

1111 0101

A continuación, puedes ver un código de ejemplo:

byte a = 1 * bit(0);

byte c = !a;

String str = String(c, BIN);

Serial.println("NOT");

Serial.println(str);

El último operador, más raro de encontrar, es ExOR (o exclusivo), que devuelve 1 solo si los dos operandos son distintos. Su comportamiento se muestra en la Tabla 2.5. El símbolo que se utiliza en el lenguaje C es «^».

Tabla 2.5 – El operador ExOR.


ABResultado
110
101
011
000

Бесплатный фрагмент закончился.

1 948,48 ₽
Жанры и теги
Возрастное ограничение:
0+
Объем:
569 стр. 249 иллюстраций
ISBN:
9788426727756
Издатель:
Правообладатель:
Bookwire
Формат скачивания:
epub, fb2, fb3, ios.epub, mobi, pdf, txt, zip

С этой книгой читают

Эксклюзив
Черновик
4,7
127
Хит продаж
Черновик
4,9
474