Skip to main content
Engineering LibreTexts

10.1: Introduction

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

    In this exercise we shall combine digital input and output processing along with timing functions and random number generation to create a simple game. The point of the game will be to test the player’s reaction time (technically, we’ll be measuring the response time which is the sum of the reaction time plus the resulting movement time). Instead of just looking at code, this exercise also will allow us to take a step back and look at system design and simple user interface issues.

    The game will give the player five tries to achieve their best reaction time. Each try will consist of activating a red warning light as a means of telling the player to get ready. Then, a second green light (i.e., “go”) will be activated between one and ten seconds later, the precise time being random between tries. Once the green light comes on, the player needs to hit a switch. The amount of time between the green light turning on and the response switch activating is the player’s reaction time. The player will be told this value and at the end of the five tries, the best result will be displayed. The game also has to guard against cheating. That is, it has to disallow a hit that simply anticipates the green light firing, in other words, “jumping the gun”.

    Before we consider hardware and software, we need to have some idea of what we’re measuring. In general, how fast is human response time? It turns out that this is partly a function of the stimulus (audible cues are processed more quickly by the human brain than visual cues). Typical response times are around 160 milliseconds for an aural stimulus and 190 milliseconds for a visual stimulus, although professional athletes such as Olympic sprinters might on occasion achieve a value only 2/3rds of this1. Obviously then, our hardware and software need to be much faster if we want to achieve a decent level of accuracy.

    The user interface consists of four things: a red light, a green light, the player’s response switch and some means of text output. For simplicity and ease of prototyping, we shall use the Serial Monitor for text output. If this were to become a production item, the monitor could be replaced with a dedicated LCD display. Regarding the lights, LEDs seem to be an obvious choice perhaps because of their familiarity and the one-on-one personal immediacy of the game (if several people were to respond in sync, we might need a much larger or brighter light source than an ordinary LED). There is one other item to be considered and that is the turn-on speed of the light. LEDs turn on in a fraction of a millisecond whereas incandescent lights might not reach full brightness for tens or even hundreds of milliseconds. As we might be expecting response times in the 200 millisecond region, an incandescent may not be fast enough. That is, the time delay required for it to reach full brightness might skew the results, slowing the player’s apparent (measured) response time. So, it seems that simple LEDs would be a good choice here.

    The final item is the player’s switch. The player might hit the switch fairly hard because they’ll want to move their hand as fast as possible to minimize the movement time. Consequently, the switch needs to be fairly robust. It should also represent a decently sized target for the player, that is, one that is large enough that the player doesn’t also have to worry about making a very precise movement. While a simple momentary contact push-button might seem like a decent choice, an FSR might prove better. The FSR contains no moving parts and provides a large target area. It also responds nearly instantly and does not suffer from switch bounce. Interconnection is easy too: We can simply connect it between an input pin and ground, and enable the internal pull-up. Normally the FSR is a very high resistance but when pressed the value drops to maybe 100 or so ohms. This would be in series with the pull-up that is connected to the power supply. With the FSR untouched, the voltage divider rule indicates the pin voltage will float high. When the FSR is depressed, the voltage will dive to a logic low. The only downside to the FSR is the lack of physical feedback. Many switches make a clicking sound when they are engaged. This provides auditory feedback to player so that they know that their strike has registered. We can accomplish some player feedback by lighting one or both of the LEDs at the moment the strike is sensed.

    We now have an idea of the potential hardware requirements. What about the software? Obviously, previously examined techniques for digital input and output can be used to read and control the player switch and LEDs. The delay() function can be used for timing the LED flashes and the main “go” delay. Two new items are needed though: some way of generating random values (in our case, between 1000 milliseconds and 10,000 milliseconds) and some method of measuring the time between two events. Let’s consider the random number first.

    Generally, digital computing devices are not good at generating truly random values because they are deterministic machines. In fact, we try to “design out” all potential randomness because we need highly predictable operation. Truly random values are in no way correlated to each other. That is, there is nothing about a string of random values that will allow you to determine the next value in the sequence. It turns out, though, that we can create pseudo-random sequences without a lot of effort. A pseudo-random sequence appears to be random but if you run the sequence long enough, it will repeat. Pseudo-random sequences may not be sufficiently random for proper scientific research efforts but they’re plenty sufficient for something like our game. While the process of deriving pseudo-random numbers is beyond the scope of this exercise, we can say that the process involves some fairly rudimentary processing, such as bit shifting, on a starting value referred to as a seed. The result of this computation is then used as input to compute the next value and so on.

    The Arduino library includes a function, random(), which will return a pseudo-random long integer value. The function may also be called with upper and lower bounds to constrain the returned values as in random(30,200). The sequence itself is initiated via the randomSeed() function which takes a long integer argument. A sometimes useful effect of this is that if the seed value is the same for subsequent runs of the program, the resulting pseudo-random sequence will repeat exactly. This allows for a somewhat systematic debugging of a supposedly random feature. But this raises a problem, namely, how do we get a random number for the seed to begin with? While it’s possible to ask the user for a seed value, that opens the door to possible cheating. A better way is to use a noise voltage. By its very nature, noise is random. If we look at an analog input pin which is not connected to anything, we will see a small noise voltage. We can digitize this truly random value with the analogRead() function and use it for the seed. This voltage won’t change over a very large range, but it will change a sufficient amount to make our pseudo-random sequence to appear very random indeed.

    The following code shows how the random number system works. Enter the code, compile, and transfer it, and then run it. First it will print out the noise voltage and then print out random values between 50 and 1000. After you see a sufficient number of values, hit the reset button on the board to restart the code. Do this several times. These restarts should yield different sequences. From time to time you might get the same noise voltage. If you do see this, notice that the pseudo-random number sequence will be repeated exactly.

    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);
    }
    

    This technique should work perfectly for our application. All we need to do is constrain the random function to 1000 to 10,000 so we can use the values directly as the millisecond argument for the delay() call used to randomize the lighting of the green “go” LED.

    The second part that we need involves measuring the time delay between the lighting of the “go” LED and the player striking the FSR. Normally, if you were doing this from scratch you’d have to initialize one of the ATmega 328P’s timers and check its contents when needed. Timing events is a common need in the embedded world so the Arduino initialization routine sets up a timer for you. The instant you turn on or reset the board, this internal timer starts incrementing a register that is initialized at zero. The register is incremented by one each millisecond. This timer keeps running regardless of and independent of the main code that is running (unless the main code reprograms the timer, of course). Consequently, the register value indicates how many milliseconds have passed since the board was turned on or reset. The contents of this register may be read via the millis() function. That is, millis() returns the number of milliseconds that have transpired since the last reset/power up. After about 50 days, this register will overflow and the count will begin again. It is doubtful that any individual will want to play this game that long continuously so this should not be issue. The following code snippet will do what we need:

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

    So, we can now come up with a pseudo code for the looping portion of the game:

    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
    

    A few of these steps might need further clarification or adjustment. In step 4.k, a “cheat” is declared if the response time is under 100 milliseconds. This is probably generous given the statistics noted earlier and could be adjusted to a higher value. The penalty value here could be the same as the initialization value, perhaps 10,000 or more milliseconds. After all, if it takes an otherwise normal individual more than 10 seconds to respond to the stimulus, one might surmise that they are inebriated, easily confused or need to stop texting so much.

    Another item of importance is the code for steps 3 and 4.g. In essence, the code needs to “wait on” the player hitting the FSR. If the FSR is wired to an input pin utilizing an internal pull-up resistor, pressing the FSR will cause the digital input at that pin to go from high to low. (Remember, pressing the FSR creates a low resistance and the FSR is basically in series with the pull-up resistor. Therefore the voltage divider rule shows that the voltage between the two items drops to a very low value, or a logic low.) Consequently, the code can “busy wait” on that pin, breaking out when the pin goes low. We might wire up the FSR to Arduino pin 8 which is bit 0 of port B. Further, we might avoid hard coding this by using a #define:

    #define FSRMASK  0x01
    

    So the requisite bit test code looks like so:

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

    Remember, for input we look at PINx and for output we use PORTx. So this loop keeps running as long as the FSR input bit is high. As soon as the player pushes on the FSR with reasonable force, the pin goes low and the loop terminates.

    We can streamline our code a little more regarding steps 4.c, 4.f and 4.h which flash an LED. We might hook up the green and red LEDs to Arduino pins 6 and 7 (port D.6 and D.7) and use #defines along with a function that turns an LED on and off quickly:

    // 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);
    }
    

    This function would be called like so for a single quick flash:

    FlashLED(GREENLED);
    

    It could also be placed in a loop for a series of quick flashes and you could bitwise OR the mask values to flash the two LEDs simultaneously.

    The last item of concern is step 6, “Wait a moment before starting next game”. Why would we include this? The reason is not obvious. Remember that the microcontroller will process everything in very short order, perhaps just a few milliseconds will transpire between the time the player hits the FSR, the finals values are printed and the loop() function terminates, only to be called again by the invisible main(). Upon re-entry, into loop(), the code tells the player to press the FSR to start the next game and then waits on the FSR bit. In that short amount of time the player will not have had a chance to lift their finger. In other words, the FSR bit is still low so the loop terminates and trial one executes immediately! To prevent this, we can either enter a small delay as the average person will not keep pressing the FSR once the response time is recorded, or we could use a separate FSR to start the game. The former seems much more straightforward.

    Based on the earlier code snippets, the setup code might look something like this:

    // 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: Introduction 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.