Skip to main content
Engineering LibreTexts

Arduino Interruptus - or Revenge of the Son of the Reaction Timer Redux Revisited

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

    The concept of interrupts was presented in the Event Counter exercise as a means of responding to external events in an accurate and timely manner. Interrupts can be used in other ways. One good example would be to maintain a multiplexed display, like the ones used in the Reaction Timer and Event Counter exercises. As presented originally, the display code is a bit problematic in that it runs in the main loop and wastes a lot of processor cycles through the use of delay() calls. The other code is forced to “fit around” that timing. A better scheme would be to use software interrupts to generate the timing for the display digit scan. A timer would be programmed to trigger an interrupt every 10 milliseconds or so, corresponding to the individual scan time for each display digit. When triggered, the ISR would extinguish the display and then enable the next digit, working in a round-robin fashion of units-digit, tens-digit, hundreds-digit, units-digit, tens-digit, and so on. Nearly all of the original display code could be used to create the ISR. The ISR would just need one additional variable to keep track of the currently illuminated digit. Also, the timer/counter would have to be set up to achieve a proper trigger rate.

    If we use a 1024 prescale with a 16 MHz clock, timing ticks come along at about 16 kHz. Using the Normal mode, a full 0 to 255 count would take about 16 milliseconds. With three digits, this requires nearly 50 milliseconds for one scan and will likely produce noticeable scanning artifacts on the display (i.e., a “beating” or “throbbing” effect). Unfortunately, the Uno's 328p does not have a 512 prescale (the 256 being perhaps a bit too fast). The solution to this is to shorten the time by preloading the counter's register so that it doesn't have as far to count (e.g., instead of counting from 0 to 255, we could count from 150 to 255). This is a nice technique to remember when we need to obtain specific timing values. In this case, we can adjust the start value “by eye” without resorting to precise calculations.

    The code presented below is built on the original Event Counter code and assumes identical hardware. First, we need to add the initialization for timer/counter number 2 in the setup() function:

      // 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
    

    We also introduce a constant that will allow us to adjust the scan time, with larger values producing faster scan times.

    #define OVF_COUNT_START 150
    

    We now lift the display code from the old loop() function and use it to form the ISR for the overflow interrupt. We need to create a global variable to keep track of the currently active digit and we also need to remove any reference to the delay() function as it is no longer being used.

    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.
    }
    

    Comparison of the code above with the original shows that very little modification was required. Now that the display updating has been removed from the main event loop, there is nothing to do there:

    void loop()
    {
    }
    

    The obvious question at this juncture is, “What's the point of having an empty loop function?” By itself, not much, but in a situation where other input and/or output devices, sensors and the like need to be monitored or set, the new loop() function can be designed with just that utility in mind, and without the worry of inserting specific bits of timing code into it. This makes for a much cleaner and easier to maintain system. Essentially, you have “handed off” the maintenance of the display to an automatic background process.

    In the testing of this new version, it is worthwhile to first compare it to the original version to see if the performance is identical. Second, try several different values of OVF_COUNT_START in order to see the effect of display flicker.


    This page titled Arduino Interruptus - or Revenge of the Son of the Reaction Timer 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.