Saltar al contenido principal
LibreTexts Español

11.1: Introducción

  • Page ID
    83191
  • \( \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}}\)

    A veces es agradable volver a visitar un proyecto pasado para investigar un enfoque diferente del diseño. Aquí vamos a reelaborar el ejercicio Reaction Timer. Si bien ciertamente fue útil como ejercicio instructivo, la limitación obvia en lo que respecta al juego en sí es el hecho de que la placa de desarrollo necesitaba estar conectada a una PC host que ejecutara el software Arduino porque confiaba en el Serial Monitor para la salida numérica. En cambio, vamos a hacer un temporizador de respuesta que sea autónomo. Todo lo que necesitará es una fuente de energía. Hay algunas formas diferentes de lograr una salida numérica, quizás la más directa es mediante el uso de pantallas LED de siete segmentos. Estos dispositivos nos permitirán mostrar los diez números más algunos caracteres alfabéticos (pero no todo el alfabeto inglés sin ambigüedades).

    Para facilitar la codificación, prescindiremos de los “extras” del juego, es decir, determinar el mejor de cinco intentos, el promedio, etc.; y en su lugar nos centraremos en la interfaz de las pantallas con un temporizador de respuesta básico. El funcionamiento general será similar: Las pantallas parpadearán rápidamente para decirle al usuario que “se prepare”, habrá un retraso de tiempo aleatorio entre uno y diez segundos, luego las pantallas parpadearán rápidamente ya que el estímulo desencadenante en cuyo punto el jugador presionará el botón de respuesta (FSR) lo más rápido posible. El tiempo de respuesta se mostrará en las pantallas durante unos segundos y después de una breve pausa, el proceso se repetirá. El esquema funcional, o pseudo-código, es muy similar al del juego original. El problema principal es el cambio del dispositivo de salida de Serial Monitor a pantallas de siete segmentos, tanto en hardware como en software.

    En lo que respecta al resto del hardware, ya no necesitaremos los LEDs de señalización aunque todavía necesitaremos el interruptor del reproductor (FSR). No esperamos tiempos de reacción más rápidos que los 100 milisegundos y es dudoso que los individuos sanos exhiban tiempos de respuesta más lentos que un segundo, asumiendo que no están distraídos u operando dentro de las limitaciones de alguna manera de ayuda química de diversión de moda que realiza una (ojalá) temporal autoinfligido error de encendido de su red neuronal. En consecuencia, tres pantallas deberían ser suficientes, lo que nos permite renderizar de 0 a 999 milisegundos.

    Tres pantallas requerirán 21 bits de puerto si los manejamos de forma independiente. Esto es sobre todo lo que tenemos en el Arduino Uno así que no es una solución práctica. En su lugar, podemos multiplexar en tiempo las pantallas. En esencia, renderizaremos el dígito de los cientos en la pantalla más a la izquierda durante unos milisegundos, luego renderizaremos el dígito de las decenas en la pantalla central durante unos milisegundos, y finalmente renderizaremos el dígito de las unidades en la pantalla más a la derecha durante unos milisegundos. Después vamos a repetir el proceso. Siempre y cuando mantengamos el tiempo de bucle general a menos de aproximadamente 25 milisegundos, nuestro procesamiento ojo-cerebro integrará la pantalla a lo largo del tiempo para que parezca ser un conjunto sólido e inquebrantable de tres dígitos. Para ello solo necesitaremos diez bits de puerto; siete para los segmentos y tres más para multiplexar las pantallas individuales. Si usamos pantallas de ánodo comunes (por ejemplo, FND507) podríamos configurarlas como se muestra en la Figura\(\PageIndex{1}\).

    clipboard_ea83ed9e8178158e8929b29609ed61102.png

    Figura\(\PageIndex{1}\): Configuración de pantalla de siete segmentos

    La lógica para este circuito será “bajo activo”, lo que significa que una lógica baja ilumina un segmento/pantalla y una lógica alta lo apaga. Tenga en cuenta que los controladores LED no se muestran aquí, más bien, los segmentos están conectados directamente a los pines del puerto a través de resistencias limitadoras de corriente, Rs. El Arduino tendrá suficiente capacidad de corriente para esto si mantenemos el segmento actual a un nivel modesto. Suponiendo un suministro de 5 voltios y aproximadamente 2 voltios para los LEDs, un 470\(\Omega\) mantendría las corrientes alrededor de 6 o 7 miliamperios por segmento. Esto sería suficiente para uso en interiores. Las pantallas individuales se habilitan mediante el uso de los controladores PNP. Una lógica baja en sus resistencias base los colocará en saturación, permitiendo el flujo de corriente a la pantalla. Para esta aplicación, bastará con una pequeña señal PNP como la 2N3906. Tener todos los segmentos encendidos atraerá alrededor de 50 miliamperios a través del colector. Por lo tanto, un valor de resistencia base en el\(\Omega\) rango k bajo de un solo dígito debería funcionar bien, produciendo una corriente base de unos pocos miliamperios.

    Prácticamente hablando, sería más fácil dedicar el puerto D a los segmentos (D. 1:7, pines Arduino 1—7). Esto significa que perderemos la capacidad de usar el Monitor Serial (usa D. 0:1) pero eso no debería ser un problema. Podemos usar B. 0:2 (pines Arduino 8—10) para los multiplexores de dígitos y B.3 (pin 11) para el FSR. Algunos #define s serían buenos para estos. También necesitaremos una matriz de patrones de bits para las pantallas:

    // Port B.0:2 for 7 segment mux
    #define DIGIT1   0x01
    #define DIGIT10  0x02
    #define DIGIT100 0x04
    #define DIGIT111 0x07
    
    #define FSRMASK  0x08
    
    unsigned char numeral[]={
       //ABCDEFG,dp
       0b00000011,   // 0
       0b10011111,   // 1 
       0b00100101,   // 2
       0b00001101,   // 3
       0b10011001,
       0b01001001,
       0b01000001,
       0b00011111,
       0b00000001,
       0b00011001,   // 9
       0b11111111,   // blank
       0b01100001,   // E
       0b01110011,   // r
       0b00001001,   // g
       0b00111001    // o  
    };
    
    #define LETTER_BLANK  10
    #define LETTER_E      11
    #define LETTER_R      12
    #define LETTER_G      13
    #define LETTER_O      14
    #define MSG_GO        -1
    #define MSG_ERR       -2
    

    La variable numeral es una matriz definida usando valores binarios. Cada bit significa un segmento con el MSB siendo el segmento A (horizontal superior) y el LSB siendo el punto decimal (sin usar aquí). Por ejemplo, el numeral “0” requiere que se encienda cada segmento excepto G (la barra horizontal media) y el punto decimal. Debido a que nuestro circuito está activo bajo, ponemos un 0 en cada bit que se encenderá. Repetimos este proceso para los nueve números restantes. También agregamos algunos caracteres especiales, a saber, en blanco y las letras E, r, g y o. usaremos los dos primeros para deletrear “Err” por error (lo que solíamos llamar un “tramposo”) y los dos segundos para deletrear “go” que ocupará el lugar del antiguo LED verde “go”. También hemos definido un par de variables MSG para ser examinadas en un momento.

    Dadas estas definiciones, echemos un vistazo a cómo ha cambiado la función setup (). Necesitamos cambiar qué pines se están usando pero aún necesitamos el valor semilla para random (). Tenga en cuenta que ya que estamos configurando todos los bits del puerto D en modo de salida, podemos simplemente volarlo en 0xff en lugar de bit o en los bits específicos. También es importante que fijemos los bits mux (DIGIT111) del puerto B alto. Como los pines del puerto D también están bajos por defecto, todas las pantallas se iluminarán cuando se reinicie la placa. Esto provocará un considerable consumo de corriente a través del microcontrolador. Establecer los bits mux altos asegura que todo estará apagado (no habilitado).

    void setup()
    {
       // set Arduino pins 0 through 7 (port D.0:7) for output displays
       DDRD = 0xff;
    
       // set Arduino pins 8 through 10 (port B.0:2) for output to mux LEDs
       DDRB |= DIGIT111;
    
       // Displays are active low so turn them off initially
       PORTB |= DIGIT111;
    
       // Arduino pin 11, port B.3 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 ));
    }
    

    Aquí está nuestro bucle principal, ligeramente alterado. Compárelo línea por línea con la versión original y anote cualquier similitud y alteración. Los comentarios por sí solos deben proporcionar suficiente detalle. Los cambios más obvios son la eliminación del código Serial Monitor y la inclusión de una nueva función llamada displayValue () que se discutirá en breve.

    void loop()
    {
       unsigned long starttime, finishtime;
       int i, j, nettime;
       long a;
      
       for(i=0;i<5;i++)
       {
          a = random(1000, 10000);
    
          // wait a couple of seconds to start this round
          delay(2000);
      
          // flash display a few times to tell player to get ready
          for(j=0;j<5;j++)
          {
             DisplayValue( 888, 100 );
             delay(100);
          }
    
          // delay random amount
          delay(a);
    
          // flash LED for real
          starttime = millis();
          DisplayValue( MSG_GO, 50 );
    
          // wait for response, pressing FSR makes a low
          while( PINB & FSRMASK );
      
          finishtime = millis();
          nettime = finishtime - starttime;
    
          // check for cheat
          if( nettime < 100 )
             nettime = MSG_ERR;
    
          DisplayValue( nettime, 3000 );
       }
    }
    

    Funcionalmente, displayValue () es bastante sencillo de usar. El primer argumento es el número a mostrar, del 0 al 999. El segundo valor es la duración de la visualización en milisegundos. Entonces displayValue (888, 100) significa “mostrar el número 888 por 100 milisegundos”. Esta línea en particular hace que el flash de “prepárate”. displayValue () también permite algunos valores especiales. Estos se codifican como valores negativos. Un tiempo de respuesta negativo es imposible, por supuesto, sin alguna forma de capacidad de viaje en el tiempo. Si el valor se establece en MSG_ERR entonces la pantalla muestra “Err”. Si el valor se establece en MSG_GO entonces la pantalla muestra “go”. De esta manera, el antiguo parpadeo del LED verde “go” se reemplaza por DisplayValue (MSG_GO, 50), produciendo el mensaje “go” durante 50 milisegundos.

    Lo primero que debe hacer la función es validar el valor que se le pasa. Necesita verificar los códigos de mensaje especiales, así como los valores fuera de rango. Sea cual sea el valor que se pase, debemos derivar tres nuevos valores, a saber, los índices para la matriz numérica. Estos se almacenarán en las variables h, t y u que representan cientos lugar, decenas lugar y unidades lugar. Los casos especiales deben ser revisados primero. Más allá de eso, el valor necesita ser desglosado en sus dígitos individuales mediante el uso de divisiones enteras y mods. Las sucesivas divisiones enteras por 10 desplazarán efectivamente el número hacia abajo un lugar mientras que mod por 10 dejará el resto. Piensa en un número de tres dígitos al azar y verifica que la lógica funciona.

    void DisplayValue( int v, int msec )
    {
       unsigned char i, h, t, u;
    
       if( (v <= MSG_ERR) || (v > 999) ) // error code
       {
          h = LETTER_E;
          t = u = LETTER_R;
       }
       else
       {
          if( v == MSG_GO )
          {
             h = LETTER_G;
             t = LETTER_O;
             u = LETTER_BLANK;
          }
          else
          {
             u = v%10;
             v = v/10;
             t = v%10;
             h = v/10;
          }
       }
    

    En este punto necesitamos convertir la duración solicitada en iteraciones de bucle. Según se codifica, cada iteración dura aproximadamente 15 milisegundos dado que cada dígito se ilumina durante 5 milisegundos. Una división simple bastará aunque es posible que deseemos protegernos de alguien que accidentalmente solicite solo unos milisegundos (lo que arrojaría cero).

       // turn millisecs into loop iterations
       msec = msec/15;
       if( msec < 1 )
          msec = 1;
    

    Ahora creamos un bucle que dará aproximadamente la duración solicitada con una resolución de 15 milisegundos. Dentro del bucle, primero limpiamos todas las pantallas. Luego habilitamos la visualización deseada y volamos en el código para el numeral. Tenga en cuenta que al ingresar los valores binarios en la matriz en orden ascendente, simplemente podemos usar el valor de dígito calculado como índice en la matriz. No necesitamos traducir el numeral deseado al índice apropiado. Un poco de previsión puede ahorrar esfuerzo de codificación y espacio de memoria. Cada dígito se mantiene durante 5 milisegundos. Al cierre de la función apagamos la pantalla.

       // display the value for the specified time
       for( i=0; i<msec; i++ )
       {
          // clear all displays then activate the desired digit
          PORTB |= DIGIT111;
          PORTD = numeral[h];
          PORTB &= ~DIGIT100;
          delay(5);
    
          PORTB |= DIGIT111;
          PORTD = numeral[t];
          PORTB &= ~DIGIT10;
          delay(5);
    
          PORTB |= DIGIT111;
          PORTD = numeral[u];
          PORTB &= ~DIGIT1;
          delay(5);   
       }
      
       // clear display
       PORTB |= DIGIT111;
    

    This page titled 11.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.