Saltar al contenido principal
LibreTexts Español

10.1: Introducción

  • Page ID
    83252
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    En este ejercicio combinaremos el procesamiento digital de entrada y salida junto con funciones de cronometraje y generación de números aleatorios para crear un juego sencillo. El objetivo del juego será probar el tiempo de reacción del jugador (técnicamente, estaremos midiendo el tiempo de respuesta que es la suma del tiempo de reacción más el tiempo de movimiento resultante). En lugar de solo mirar el código, este ejercicio también nos permitirá dar un paso atrás y mirar el diseño del sistema y los problemas simples de la interfaz de usuario.

    El juego le dará al jugador cinco intentos para lograr su mejor tiempo de reacción. Cada intento consistirá en activar una luz roja de advertencia como medio para decirle al jugador que se prepare. Entonces, una segunda luz verde (es decir, “ir”) se activará entre uno y diez segundos después, siendo el tiempo preciso aleatorio entre intentos. Una vez que se enciende la luz verde, el jugador necesita presionar un interruptor. La cantidad de tiempo entre el encendido de la luz verde y la activación del interruptor de respuesta es el tiempo de reacción del jugador. Al jugador se le dirá este valor y al finalizar los cinco intentos, se mostrará el mejor resultado. El juego también tiene que resguardarse contra las trampas. Es decir, tiene que desautorizar un golpe que simplemente anticipa la luz verde disparando, es decir, “saltando el arma”.

    Antes de considerar el hardware y el software, necesitamos tener alguna idea de lo que estamos midiendo. En general, ¿qué tan rápido es el tiempo de respuesta humana? Resulta que esto es en parte una función del estímulo (las señales audibles son procesadas más rápidamente por el cerebro humano que las señales visuales). Los tiempos de respuesta típicos son alrededor de 160 milisegundos para un estímulo auditivo y 190 milisegundos para un estímulo visual, aunque atletas profesionales como los velocistas olímpicos podrían en ocasiones lograr un valor de solo 2/3rds de este 1. Obviamente entonces, nuestro hardware y software necesitan ser mucho más rápidos si queremos lograr un nivel decente de precisión.

    La interfaz de usuario consta de cuatro cosas: una luz roja, una luz verde, el interruptor de respuesta del jugador y algunos medios de salida de texto. Para simplificar y facilitar la creación de prototipos, utilizaremos el Monitor Serial para la salida de texto. Si esto se convirtiera en un elemento de producción, el monitor podría reemplazarse por una pantalla LCD dedicada. En cuanto a las luces, los LED parecen ser una opción obvia quizás por su familiaridad y la inmediatez personal uno a uno del juego (si varias personas respondieran sincronizadas, podríamos necesitar una fuente de luz mucho más grande o más brillante que un LED ordinario). Hay otro ítem a considerar y es la velocidad de encendido de la luz. Los LED se encienden en una fracción de milisegundo, mientras que las luces incandescentes podrían no alcanzar el brillo completo durante decenas o incluso cientos de milisegundos. Como podríamos estar esperando tiempos de respuesta en la región de 200 milisegundos, una incandescencia puede no ser lo suficientemente rápida. Es decir, el retraso de tiempo requerido para que alcance el brillo completo podría sesgar los resultados, ralentizando el tiempo de respuesta aparente (medido) del jugador. Entonces, parece que los LED simples serían una buena opción aquí.

    El ítem final es el cambio del jugador. El jugador podría golpear el interruptor bastante fuerte porque querrá mover su mano lo más rápido posible para minimizar el tiempo de movimiento. En consecuencia, el interruptor necesita ser bastante robusto. También debe representar un objetivo de tamaño decente para el jugador, es decir, uno que sea lo suficientemente grande como para que el jugador no tenga que preocuparse también por hacer un movimiento muy preciso. Si bien un simple pulsador de contacto momentáneo puede parecer una elección decente, un FSR podría resultar mejor. El FSR no contiene partes móviles y proporciona una gran área objetivo. También responde casi instantáneamente y no sufre de rebote de interruptor. La interconexión también es fácil: simplemente podemos conectarlo entre un pin de entrada y tierra, y habilitar el pull-up interno. Normalmente el FSR es una resistencia muy alta pero cuando se presiona el valor cae a tal vez 100 ohmios o así. Esto sería en serie con el pull-up que está conectado a la fuente de alimentación. Con el FSR intacto, la regla del divisor de voltaje indica que el voltaje del pin flotará alto. Cuando se deprime el FSR, el voltaje se hundirá a un nivel lógico bajo. El único inconveniente de la FSR es la falta de retroalimentación física. Muchos interruptores hacen un sonido de clic cuando están conectados. Esto proporciona retroalimentación auditiva al jugador para que sepa que su strike se ha registrado. Podemos lograr algunos comentarios de los jugadores iluminando uno o ambos LEDs en el momento en que se detecta la huelga.

    Ahora tenemos una idea de los requisitos potenciales de hardware. ¿Qué pasa con el software? Obviamente, las técnicas previamente examinadas para entrada y salida digitales se pueden utilizar para leer y controlar el interruptor del reproductor y los LED. La función delay () se puede utilizar para cronometrar los flashes LED y el retardo principal de “ir”. Sin embargo, se necesitan dos nuevos elementos: alguna forma de generar valores aleatorios (en nuestro caso, entre 1000 milisegundos y 10,000 milisegundos) y algún método para medir el tiempo entre dos eventos. Consideremos primero el número aleatorio.

    Generalmente, los dispositivos de computación digital no son buenos para generar valores verdaderamente aleatorios porque son máquinas deterministas. De hecho, tratamos de “diseñar” toda la aleatoriedad potencial porque necesitamos una operación altamente predecible. Los valores verdaderamente aleatorios no están de ninguna manera correlacionados entre sí. Es decir, no hay nada en una cadena de valores aleatorios que te permita determinar el siguiente valor en la secuencia. Resulta, sin embargo, que podemos crear secuencias pseudo-aleatorias sin mucho esfuerzo. Una secuencia pseudo-aleatoria parece ser aleatoria pero si ejecutas la secuencia lo suficientemente larga, se repetirá. Las secuencias pseudoaleatorias pueden no ser lo suficientemente aleatorias para los esfuerzos de investigación científica adecuados, pero son suficientes para algo como nuestro juego. Si bien el proceso de derivar números pseudoaleatorios está más allá del alcance de este ejercicio, podemos decir que el proceso implica algún procesamiento bastante rudimentario, como el desplazamiento de bits, en un valor inicial denominado semilla. El resultado de este cálculo se utiliza entonces como entrada para calcular el siguiente valor y así sucesivamente.

    La biblioteca Arduino incluye una función, random (), que devolverá un valor entero largo pseudo-aleatorio. También se puede llamar a la función con límites superior e inferior para constreñir los valores devueltos como en random (30,200). La secuencia en sí se inicia a través de la función randomSeed () que toma un argumento entero largo. Un efecto a veces útil de esto es que si el valor semilla es el mismo para ejecuciones posteriores del programa, la secuencia pseudoaleatoria resultante se repetirá exactamente. Esto permite una depuración algo sistemática de una característica supuestamente aleatoria. Pero esto plantea un problema, es decir, ¿cómo obtenemos un número aleatorio para que comience la semilla? Si bien es posible pedirle al usuario un valor semilla, eso abre la puerta a posibles trampas. Una mejor manera es usar un voltaje de ruido. Por su propia naturaleza, el ruido es aleatorio. Si nos fijamos en un pin de entrada analógica que no está conectado a nada, veremos un pequeño voltaje de ruido. Podemos digitalizar este valor verdaderamente aleatorio con la función analogRead () y usarlo para la semilla. Este voltaje no cambiará en un rango muy grande, pero cambiará una cantidad suficiente para hacer que nuestra secuencia pseudo-aleatoria aparezca realmente muy aleatoria.

    El siguiente código muestra cómo funciona el sistema de números aleatorios. Ingrese el código, compile y transfiéralo, y luego ejecútelo. Primero imprimirá el voltaje de ruido y luego imprimirá valores aleatorios entre 50 y 1000. Después de ver un número suficiente de valores, presiona el botón de reinicio en el tablero para reiniciar el código. Haz esto varias veces. Estos reinicios deben producir diferentes secuencias. De vez en cuando es posible que obtenga el mismo voltaje de ruido. Si ve esto, observe que la secuencia numérica pseudo-aleatoria se repetirá exactamente.

    void setup()
    {
          long i;
         
          Serial.begin(9600);
    
          i=analogRead(0); // any analog channel will do
          randomSeed(i);
    
          Serial.print(“Noise voltage: ”);
          Serial.println(i);
    }
    
    void loop()
    {
          long a;
    
          a = random(50,1000);
          Serial.println(a);
          delay(2000);
    }
    

    Esta técnica debería funcionar perfectamente para nuestra aplicación. Todo lo que necesitamos hacer es constreñir la función aleatoria a 1000 a 10,000 para que podamos usar los valores directamente como el argumento de milisegundos para la llamada delay () utilizada para aleatorizar la iluminación del LED verde “go”.

    La segunda parte que necesitamos consiste en medir el retardo de tiempo entre la iluminación del LED “go” y el jugador golpeando el FSR. Normalmente, si estuvieras haciendo esto desde cero tendrías que inicializar uno de los temporizadores del ATMega 328P y verificar su contenido cuando sea necesario. El cronometraje de eventos es una necesidad común en el mundo integrado, por lo que la rutina de inicialización de Arduino configura un temporizador para usted. En el instante en que enciendes o reinicias la placa, este temporizador interno comienza a incrementar un registro que se inicializa a cero. El registro se incrementa en uno por milisegundo. Este temporizador sigue funcionando independientemente e independiente del código principal que se esté ejecutando (a menos que el código principal reprograma el temporizador, por supuesto). En consecuencia, el valor del registro indica cuántos milisegundos han pasado desde que se encendió o restableció la placa. El contenido de este registro puede leerse a través de la función millis (). Es decir, millis () devuelve el número de milisegundos que han ocurrido desde el último reinicio/encendido. Después de unos 50 días, este registro se desbordará y el conteo comenzará de nuevo. Es dudoso que algún individuo quiera jugar a este juego tanto tiempo continuamente por lo que esto no debería ser tema. El siguiente fragmento de código hará lo que necesitamos:

    starttime = millis();
    
    // Flash “go” LED
    
    // wait for response from player pressing FSR, looking at input port pin
    
    finishtime = millis();
    responsetime = finishtime - starttime;
    

    Entonces, ahora podemos idear un pseudo código para la porción de bucle del juego:

    1. Initialize best response time as a very high value (long int)
    2. Tell user to start game by hitting FSR pad
    3. Wait for the player’s start response of pressing the FSR
    4. Start a loop for five tries, for each try:
          a. Generate a random number between 1000 and 10,000
          b. Wait a second or two to give the user a moment to get ready
          c. Flash red LED one or more times quickly to indicate the start 
          of this try
          d. Delay the random amount of milliseconds from 4.a
          e. Record start time millis
          f. Flash green “go” LED quickly
          g. Wait for player’s response of pressing FSR
          h. Record finish time and calculate response time millis
          j. Flash both red and green LEDs for visual feedback
          k. If response time is less than 100 msec, declare a cheat and 
          reset response time to a high penalty value
          l. Print out the response time
          m. Compare response time to best time so far, resetting best to 
          this value if this value is smaller (i.e., better)
          (Loop complete)
    5. Print out the best value
    6. Wait a moment before starting next game
    

    Algunos de estos pasos podrían necesitar más aclaraciones o ajustes. En el paso 4.k, se declara un “tramposo” si el tiempo de respuesta es inferior a 100 milisegundos. Esto es probablemente generoso dadas las estadísticas señaladas anteriormente y podría ajustarse a un valor mayor. El valor de penalización aquí podría ser el mismo que el valor de inicialización, quizás 10,000 o más milisegundos. Después de todo, si a un individuo por lo demás normal le toma más de 10 segundos responder al estímulo, uno podría suponer que está ebrio, fácilmente confundido o necesita dejar de enviar tanto mensajes de texto.

    Otro elemento de importancia es el código para los pasos 3 y 4.g. En esencia, el código necesita “esperar” al jugador golpeando la FSR. Si el FSR está cableado a un pin de entrada utilizando una resistencia interna de pull-up, presionar el FSR hará que la entrada digital en ese pin pase de alta a baja. (Recuerde, presionar la FSR crea una resistencia baja y la FSR está básicamente en serie con la resistencia pull-up. Por lo tanto, la regla del divisor de voltaje muestra que el voltaje entre los dos elementos cae a un valor muy bajo, o un valor lógico bajo). En consecuencia, el código puede “esperar ocupado” en ese pin, rompiendo cuando el pin va bajo. Podríamos conectar el FSR al pin 8 de Arduino, que es el bit 0 del puerto B. Además, podríamos evitar codificar esto usando un #define:

    #define FSRMASK  0x01
    

    Entonces el código de prueba de bits requerido se ve así:

    while( PINB & FSRMASK );      // DON’T use PORTB!!  

    Recuerda, para entrada miramos a PinX y para salida usamos PortX. Entonces este bucle sigue funcionando siempre y cuando el bit de entrada FSR sea alto. Tan pronto como el jugador empuja el FSR con una fuerza razonable, el pin baja y el bucle termina.

    Podemos agilizar un poco más nuestro código con respecto a los pasos 4.c, 4.f y 4.h que parpadean un LED. Podríamos conectar los LED verde y rojo a los pines Arduino 6 y 7 (puerto D.6 y D.7) y usar #define s junto con una función que enciende y apaga un LED rápidamente:

    // At D.6
    #define GREENLED 0x40
    // At D.7
    #define REDLED   0x80
    // on/off time in msec
    #define LEDTIME  30
    
    void FlashLED( int mask )
    {
       PORTD |= mask;    // turn on LED
       delay(LEDTIME);
       PORTD &= (~mask); // turn off LED
       delay(LEDTIME);
    }
    

    Esta función se llamaría así para un solo flash rápido:

    FlashLED(GREENLED);
    

    También podría colocarse en un bucle para una serie de flashes rápidos y podría bit a bit O los valores de la máscara para destellar los dos LEDs simultáneamente.

    El último punto de preocupación es el paso 6, “Espera un momento antes de comenzar el próximo juego”. ¿Por qué incluiríamos esto? El motivo no es obvio. Recuerda que el microcontrolador procesará todo en muy poco tiempo, tal vez solo transcurrirán unos milisegundos entre el momento en que el jugador golpea el FSR, se imprimen los valores finales y termina la función loop (), solo para ser llamado nuevamente por el invisible main (). Al reingresar, en loop (), el código le dice al jugador que presione el FSR para iniciar el siguiente juego y luego espera en el bit FSR. En ese corto periodo de tiempo el jugador no habrá tenido oportunidad de levantar el dedo. En otras palabras, el bit FSR sigue siendo bajo por lo que el bucle termina y ¡el trial one se ejecuta de inmediato! Para evitar esto, podemos ingresar un pequeño retraso ya que la persona promedio no seguirá presionando el FSR una vez que se registre el tiempo de respuesta, o podríamos usar un FSR separado para iniciar el juego. El primero parece mucho más sencillo.

    Según los fragmentos de código anteriores, el código de configuración podría verse así:

    // Green LED at D.6, red at D.7, FSR at B.0, flash time in msec
    #define GREENLED 0x40
    #define REDLED   0x80
    #define FSRMASK  0x01
    #define LEDTIME  30
    
    void setup()
    {
       Serial.begin(9600);
    
       // set Arduino pins 6 & 7 for output to LEDs
       DDRD |= (GREENLED | REDLED);
       // Arduino pin 8 for input from FSR
       DDRB &= (~FSRMASK);   // initialize the pin as an input.
       PORTB |= FSRMASK;     // enable pull-up  
    
       // get seed value for random, noise voltage at pin A0
       randomSeed(analogRead( 0 ));
    }

    This page titled 10.1: Introducción is shared under a CC BY-NC-SA 4.0 license and was authored, remixed, and/or curated by James M. Fiore via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.