Saltar al contenido principal
LibreTexts Español

13.2: Interrumpimos Nuestro Código Regularmente Programado...

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

    La solución es el uso de una interrupción. Piense en una interrupción como un esquema mediante el cual un bit especial de código llamado manejador de interrupciones o rutina de servicio de interrupción (ISR) se activa para ejecutarse en condiciones especiales. Se suspende la ejecución del código existente hasta que se haga el ISR. La interrupción puede ser desencadenada por eventos de software o hardware. Un ejemplo típico sería tener la interrupción activada por un cambio de señal en un pin de puerto de entrada. La mayoría de los microcontroladores tienen muchos niveles diferentes de interrupciones y estos se pueden ordenar por importancia. Es decir, algunos eventos pueden tener prioridad sobre otros eventos. Esto significa que puede ser posible tener un ISR interrumpido por una interrupción de mayor prioridad. Generalmente, los ISR deben ser cortos, ejecutando rápidamente fragmentos de código para no obstruir el funcionamiento del código principal. En el caso de nuestro contador de eventos, el interruptor de elemento podría estar conectado a un pin de entrada que desencadena una interrupción. El ISR no haría más que incrementar una variable global que realiza un seguimiento del recuento de artículos. El cuerpo principal del código entonces verificaría periódicamente este global y respondería en consecuencia (en este caso, actualizando la pantalla). Podemos pensar en el esquema ISR como “ejecutándose en segundo plano” verificando constantemente el pin de entrada. Esto nos libera de tener que rociar código de comprobación de puerto en todo el cuerpo principal del programa.

    Normalmente, ninguna de las interrupciones disponibles está activa con la excepción del reinicio del sistema. Estos tienen que ser habilitados individualmente antes de que puedan ser utilizados. El sistema de desarrollo define un nombre para cada rutina de interrupción así que todo lo que tenemos que hacer es habilitar los bits adecuados en los registros de control de interrupciones y rellenar el código para el ISR.

    Para el contador de eventos, vamos a reutilizar una cantidad considerable de código y hardware del ejercicio Reaction Timer Redux, especialmente las pantallas de siete segmentos y el código de multiplexación. El cambio del jugador se intercambiará por el interruptor de conteo de eventos. Haremos uso de la capacidad de interrupción de cambio de pin (PCIRT) del ATMega 328P. Cuando se programa, cada uno de los pines del puerto de entrada puede desencadenar una interrupción cuando su señal de entrada cambia de alto a bajo o bajo a alto. Estos están dispuestos en bancos que corresponden a los puertos, por ejemplo el banco 0 corresponde al puerto B. Cada banco tiene un ISR asociado a él. El banco 0 ISR se llama Pcint0_vect. Esto significa “Pin Change Interrupt Bank 0 Vector” (técnicamente, un vector es una dirección de función por lo que esta es realmente la dirección de inicio de la rutina de servicio de interrupción). Cada uno de los pines de un puerto en particular se activa a través de su propio bit de interrupción. Para el banco 0, estos bits son PCINT0 a PCINT7. Entonces, si los pines de puerto 0 y 1 están habilitados para interrupciones (vía PCINT0 y PCINT1), un cambio en cualquiera de sus pines generará la interrupción de cambio de pin de banco 0 y provocará que se ejecute el ISR PCint0_Vect. Una vez hecho el ISR, la ejecución normal del código principal continuará donde lo dejó.

    Antes de mirar el hardware consideremos algún código. Como se mencionó, podemos modificar el código Reaction Timer Redux existente. Ya no necesitaremos el mensaje especial “go” para que podamos eliminar algunos elementos de la matriz numérica. Otras modificaciones serán mínimas si mantenemos el cableado igual. Usaremos el término genérico “sensor” en lugar del anterior FSR, ya que todavía tenemos que determinar cómo se implementará el interruptor de conteo de eventos. Tenga en cuenta que tener alguna documentación dentro del código original sobre cómo está cableado el circuito nos ahorra reinventar la rueda. ¡Todos los buenos programadores, técnicos e ingenieros hacen esto!

    /* Event counter with common anode 7 segment displays X 3. Displays off of 
    D1:7 (segment a=7), Mux off of B0:2 (hundreds on B.2), w/ PNP drivers. Sensor 
    on B.3. Values > 999 show up as "Err" ALL SEGMENTS AND MUX ACTIVE LOW */
    
    // Port B.0:2 for 7 segment mux
    #define DIGIT1   0x01
    #define DIGIT10  0x02
    #define DIGIT100 0x04
    #define DIGIT111 0x07
    
    #define SENSORMASK  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
    };
    
    #define LETTER_BLANK  10
    #define LETTER_E      11
    #define LETTER_R      12
    

    En este punto necesitamos declarar la variable global antes mencionada y modificar la rutina setup ().

    unsigned int g_count = 0;
    
    void setup()
    {
       // set Arduino pins 0-7, port D.0:7, for output to 7 segment LEDs
       DDRD = 0xff;
    
       // set Arduino pins 8-10, port B.0:2, for output to mux 7 segment LEDs
       DDRB |= DIGIT111;
    
       // Displays are active low so turn them off initially
       PORTB |= DIGIT111;
    
       // Arduino pin 11, port B.3 for input from sensor
       DDRB &= (~SENSORMASK);   // initialize the pin as an input.
       PORTB |= SENSORMASK;     // enable pull-up  
    
       // enable pin change interrupt bank 0
       bitSet(PCICR,PCIE0);
    
       // enable pin change interrupt on port B.3
       bitSet(PCMSK0,PCINT3);
    }
    

    Las dos últimas líneas del código anterior son particularmente importantes. La primera línea habilita la interrupción del banco 0 de cambio de pin (bit PCIE0 o banco 0 de habilitación de interrupción de cambio de pin) en el registro de control de interrupción de cambio de pin (PCICR). La segunda línea especifica entonces qué pin (s) en el banco 0 se utilizará (Pin Change Interrupt pin 3 o PCINT3) en el registro Pin Change Mask para el banco 0 (PCMSK0).

    Una vez habilitado, ahora necesitamos escribir el propio ISR. Como se mencionó, esta rutina ya tiene un nombre (Pcint0_vect) y todo lo que necesita hacer es incrementar la variable de conteo global.

    /* This is the bank 0 interrupt handler. It triggers on any change to the appropriate pin(s) of bank 0 (port B), either H->L or L->H. */
    
    ISR(PCINT0_vect)
    {
       // increment only on sensor going low to avoid double counting
       if( !(PINB & SENSORMASK) )
          g_count++;
    }
    

    La función anterior loop () puede ser lanzada. En su lugar utilizamos los contenidos de la antigua función displayValue () que controlaba la multiplexación de la pantalla. A diferencia del original, esto no renderiza la pantalla durante un tiempo especificado. Más bien, realiza un solo escaneo de las tres pantallas para un total de 15 milisegundos. loop () se llamará de nuevo y el proceso se repite.

    Al inicio, el contenido de la variable de recuento global se copia a una variable local que luego se desarma para determinar los índices en la matriz numérica como antes. Tenga en cuenta un refinamiento menor, a saber, el fragmento de código agregado a los ceros iniciales “en blanco”. Sin embargo, podría quedar una pregunta, y es decir, ¿por qué molestarse en hacer una copia local de la variable global? El motivo es porque una interrupción puede ocurrir en cualquier momento. Eso significa que a mitad del código de índice h, t, u podría ocurrir una interrupción y la variable global cambiará. Si esto sucede los índices calculados serán incorrectos porque el código estará operando en dos (o posibles más) valores diferentes. Si hacemos una copia local esto no sucederá. Solo cambiará el global y la pantalla se actualizará apropiadamente la próxima vez que se llame al loop ().

    void loop()
    {
       unsigned int v;
       unsigned char h, t, u; // hundreds, tens, units
    
       // 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;
       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;
    }
    

    En este punto tenemos algún código viable. Si el hardware anterior aún no está en su lugar, vuelva a cablearlo. Ingresa el código anterior, compila y transfiérelo. Cada pulsación del botón del reproductor debe mostrar un incremento en la pantalla. Nota: Si el interruptor no fue desbotado, es posible que obtenga un conteo adicional o dos en las prensas aleatorias.


    This page titled 13.2: Interrumpimos Nuestro Código Regularmente Programado... 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.