Tabla de contenido:

Un menú en Arduino y cómo usar los botones: 10 pasos (con imágenes)
Un menú en Arduino y cómo usar los botones: 10 pasos (con imágenes)

Video: Un menú en Arduino y cómo usar los botones: 10 pasos (con imágenes)

Video: Un menú en Arduino y cómo usar los botones: 10 pasos (con imágenes)
Video: Como Hacer Un Menú en Arduino "MUY FÁCIL" 2024, Noviembre
Anonim
Un menú en Arduino y cómo usar los botones
Un menú en Arduino y cómo usar los botones

En mi tutorial de Arduino 101, se le enseñará cómo configurar su entorno en Tinkercad. Utilizo Tinkercad porque es una plataforma en línea bastante poderosa que me permite demostrar una variedad de habilidades a los estudiantes para construir circuitos. ¡Siéntete libre de crear todos mis tutoriales usando el IDE de Arduino y un Arduino real!

En este tutorial, aprenderemos sobre los botones. Necesitamos saber:

  • Cómo conectarlos
  • Leer su valor
  • Rebote y por qué es importante
  • Una aplicación práctica (crear un menú)

La mayoría de la gente piensa que lo más práctico que se puede hacer con un botón es encender y apagar una luz. ¡Nosotros, no aquí! Usaremos el nuestro para crear un menú y configurar algunas opciones en Arduino.

¿Listo? ¡Empecemos!

Paso 1: configura la placa

Configurar el tablero
Configurar el tablero
Configurar el tablero
Configurar el tablero

El primer paso es colocar un Arduino y Breadboard Small en el área de creación de prototipos. Consulte las imágenes de arriba para ver cómo conectar los rieles de alimentación.

Un Breadboard Mini tiene dos rieles de alimentación en la parte superior e inferior. Los conectamos al Arduino para que podamos proporcionar energía a más componentes. Más adelante en este tutorial usaremos 3 botones, por lo que necesitaremos más potencia. Lo que hay que tener en cuenta es que en una placa de pruebas pequeña, los rieles de alimentación corren a través de la placa, horizontalmente. Esto es diferente a las columnas en el área principal de prototipos en el medio; estos corren verticalmente. Puede usar cualquiera de los pines de alimentación para proporcionar energía a cualquier columna en el área principal en el medio.

Cuando agregue energía, use cables negros y rojos para el negativo y el positivo respectivamente. Agregue cables en el extremo que llevan la energía al otro lado de la placa. No usaremos ese lado, pero es una buena práctica.

Paso 2: agregue el botón y la resistencia

Agregue el botón y la resistencia
Agregue el botón y la resistencia
Agregue el botón y la resistencia
Agregue el botón y la resistencia
Agregue el botón y la resistencia
Agregue el botón y la resistencia

Agregue un pequeño botón de la bandeja de componentes. Debería verse como el de la imagen. ¡Asegúrate de que no sea un interruptor! Agrega también una resistencia. Haga clic en él y establezca su valor en 10 kΩ. Eso es suficiente para bajar el pin cuando no está conectado, lo cual es muy importante más adelante en el código.

Coloque el componente en el medio de la placa de pruebas. La forma en que funciona un botón es:

  • De esquina a esquina, el botón no está conectado. Al presionar el botón, se cierran los contactos y se conectan las esquinas.
  • Los lados del botón están conectados. Si conecta un cable en la parte superior izquierda y en la parte inferior izquierda, el circuito se cerrará.

Es por eso que colocamos el componente en el espacio del medio. Se asegura de que las esquinas no estén conectadas debajo de los pines en la placa.

El siguiente paso proporciona un par de imágenes que ilustran estos puntos.

Coloque la resistencia del pin inferior derecho a través de las columnas, de modo que se asiente horizontalmente.

Paso 3: Conexiones de botones

Conexiones de botones
Conexiones de botones
Conexiones de botones
Conexiones de botones

Las imágenes de arriba dejan bastante claro cómo se conectan los botones. ¡Siempre fue un punto de confusión cuando piensas que algo está bien y no funciona!

Ahora, agreguemos los cables.

  • Coloque un cable rojo de una clavija de alimentación positiva a la misma columna que la clavija inferior derecha del botón
  • Coloque un cable negro de un pin de alimentación negativo a la misma columna que la resistencia.
  • Coloque un cable de color (no rojo / negro) desde el pin superior izquierdo al pin digital 2 en el Arduino

Verifique las imágenes de arriba para asegurarse de que su cableado sea correcto.

Paso 4: el código …

El código…
El código…
El código…
El código…

Echemos un vistazo al código de un botón básico.

Abra el editor de código y cambie de Bloques a Texto. Borre la advertencia que aparece. ¡Estamos contentos con el texto!

Ya conoce la configuración básica, así que definamos el botón y hagamos una lectura básica. Imprimiremos la salida en Serial.

Puse algunos comentarios adicionales en el código a continuación para que sea más fácil de leer que la imagen.

// Definir constantes

#define button 2 void setup () {pinMode (botón, ENTRADA); Serial.begin (9600); } void loop () {// Leer el pin digital para verificar el estado del botón int presionado = digitalRead (button); // El botón devuelve ALTO si se presiona, BAJO si no si (presionado == ALTO) {Serial.println ("Presionado!"); }}

¡Bien, eso funciona!

Esencialmente, todo lo que estamos haciendo es verificar el estado del pin digital cada vez que el código se repite. Si hace clic en Iniciar simulación y presiona el botón, verá que el monitor en serie (haga clic en el botón debajo del código) muestra "¡Presionado!" repetidamente.

Una característica que verá en el código anterior es la evaluación de la condición if () que se está llevando a cabo. Todo lo que hace el código es hacer una pregunta y evaluar si es cierta, en este caso. Usamos el es igual (doble signo igual, como este: ==) para verificar si el valor de la variable es igual a cierto valor. Un digitalRead () devuelve HIGH o LOW.

Usando if () else if / else podemos verificar muchas condiciones o todas las condiciones, y si regresa a los conceptos básicos de Arduino, verá algunas de las comparaciones que puede hacer.

Ahora … Nuestro código puede parecer completo … Pero tenemos un problema.

Mira, eso funciona muy bien en el simulador. Pero la electricidad real tiene ruido, especialmente la electrónica de CC. Entonces, nuestro botón puede devolver una lectura falsa a veces. Y eso es un problema, porque es posible que su proyecto no responda de la manera correcta para el usuario.

¡Arreglemoslo!

Paso 5: un poco de rebote

Un poco de rebote
Un poco de rebote

Usamos un procedimiento llamado antirrebote para superar nuestro problema con los botones. Básicamente, esto espera una cantidad de tiempo específica entre el momento en que se presiona el botón y la respuesta real a la presión. Todavía se siente natural para el usuario (a menos que alargue el tiempo). También puede utilizarlo para comprobar la duración de la pulsación, de modo que pueda responder de forma diferente cada vez. ¡No es necesario cambiar ninguno de los cables!

Veamos el código:

# definir botón 2 # definir debounceTimeout 100

El primer cambio está en el ámbito global. Recordará que ahí es donde definimos las variables que muchas de nuestras funciones podrían usar o aquellas que no se pueden restablecer cada vez que se activa el bucle. Entonces, agregamos debounceTimeout a las constantes definidas. Hicimos este 100 (que luego se traducirá en 100 ms), pero podría ser más corto. Un poco más y se sentirá antinatural.

long int lastDebounceTime;

Esta variable se declara debajo de las constantes. Este es un tipo int largo, que básicamente nos permite almacenar números largos en la memoria. Lo llamamos lastDebounceTime.

No necesitamos cambiar nada en la función void setup (). Dejemos ese.

void loop () {// Leer el pin digital para verificar el estado del botón int presionado = digitalRead (botón); long int currentTime = millis (); // Código del botón}

El primer cambio que hacemos en la función loop () está debajo de la llamada para leer el botón. Necesitamos realizar un seguimiento de la hora actual. La función millis () devuelve la hora actual del reloj desde que Arduino arrancó en milisegundos. Necesitamos almacenar esto en una variable de tipo int larga.

Ahora, debemos asegurarnos de que estamos al tanto del tiempo desde que se presionó el botón, por lo que reiniciamos el temporizador cuando no se presiona. Echar un vistazo:

void loop () {// Leer el pin digital para verificar el estado del botón int presionado = digitalRead (botón); long int currentTime = millis (); if (presionado == LOW) {// Restablecer el tiempo de conteo mientras el botón no está presionado lastDebounceTime = currentTime; } // Código del botón}

El algoritmo if (presionado == LOW) comprueba si el botón no está presionado. Si no es así, el código almacena la hora actual desde la última antirrebote. De esa forma, cada vez que se presiona el botón, tenemos un punto en el tiempo a partir del cual podemos verificar cuándo se presionó el botón. Luego podemos hacer un cálculo matemático rápido para ver cuánto tiempo se presionó el botón y responder correctamente. Veamos el resto del código:

void loop () {// Leer el pin digital para verificar el estado del botón int presionado = digitalRead (botón); long int currentTime = millis (); if (presionado == LOW) {// Restablecer el tiempo de conteo mientras el botón no está presionado lastDebounceTime = currentTime; } // El botón se ha presionado durante un tiempo determinado if (((currentTime - lastDebounceTime)> debounceTimeout)) {// Si se alcanza el tiempo de espera, ¡se presiona el botón! Serial.println ("¡Presionado!"); }}

El último bloque de código toma el tiempo actual, resta el último tiempo de antirrebote y lo compara con el tiempo de espera que establecimos. Si es mayor, el código asume que el botón se ha presionado durante ese tiempo y responde. ¡Limpio!

Ejecute su código y verifique que funcione. Si tienes errores, ¡revisa tu código!

Ahora, veamos un ejemplo práctico.

Paso 6: Elaboración de un menú

La elaboración de un menú
La elaboración de un menú

Los botones son interesantes, ¡porque hay tantas posibilidades con ellos! En este ejemplo, vamos a hacer un menú. Supongamos que ha creado este dispositivo realmente genial y necesita que los usuarios puedan cambiar las opciones para activar o desactivar ciertas cosas, o establecer un valor particular para una configuración. ¡Este diseño de tres botones puede hacer eso!

Entonces, para este proyecto necesitamos:

  • Tres botones
  • Tres resistencias ajustadas a 10kΩ

Ya tenemos uno de estos, solo necesitamos los otros dos. Así que agrégalos al tablero. El cableado es un poco más complejo, pero solo porque quería mantenerlo realmente compacto. Puede seguir el mismo patrón para el primer botón o seguir la imagen de arriba.

Los tres botones son una opción de menú abrir / siguiente, una opción de cambio (como en, modificar la configuración) y un botón de menú guardar / cerrar.

Conéctelo, ¡echemos un vistazo al código!

Paso 7: Desglose del código: global

Ok, este va a ser un paso largo, pero voy a repasar cada sección del código.

Primero, veamos las variables globales necesarias.

// Definir constantes # definir menuButton 2 #definir menuSelect 3 # definir menuSave 4 #define debounceTimeout 50 // Definir variables int menuButtonPreviousState = LOW; int menuSelectPreviousState = LOW; int menuSavePreviousState = LOW; long int lastDebounceTime; // Opciones de menú char * menuOptions = {"Comprobar temperatura", "Comprobar luz"}; bool featureSetting = {falso, falso}; bool menuMode = false; bool menuNeedsPrint = false; int optionSelected = 0;

Estos tres bloques son bastante similares a lo que hemos visto antes. En el primero, he definido los tres botones y el tiempo de espera. Para esta parte del proyecto, lo configuré en 50 ms, por lo que se necesita una presión deliberada para que funcione.

El segundo bloque son todas las variables. Necesitamos realizar un seguimiento del buttonPreviousState, y necesitamos realizar un seguimiento del lastDebounceTime. Todas estas son variables de tipo int, pero la última es de tipo largo porque supongo que necesitamos el espacio en la memoria.

El bloque de opciones del menú tiene algunas características nuevas. Primero, el char * (sí, es un asterisco deliberado), que es una variable literal de carácter / cadena. Es un puntero a un almacenamiento estático en la memoria. No puede cambiarlo (como puede hacerlo en Python, por ejemplo). Esta línea char * menuOptions crea una matriz de cadenas literales. Puede agregar tantos elementos de menú como desee.

La variable featureSetting bool es solo la matriz de valores que representa cada elemento del menú. Sí, puede almacenar lo que quiera, simplemente cambie el tipo de variable (todas tienen que ser del mismo tipo). Ahora, puede haber mejores formas de administrar esto, como diccionarios o tuplas, pero esto es simple para esta aplicación. Probablemente crearía uno de estos últimos en una aplicación implementada.

He realizado un seguimiento del menuMode, por lo que si quisiera otras cosas en mi pantalla, podría hacerlo. Además, si tuviera la lógica del sensor, podría pausarla durante la operación del menú, en caso de que algo entre en conflicto. Tengo una variable menuNeedsPrint porque quiero imprimir el menú en momentos específicos, no solo todo el tiempo. Finalmente, tengo una variable optionSelected, por lo que puedo realizar un seguimiento de la opción seleccionada a medida que accedo a ella en varios lugares.

Veamos el siguiente conjunto de funciones.

Paso 8: Desglose del código: configuración y funciones personalizadas

La función setup () es bastante fácil, solo tres declaraciones de entrada:

configuración vacía () {pinMode (menuSelect, INPUT); pinMode (menuGuardar, ENTRADA); pinMode (menuSelect, INPUT); Serial.begin (9600); }

A continuación, se encuentran las tres funciones personalizadas. Veamos los dos primeros, luego el último por separado.

Necesitamos dos funciones que devuelvan alguna información. La razón es que queremos asegurarnos de que esto sea legible por humanos. También ayudará a depurar el código si tenemos un problema. Código:

// Función para devolver la opción seleccionada actualchar * ReturnOptionSelected () {char * menuOption = menuOptions [optionSelected]; // Opción de retornoSelected return menuOption; } // Función para devolver el estado de la opción seleccionada actualmente char * ReturnOptionStatus () {bool optionSetting = featureSetting [optionSelected]; char * optionSettingVal; if (optionSetting == false) {optionSettingVal = "False"; } else {optionSettingVal = "True"; } // Devolver optionSetting return optionSettingVal; }

La función char * ReturnOptionSelected () verifica la opción seleccionada (si ve arriba, configuramos una variable para realizar un seguimiento de eso) y extrae la cadena literal de la matriz que creamos anteriormente. Luego lo devuelve como un tipo char. Sabemos esto porque la función indica el tipo de retorno.

La segunda función, char * ReturnOptionStatus () lee el estado de la opción guardada en la matriz y devuelve una cadena literal que representa el valor. Por ejemplo, si la configuración que hemos almacenado es falsa, devolvería "False". Esto se debe a que le mostramos al usuario esta variable y es mejor mantener toda esta lógica junta. Podría hacerlo más tarde, pero tiene más sentido hacerlo aquí.

// Función para alternar el optionbool actual ToggleOptionSelected () {featureSetting [optionSelected] =! FeatureSetting [optionSelected]; devuelve verdadero; }

La función bool ToggleOptionSelected () es una función de conveniencia para cambiar el valor de la configuración que hemos seleccionado en el menú. Simplemente cambia el valor. Si tuviera un conjunto de opciones más complejo, esto podría ser bastante diferente. Devuelvo verdadero en esta función, porque mi devolución de llamada (la llamada más adelante en el código que activa esta función) espera una respuesta verdadera / falsa. Estoy 100% seguro de que esto funcionará, así que no tuve en cuenta que no funcionaba, pero lo haría en una aplicación implementada (por si acaso).

Paso 9: El bucle …

La función loop () es bastante larga, así que la haremos por partes. Puede asumir que todo lo que está debajo se anida dentro de esta función:

bucle vacío () {

// Trabaja aquí <-----}

Ok, vimos estas cosas antes:

// Leer los botones int menuButtonPressed = digitalRead (menuButton); int menuSelectPressed = digitalRead (menuSelect); int menuSavePressed = digitalRead (menuSave); // Obtiene el tiempo actual largo int currentTime = millis (); if (menuButtonPressed == LOW && menuSelectPressed == LOW && menuSavePressed == LOW) {// Restablecer el tiempo de conteo mientras el botón no está presionado lastDebounceTime = currentTime; menuButtonPreviousState = LOW; menuSelectPreviousState = LOW; menuSavePreviousState = LOW; }

Todo lo que tenía que hacer aquí era agregar las tres llamadas digitalRead () y asegurarme de tener en cuenta el hecho de que si todos los botones estaban bajos, deberíamos restablecer el temporizador (lastDebounceTime = currentTime) y establecer todos los estados anteriores en bajo. También almaceno millis () en currentTime.

La siguiente sección anida dentro de la línea.

if (((currentTime - lastDebounceTime)> debounceTimeout)) {

// Trabaja aquí <----}

Hay tres secciones. Sí, podría haberlos movido a sus propias funciones, pero por simplicidad guardé los tres algoritmos de los botones principales aquí.

if ((menuButtonPressed == HIGH) && (menuButtonPreviousState == LOW)) {if (menuMode == false) {menuMode = true; // Informar al usuario Serial.println ("El menú está activo"); } else if (menuMode == true && optionSelected = 1) {// Restablecer opción optionSelected = 0; } // Imprime el menú menuNeedsPrint = true; // Alternar el botón anterior. estado para mostrar solo el menú // si el botón se suelta y se presiona de nuevo menuButtonPreviousState = menuButtonPressed; // Sería ALTO}

Este primero maneja cuando menuButtonPressed es HIGH, o cuando se presiona el botón de menú. También verifica que el estado anterior fuera BAJO, por lo que el botón tuvo que soltarse antes de volver a presionarlo, lo que evita que el programa dispare constantemente el mismo evento una y otra vez.

Luego comprueba que si el menú no está activo, lo activa. Imprimirá la primera opción seleccionada (que es el primer elemento en la matriz menuOptions por defecto. Si presiona el botón una segunda o tercera vez (etc.), obtendrá la siguiente opción en la lista. Algo que podría arreglar es que cuando llega al final, vuelve al principio. Esto podría leer la longitud de la matriz y hacer que el ciclo de regreso sea más fácil si cambia el número de opciones, pero esto era simple por ahora.

La última pequeña sección (// Imprime el menú) obviamente imprime el menú, pero también establece el estado anterior en ALTO para que la misma función no se repita (vea mi nota anterior sobre cómo verificar si el botón estaba anteriormente BAJO).

// se presiona menuSelect, proporcionar la lógica si ((menuSelectPressed == HIGH) && (menuSelectPreviousState == LOW)) {if (menuMode) {// Cambiar la opción seleccionada // Por el momento, esto es solo verdadero / falso // pero podría ser cualquier cosa bool toggle = ToggleOptionSelected (); if (alternar) {menuNeedsPrint = true; } else {Serial.println ("Algo salió mal. Vuelva a intentarlo"); }} // Cambiar el estado para cambiar solo si se suelta y se presiona de nuevo menuSelectPreviousState = menuSelectPressed; }

Este fragmento de código maneja el botón menuSelectPressed de la misma manera, excepto que esta vez solo activamos la función ToggleOptionSelected (). Como dije antes, podría cambiar esta función para que haga más, pero eso es todo lo que necesito que haga.

Lo principal a tener en cuenta es la variable de alternancia, que rastrea el éxito de la devolución de llamada e imprime el menú si es verdadero. Si no devuelve nada o es falso, imprimirá el mensaje de error. Aquí es donde puede usar su devolución de llamada para hacer otras cosas.

if ((menuSavePressed == HIGH) && (menuSavePreviousState == LOW)) {// Salir del menú // Aquí puedes hacer cualquier orden // o guardar en EEPROM menuMode = false; Serial.println ("Menú salido"); // Cambiar el estado para que el menú solo salga una vez menuSavePreviousState = menuSavePressed; }}

Esta función maneja el botón menuSave, que simplemente sale del menú. Aquí es donde podría tener una opción para cancelar o guardar, tal vez hacer una limpieza o guardar en la EEPROM. Simplemente imprimo "Menú salido" y configuro el estado del botón en ALTO para que no se repita.

if (menuMode && menuNeedsPrint) {// Hemos impreso el menú, así que a menos que // suceda algo, no es necesario volver a imprimirlo menuNeedsPrint = false; char * optionActive = ReturnOptionSelected (); char * optionStatus = ReturnOptionStatus (); Serial.print ("Seleccionado:"); Serial.print (optionActive); Serial.print (":"); Serial.print (optionStatus); Serial.println (); }

Este es el algoritmo menuPrint, que solo se activa cuando el menú está activo y cuando la variable menuNeedsPrint se establece en true.

Esto definitivamente podría trasladarse a su propia función, ¡pero en aras de la simplicidad …!

¡Bueno, eso es todo! Vea el siguiente paso para todo el bloque de código.

Paso 10: Bloque de código final

// Definir constantes

#define menuButton 2 #define menuSelect 3 #define menuSave 4 #define debounceTimeout 50 int menuButtonPreviousState = LOW; int menuSelectPreviousState = LOW; int menuSavePreviousState = LOW; // Definir variables long int lastDebounceTime; bool lightSensor = true; bool tempSensor = true; // Opciones de menú char * menuOptions = {"Comprobar temperatura", "Comprobar luz"}; bool featureSetting = {falso, falso}; bool menuMode = false; bool menuNeedsPrint = false; int optionSelected = 0; // Función de configuración

configuración vacía () {pinMode (menuSelect, INPUT); pinMode (menuGuardar, ENTRADA); pinMode (menuSelect, INPUT); Serial.begin (9600); }

// Función para devolver la opción seleccionada actualmente char * ReturnOptionSelected () {char * menuOption = menuOptions [optionSelected]; // Opción de retornoSelected return menuOption; } // Función para devolver el estado de la opción seleccionada actualmente char * ReturnOptionStatus () {bool optionSetting = featureSetting [optionSelected]; char * optionSettingVal; if (optionSetting == false) {optionSettingVal = "False"; } else {optionSettingVal = "True"; } // Devolver optionSetting return optionSettingVal; } // Función para alternar la opción actual bool ToggleOptionSelected () {featureSetting [optionSelected] =! FeatureSetting [optionSelected]; devuelve verdadero; } // El bucle principal

void loop () {// Leer los botones int menuButtonPressed = digitalRead (menuButton); int menuSelectPressed = digitalRead (menuSelect); int menuSavePressed = digitalRead (menuSave); // Obtiene el tiempo actual largo int currentTime = millis (); if (menuButtonPressed == LOW && menuSelectPressed == LOW && menuSavePressed == LOW) {// Restablecer el tiempo de conteo mientras el botón no está presionado lastDebounceTime = currentTime; menuButtonPreviousState = LOW; menuSelectPreviousState = LOW; menuSavePreviousState = LOW; } if (((currentTime - lastDebounceTime)> debounceTimeout)) {// ¡Si se alcanza el tiempo de espera, botón presionado!

// menuButton está presionado, proporciona lógica

// Solo se dispara cuando el botón se ha soltado previamente if ((menuButtonPressed == HIGH) && (menuButtonPreviousState == LOW)) {if (menuMode == false) {menuMode = true; // Informar al usuario Serial.println ("El menú está activo"); } else if (menuMode == true && optionSelected = 1) {// Restablecer opción optionSelected = 0; } // Imprime el menú menuNeedsPrint = true; // Alternar el botón anterior. estado para mostrar solo el menú // si el botón se suelta y se presiona de nuevo menuButtonPreviousState = menuButtonPressed; // Sería ALTO} // Se presiona menuSelect, proporcionar lógica if ((menuSelectPressed == HIGH) && (menuSelectPreviousState == LOW)) {if (menuMode) {// Cambiar la opción seleccionada // Por el momento, esto es solo verdadero / falso // pero podría ser cualquier cosa bool toggle = ToggleOptionSelected (); if (alternar) {menuNeedsPrint = true; } else {Serial.print ("Algo salió mal. Vuelva a intentarlo"); }} // Cambiar el estado para cambiar solo si se suelta y se presiona de nuevo menuSelectPreviousState = menuSelectPressed; } if ((menuSavePressed == HIGH) && (menuSavePreviousState == LOW)) {// Salir del menú // Aquí puedes hacer cualquier orden // o guardar en EEPROM menuMode = false; Serial.println ("Menú salido"); // Cambiar el estado para que el menú solo salga una vez menuSavePreviousState = menuSavePressed; }} // Imprime la opción de menú actual activa, pero solo imprímela una vez if (menuMode && menuNeedsPrint) {// Hemos impreso el menú, así que a menos que // suceda algo, no es necesario volver a imprimirlo menuNeedsPrint = false; char * optionActive = ReturnOptionSelected (); char * optionStatus = ReturnOptionStatus (); Serial.print ("Seleccionado:"); Serial.print (optionActive); Serial.print (":"); Serial.print (optionStatus); Serial.println (); }}}

El circuito está disponible en el sitio de Tinkercad. ¡He incrustado el circuito de abajo para que lo veas también!

Como siempre, si tiene preguntas o problemas, ¡hágamelo saber!

Recomendado: