Saltar al contenido principal
LibreTexts Español

Arduino Interruptus - o Venganza del Hijo del Temporizador de Reacción Redux Revisited

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

    El concepto de interrupciones se presentó en el ejercicio Contador de Eventos como medio para responder a eventos externos de manera precisa y oportuna. Las interrupciones se pueden utilizar de otras maneras. Un buen ejemplo sería mantener una pantalla multiplexada, como las utilizadas en los ejercicios Reaction Timer y Event Counter. Como se presentó originalmente, el código de visualización es un poco problemático ya que se ejecuta en el bucle principal y desperdicia muchos ciclos de procesador mediante el uso de llamadas delay (). El otro código se ve obligado a “encajar” en ese momento. Un mejor esquema sería usar interrupciones de software para generar el tiempo para el escaneo de dígitos de visualización. Se programaría un temporizador para activar una interrupción cada 10 milisegundos más o menos, correspondiente al tiempo de escaneo individual para cada dígito de visualización. Cuando se activa, el ISR apagaría la pantalla y luego habilitaría el siguiente dígito, trabajando en forma de round-robin de unidades de dígito, diez dígitos, cien dígitos, unidades de dígito, diez dígitos, y así sucesivamente. Casi todo el código de visualización original podría usarse para crear el ISR. El ISR solo necesitaría una variable adicional para realizar un seguimiento del dígito actualmente iluminado. Además, se tendría que configurar el temporizador/contador para lograr una tasa de activación adecuada.

    Si usamos una preescala de 1024 con un reloj de 16 MHz, las marcas de sincronización aparecen a aproximadamente 16 kHz. Usando el modo Normal, un conteo completo de 0 a 255 tomaría alrededor de 16 milisegundos. Con tres dígitos, esto requiere casi 50 milisegundos para un escaneo y probablemente producirá artefactos de escaneo notables en la pantalla (es decir, un efecto de “latido” o “pulsante”). Desafortunadamente, el 328p del Uno no tiene una preescala 512 (siendo quizás el 256 un poco demasiado rápido). La solución a esto es acortar el tiempo precargando el registro del contador para que no tenga tan lejos para contar (por ejemplo, en lugar de contar de 0 a 255, podríamos contar de 150 a 255). Esta es una técnica agradable para recordar cuando necesitamos obtener valores de tiempo específicos. En este caso, podemos ajustar el valor inicial “a ojo” sin recurrir a cálculos precisos.

    El código que se presenta a continuación se basa en el código original del Contador de Eventos y asume hardware idéntico. Primero, necesitamos agregar la inicialización para temporizador/contador número 2 en la función setup ():

      // Set up timer-counter for muxed 7 seg displays
      TCCR2A = 0;               //  normal mode, OC2x pin disconnected
      TCCR2B = 0x07;            // 1024x prescale tics at ~16kHz
      TCNT2 = OVF_COUNT_START;  // init counter: count up from here to 256
      TIMSK2 = (1<<TOIE2);      // enable overflow interrupt
    

    También introducimos una constante que nos permitirá ajustar el tiempo de escaneo, con valores mayores produciendo tiempos de escaneo más rápidos.

    #define OVF_COUNT_START 150
    

    Ahora levantamos el código de visualización de la función old loop () y lo usamos para formar el ISR para la interrupción de desbordamiento. Necesitamos crear una variable global para realizar un seguimiento del dígito actualmente activo y también necesitamos eliminar cualquier referencia a la función delay () ya que ya no se está utilizando.

    char g_current_digit=0; // 0 for units, 1 for tens, 2 for hundreds
    
    ISR(TIMER2_OVF_vect)
    {
       unsigned int v;
       unsigned char h, t, u; // hundreds, tens, units
    
       // Break the global count variable into units, tens and hundreds digits
       // Grab a local in case another interrupt comes by
       v = g_count;
    
       if( v > 999 ) // error code
       {
          h = LETTER_E;
          t = u = LETTER_R;
       }
       else
       {
          u = v%10;
          v = v/10;
          t = v%10;
          h = v/10;
      
          // blank leading zeros
          if( !h )
          {
              h = LETTER_BLANK;
              if( !t )
                 t = LETTER_BLANK;
          }
       }
    
       // clear all displays then activate the desired digit
       PORTB |= DIGIT111;
    
       if(!g_current_digit) // use units digit
       {
          PORTB &= ~DIGIT1;
          PORTD = numeral[u];
       }
       else
       {
          if(g_current_digit==1) // use tens digit
          {
             PORTB &= ~DIGIT10;
             PORTD = numeral[t];
          }
          else        // must be hundreds digit
          {
             PORTB &= ~DIGIT100;
             PORTD = numeral[h];
          }
       }
       g_current_digit++;     // prep for next digit on next iteration
       g_current_digit %= 3;  // wrap back to units
    
      TCNT2 = OVF_COUNT_START;  // preload counter: count up from here to 255.
    }
    

    La comparación del código anterior con el original muestra que se requirió muy poca modificación. Ahora que la actualización de la pantalla se ha eliminado del bucle del evento principal, no hay nada que hacer ahí:

    void loop()
    {
    }
    

    La pregunta obvia en esta coyuntura es: “¿Cuál es el punto de tener una función de bucle vacío?” Por sí mismo, no mucho, pero en una situación en la que otros dispositivos de entrada y/o salida, sensores y similares necesitan ser monitoreados o configurados, la nueva función loop () se puede diseñar con solo esa utilidad en mente, y sin la preocupación de insertar bits específicos de código de temporización en ella. Esto hace que el sistema sea mucho más limpio y fácil de mantener. Esencialmente, has “entregado” el mantenimiento de la pantalla a un proceso automático en segundo plano.

    En las pruebas de esta nueva versión, vale la pena compararla primero con la versión original para ver si el rendimiento es idéntico. En segundo lugar, pruebe varios valores diferentes de OVF_COUNT_START para ver el efecto del parpadeo de visualización.


    This page titled Arduino Interruptus - o Venganza del Hijo del Temporizador de Reacción Redux Revisited 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.