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

Шрифт:

13. Repetir una operación

Para repetir un grupo de instrucciones, existen varias posibilidades. Si la repetición se produce hasta que se comprueba un evento aleatorio o sin determinar, se utiliza el bucle while:

while (condición) {

//operación a repetir…

}

El bucle while puede repetir operaciones un número predeterminado de veces, utilizando un contador «externo», realizado con una variable:

int i = 0;

while (i < 10) {

//instrucciones a repetir 10 veces

i++;

}

Si se desea que las instrucciones incluidas dentro del bucle while se ejecuten como mínimo una vez se puede utilizar la forma do...while:

do {

//instrucciones a repetir

} while (condición);

Si utilizas un bucle while, debes asegurarte de que siempre haya una condición que ponga fin al bucle. En caso contrario, se repetirá sin fin.

Para repetir operaciones durante un número predeterminado de veces, se utiliza el bucle for, que necesita una variable que se utilice como contador, una regla para establecer la finalización del bucle y el método para aumentar el valor de la variable en cada iteración:

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

//instrucciones a repetir 10 veces

}

Además de estos bucles «explícitos», también es posible utilizar el mismo loop(), que Arduino repite a la mayor velocidad posible. El bucle loop() no tiene fin.

Se puede modificar la ejecución de un bucle for y while utilizando continue y break:

break interrumpe inmediatamente el bucle y sale.

continue interrumpe la ejecución de la iteración actual y pasa a la siguiente.

Al detalle

Para no escribir sketch kilométricos, resulta del todo necesario introducir bucles que repitan las operaciones. Los bucles permiten escribir código más compacto, legible y de fácil mantenimiento. Los principiantes creen que no supone ningún problema repetir fragmentos de código dentro de un programa. Este inócuo comportamiento puede llevar a situaciones ingestionables y a un código complicado. Si repites varias veces fragmentos de código, en caso de tener que modificarlo, te verás obligado a revisar cada fragmento uno a uno. Los ordenadores son especialistas en repetir operaciones largas y aburridas, y es inteligente aprovecharse de esta capacidad. Existen distintos tipos de bucles que pueden repetir un grupo de instrucciones durante un determinado número de veces o bien hasta que no se verifica una determinada condición.

El tipo más sencillo de repetición es la que implica la comprobación de una determinada condición, donde el número de repeticiones no está predefinido. Puedes realizar un bucle de este tipo con la instrucción while. Tras la palabra clave while, se añade, entre paréntesis, la condición por evaluar y después, entre corchetes, las instrucciones por repetir. El bucle se repite hasta que la condición indicada entre los dos paréntesis es verdadera. Este sería un ejemplo para mostrar diez veces una cadena en el Serial Monitor:

void setup() {

Serial.begin(9600);

int i = 0;

while (i < 10) {

Serial.print("i: ");

Serial.println(i);

i++;

}

}

void loop(){}

Todo el código está incluido en el setup(). Al inicio de la sección puedes ver la inicialización del puerto serie. Para poder contar los diez pasos, el bucle while necesita una variable. Por esta razón, inmediatamente antes de entrar en el bucle, se ha definido la variable i, inicializada a 0.

El bucle while empieza y repetirá las instrucciones incluidas entre corchetes hasta que la variable i sea menor que 10. Dentro del cuerpo de while, la variable i debe aumentar en cada iteración, sino el bucle no se detendría nunca porque la condición i < 10 siempre sería verdadera.

Por brevedad, en ocasiones verás la operación de aumento de la variable i en una forma «compactada» que tanto gusta a los programadores:

i++

Con un bucle while el número de repeticiones no está prefijado. Por este motivo, a veces se introduce una variable de contador que memoriza el número de repeticiones, aunque esta solo es necesaria si se precisa llevar la cuenta de las repeticiones.

Vamos a intentar realizar un bucle while que se interrumpe cuando se verifica un evento aleatorio. En este caso, deberás realizar un simple circuito, contectando un botón a Arduino. Coloca el botón sobre la breadboard y añade una resistencia de 10 kΩ (Figura 2.1). El evento «aleatorio» será la pulsación del botón, que interrumpirá la ejecución del bucle.


Figura 2.1 – Conecta un botón al pin 8 de Arduino.

void setup() {

Serial.begin(9600);

pinMode(8, INPUT);

}

void loop(){

bool RUN = true;

while (RUN) {

Serial.print(".");

if (digitalRead(8)){

RUN = false;

}

delay(200);

}

Serial.println("X");

}

En el setup() del sketch vemos la inicialización del puerto serie a 9600 baudios (símbolos por segundo) y del pin 8 de Arduino, que utilizaremos como entrada donde conectar el botón. En el loop() encontramos un bucle while que está controlado por la variable RUN, de tipo booleano. Una variable booleana puede contener solo un valor que puede ser verdadero o falso (true o false). El bucle while repetirá las instrucciones incluidas en su cuerpo hasta que la variable RUN sea igual a true. Para modificar el estado de la variable, es necesario pulsar el botón, controlado por una instrucción de tipo if. A la primera pulsación del botón, el valor de RUN pasa de true a false y el bucle while podrá finalizar. Si ejecutas el programa y abres el Serial Monitor podrás observar una secuencia de puntos que se interrumpen con una «X» cada vez que pulsas el botón.

En ocasiones es preferible ejecutar un grupo de instrucciones y, después, valorar si repetirlas o no cuando finaliza la ejecución. En estos casos, puedes utilizar el bucle do...while:

int i = 1;

do {

i++;

Serial.print("i vale:");

Serial.println(i);

} while (i <= 5);

Esta vez el programa entra de inmediato en el cuerpo del bucle (el bloque que se repetirá), aumenta la variable i y muestra un mensaje. Después, comprueba si ejecutar de nuevo el grupo de instrucciones.

Otro constructo muy utilizado es for, con el cual puedes repetir operaciones durante un número predefinido de veces. Para utilizar el bucle for debes especificar lo siguiente:

•la variable que se utilizará como contador.

•una prueba para saber cuándo deben finalizar las repeticiones.

•un modo para incrementar la variable contador.

Estas tres informaciones se «codifican» en C y van detrás de la palabra clave for:

for (int i = 1; i < 3; i++) {

//instrucciones que se repetirán 3 veces

}

Inmediatamente después de for, puedes ver un par de paréntesis donde, separadas por puntos y coma, se encuentran las tres informaciones necesarias para que el bucle funcione. En primer lugar, se define una variable i que se utilizará como contador y a la cual se asigna el valor inicial uno; después, se añade la condición de final de bucle: las repeticiones continuarán hasta que el contador i sea menor que tres. Por último, puedes ver el modo de incremento: la variable i se incrementará en una unidad por cada vuelta. Las instrucciones que hay que repetir están delimitadas por llaves.

El programa llega a la instrucción for, crea una variable i2 y en ella memoriza el valor «uno». Después, identifica la condición de fin de bucle (terminará cuando i sea mayor o igual que tres) y el modo de incremento de la variable (i++). Dado que i es menor que tres, el bloque de instrucciones situado entre llaves puede ser ejecutado (cuerpo del bucle). Después de esto, el programa incrementa la variable i, que ahora valdrá «dos», comprueba la condición (2 < 3) y ejecuta de nuevo el cuerpo del bucle. En el siguiente paso, i valdrá «tres» y la condición ya no se volverá a comprobar (3 < 3), por lo que el programa saltará hasta el final del bucle for y seguirá adelante.

Puedes modificar el «paso» sustituyendo i++ con el modo de incremento que prefieras. Puedes avanzar por pasos de dos unidades mediante:

for (int i = 1; i < 10; i = i + 2) {

//instrucciones a repetir

}

Ahora el modo de incremento es más comprensible: la variable i en cada paso será calculada añadiendo «dos» a su valor.

La variable «contador» también puede ser una variable preexistente y no es necesario definirla dentro de for, sino que debes inicializarla siempre insertando un valor inicial. Por ejemplo, puedes escribir:

int i = 0;

for (i = 1; i < 10; i = i + 2) {

//cuerpo del bucle for

}

Puedes modificar las repeticiones de un bucle for o while utilizando los comandos break y continue. El primero, break, interrumpe el bucle, mientras que continue interrumpe la ejecución de la iteración actual.

Prueba a ejecutar el siguiente programa:

void setup() {

Serial.begin(9600);


int i = 0;

while (i < 10) {

Serial.print("i: ");

Serial.println(i);

if (i == 5) break;

i++;

}

Serial.print("salido con i: ");

Serial.println(i);

}

void loop(){}

El bucle for debería mostrar el valor de la variable i diez veces.

Dentro del cuerpo del bucle, hay una prueba que verifica si i vale cinco y, después, llama a break. De este modo, las diez iteraciones se interrumpirán a la quinta vez y, en pantalla, podrás leer:

i:4

i:5

Salido con i = 5

La instrucción continue interrumpe solo la ejecución de la interacción actual, sin interrumpir por completo la ejecución del bucle. Prueba ahora este ejemplo:

void setup() {

Serial.begin(9600);

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

if (j == 5) continue;

Serial.print("j: ");

Serial.println(j);

}

}

void loop(){}

Cuando j sea igual a cinco, el programa saltará al inicio del bucle y continuará con las iteraciones que faltan. El resultado en pantalla será el siguiente:

i: 0

i: 1

i: 2

i: 3

i: 4

i: 6

i: 7

i: 8

i: 9

Las instrucciones break y continue funcionan con cualquier tipo de bucle: for, while y do...while.

14. Utilizar un array

Un array es una variable dotada de varias partes a las cuales es posible acceder con un índice. En un array con n posiciones, el índice va desde 0 hasta n-1. Se puede definir un array de enteros del siguiente modo:

int números[6] = {0, 10, 2, 3, 1, 9};

Para acceder y utilizar un valor contenido en un array se debe indicar, utilizando un índice, el valor que se desea utilizar. Para acceder al cuarto elemento del array deberías escribir:

números[3]

Al detalle

A veces, una simple variable no es suficiente y podría ser que necesitaras tener que crear muchos «cajones» para memorizar informaciones distintas, pero muy similares entre sí en cuanto al significado.

Veamos un caso sencillo, pero concreto: calcular la media de cinco números. Para calcular la media, debes definir cinco variables distintas, una para cada número, después las sumas y divides el resultado entre cinco. Puedes llamar a las variables n1, n2, n3, n4, n5.

float n1 = 1.1;

float n2 = 2.3;

float n3 = 3.7;

float n4 = 4.1;

float n5 = 5.8;

float media = (n1 + n2 + n3 + n4 + n5) / 5.0;

Es cierto que no resulta demasiado práctico tener que crear cinco variables distintas. Un array es como un cajón con compartimentos. El número de compartimentos está predefinido. Crea un cajón denominado «temperaturas» para memorizar tres valores diferentes:

float temperaturas[3];

Para indicar el número de posiciones disponibles (o compartimentos), debes añadir el número, entre corchetes, inmediatamente después del nombre de la variable. También puedes indicar el número de posiciones y dejar vacíos los corchetes; en este caso, después del signo igual, deberás listar los elementos del array entre llaves y separados por comas (,).

float temperaturas[] = {32.1, 30.0, 33.0};

int estados[] = {0, 10, 2, 3, 1, 9};

Así es como calculamos la media de cinco números utilizando un array:

void setup() {

Serial.begin(9600);

float nn[] = {12.2, 1.2, 324.5, 34.8, 45.9};

float media = (nn[0]+nn[1]+nn[2]+nn[3]+nn[4])/5.0;

Serial.println(media);

}

void loop() {}

Para acceder a un elemento del array, debes indicar su posición dentro del «cajón» utilizando el índice que empieza desde cero. Si el array tiene cinco posiciones, el índice irá de cero a cuatro. Para especificar el elemento deseado, se utilizan corchetes, situados después del nombre del array.

void setup() {

Serial.begin(9600);

float nn[] = {12.2, 1.2, 324.5, 34.8, 45.9};

//para mostrar el tercer elemento

Serial.println (nn[2]);

//para mostrar el primer elemento, el índice es 0

Serial.println (nn[0]);

//para mostrar el último elemento

Serial.println (nn[4]);

}

void loop() {}

15. Obtener la longitud de un array

Arduino no dispone de una función para calcular la longitud de un array. Sin embargo, se puede utilizar el operador sizeof(), que cuenta los bytes ocupados por la estructura o por cualquier variable. Para obtener el tamaño, es decir, el número de posiciones, de un array de enteros, se puede utilizar una redacción como esta:

int dimensión_array = sizeof(array_a_medir)/sizeof(int);

Al detalle

Cuando trabajes con arrays y estructuras dinámicas, puede ocurrir que necesites calcular su longitud, es decir, el número de elementos. En Arduino, no existe una función específica para obtener el número de elementos de un array. C tiene el operador sizeof(), que cuenta el número de bytes que ocupa el array. La definición de un array va precedida por el tipo de variable que será memorizada y este devolverá resultados distintos según si los elementos son de tipo byte, int o long.

Prueba el siguiente sketch:

void setup() {

Serial.begin(9600);


int números[] = {1,2,3,4};

int posiciones = sizeof(números);

Serial.println (posiciones);

}

void loop(){}

Si lo ejecutas y abres el Serial Monitor verás: ¡8! ¿Cómo es posible si el array «números» tiene solo 4 posiciones? El operador sizeof() cuenta el número de bytes y no «las casillas» efectivas del array. El tipo int, en Arduino, ocupa dos bytes y, por tanto, un array de cuatro posiciones ocupa, en total, ocho bytes. Para mostrar correctamente el número de posiciones del array, debes corregir el cálculo de la variable «posiciones»:

int posiciones = sizeof(números)/2;

Al corregir el sketch, obtendrás el valor correcto. Sin embargo, de este modo, deberás conocer cada vez el tamaño en bytes del tipo utilizado. Esto podría ser un problema en el caso de tener que compilar el sketch para una plataforma distinta, donde quizás los int ocupan cuatro bytes. Para hacer frente a futuros problemas, podemos adaptar automáticamente el cálculo solicitando a Arduino que especifique el tamaño del tipo «base» utilizado en el array. Así, el sketch pasa a ser:

void setup() {

Serial.begin(9600);


int números[] = {1,2,3,4};

int posiciones = sizeof(números)/sizeof(int);

Serial.println (posiciones);

}

void loop(){}

El cálculo dinámico del tamaño de un array se puede utilizar para gestionar de forma dinámica y automática series de pines. Con cierta frecuencia tendrás que configurar y controlar largas series de pines. Puedes ocuparte de ello administrando un pin cada vez y repitiendo todo el código necesario, o bien listando todos los números de los pines en un array que se recorrerá con un bucle for. Aquí tienes un ejemplo para inicializar un determinado número de pines como salidas:

int pinout[] = {5,10,11,13};

void setup() {

Serial.begin(9600);


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

pinMode(pinout[i], OUTPUT);

Serial.print("inic del pin ");

Serial.println(pinout[i]);

}

}

void loop() {

//código del sketch…

}

Al inicio del sketch se encuentra la definición de un array de tipo int que contiene la lista de los pines a utilizar. En el setup() inicializamos la serie para poder probar el sketch a través del Serial Monitor. La inicialización de los pines se lleva a cabo desde un bucle for que utiliza la variable i. La variable empieza desde cero e irá incrementando de manera que indique todas las celdas del array. El número de celdas no se indica de forma explícita, sino que se calcula «en línea» con la fórmula indicada anteriormente. Dentro del bucle for los pines que hay que inicializar se obtienen leyendo el valor de pinout[i] «indicado» por i.

16. Copiar o ampliar un array

Los valores contenidos en un array no se pueden copiar con una simple reasignación. El mejor método para copiar un array es utilizar el comando memcpy():

memcpy( lista_destino, lista_origen, número_bytes_a_copiar );

El único problema que presenta esta instrucción es que se debe especificar el número de bytes que hay que copiar, número que no se puede expresar en «posiciones», sino en bytes; una información que, a menudo, los neófitos desconocen y que depende del tipo de datos utilizados en el array. Para obtener fácilmente el número de bytes, es aconsejable utilizar el operador sizeof() aplicándolo al tipo de datos contenidos en el array. Para copiar un array de int formado por cuatro elementos podríamos hacer lo siguiente:

memcpy( list2, list1, 4*sizeof(int) );

No existe ningún operador que permita ampliar dinámicamente la longitud de un array. Por este motivo, el único sistema es crear un nuevo array con una longitud superior y, después, copiar los elementos de la estructura más pequeña a la más grande.

Al detalle

Desafortunadamente, en C y en la mayoría de los lenguajes de programación, los array están considerados estructuras complejas y no se pueden copiar directamente como si fueran simples variables. No es posible realizar esta operación:

int lista1[4] = lista2;

Intuitivamente es posible, si entendemos bien cómo está formado un array, realizar la copia con un bucle for. Para copiar un array de tres elementos, podríamos utilizar este código:

int nuevo_array[3];

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

nuevo_array[i] = array_a_copiar[i];

}

Si bien esta solución funciona, desgraciadamente no es muy eficaz, ni elegante. El lenguaje C dispone del operador memcpy() para copiar directamente un array en otro. La sintaxis es la siguiente:

memcpy( lista_destino, lista_origen, número_bytes_a_copiar );

El problema de memcpy() es definir bien el número de bytes que hay que copiar; de hecho, como no es una función «específica» para la copia de arrays, sino más bien una herramienta para copiar fragmentos de bytes, requiere que se especifique no el número de posiciones a copiar, ¡sino el número de bytes! Este simple requerimiento intimida, sin duda, a cualquier maker principiante. El número de bytes se puede obtener conociendo el tipo de dato memorizado en el array y ayudándose de otro operador: sizeof(). Este devuelve la dimensión en bytes de una variable o de un tipo de datos. Si tenemos un array de int, la dimensión de cada celda es igual al número de bytes ocupado por el tipo int, que se obtiene con sizeof(int). El número de bytes ocupado por un array de enteros de n posiciones será, por tanto, n multiplicado por sizeof(int). Veamos un ejemplo práctico:

int list1[4] = {5,10,11,13};

int list2[4];

void setup()

{

Serial.begin(9600);


memcpy( list2, list1, 4*sizeof(int) );

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

Serial.println(list2[i]);

}

}

void loop(){}

Las primeras instrucciones del sketch crean dos arrays. El primero, denominado list1, se inicializa con cuatro elementos, mientras que el segundo tiene el mismo tamaño pero está vacío. Para este ejemplo, no es necesario repetir operaciones, por lo que utilizaremos solo el bloque setup(). En el setup() inicializamos el puerto serie para poder escribir en él desde el sketch. La operación de copia con memcpy() es inmediata. Los argumentos del comando son la lista de destino, la lista de origen y el número de bytes a copiar. Si sabemos que los arrays son de tipo int y que el número de celdas a copiar es igual a cuatro, el cálculo del número de bytes se obtiene utilizando el operador sizeof(int),que devolverá el tamaño efectivo, en bytes, de una celda. El bucle for muestra en el Serial Monitor el contenido del segundo array, para comprobar la copia resultante.

La instrucción memcpy() también se puede utilizar para ampliar un array. Entre las instrucciones de Arduino no existe un comando para crear y gestionar matrices dinámicas y, por tanto, para ampliar un array, es preciso crear uno más grande y copiar en él el contenido del más pequeño. A continuación, puedes ver un sketch igual que el anterior pero donde el tamaño del segundo array es cinco.

int list1[4] = {5,10,11,13};

int list2[5];

void setup()

{

Serial.begin(9600);


memcpy( list2, list1, 4*sizeof(int) );


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

Serial.println(list2[i]);

}

}

void loop(){}

El sketch es completamente igual que el anterior, con la única diferencia de que el tamaño de list2 ahora es cinco. La copia realizada con memcpy() se lleva a cabo siempre sobre cuatro celdas. Esta vez, el bucle for calcula dinámicamente el número de informaciones que hay que escribir en el Serial Monitor, utilizando para ello sizeof(). El número de celdas del array a recorrer es igual a las dimensiones en bytes de todo el array (list2), dividido entre el tamaño en bytes de cada celda (sizeof(int)). El resultado de este cálculo es, por tanto, cinco.

Veamos ahora otra variante, donde el segundo array se determina de forma dinámica y solo cuando se necesita. En los sketch anteriores conocíamos de antemano las dimensiones del array, pero a menudo esta información no se sabe y se necesita crear un array con un número de celdas calculado al instante. El sketch es este:

int list1[4] = {5,10,11,13};

void setup() {

Serial.begin(9600);


//creo un array con una posición más respecto a list1

//el tamaño de list1

int posiciones_list1 = sizeof(list1)/sizeof(int);

//defino el nuevo array:

int list2[ posiciones_list1 + 1 ];

//lo inicializo:

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

list2[i] = 0;

}


//copio list1 en el nuevo array que acabo de crear

memcpy( list2, list1, 4*sizeof(int) );


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

Serial.println(list2[i]);

}

}

void loop(){}

En este fragmento tenemos solo la definición inicial de list1 que contiene cuatro números. Dentro del setup() crearemos un nuevo array con una posición más respecto a las de list1. Para crear correctamente este array, en primer lugar, se debe calcular el tamaño de list1 con:

int posiciones_list1 = sizeof(list1)/sizeof(int);

Inmediatamente después, ya se puede crear el nuevo array, list2, con una celda más:

int list2[ posiciones_list1 + 1 ];

Para tener la certeza de que list2 no contiene números aleatorios, debemos inicializarlo escribiendo en cada una de las celdas el número «0» y, después, realizar la copia de list1 utilizando memcpy().

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

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

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