Skip to main content
Engineering LibreTexts

14.4: Parte Terza – Changing Frequency

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

    Changing the wave shape is certainly useful. What about giving the user control over the frequency? At first thought, it seems obvious to use a potentiometer for a frequency control: We could read a voltage off it using the analogRead() function and use that for the delay value. The problem with this is the same as direct computation of a sine value, namely lack of speed. The acquisition time of the ADC will severely limit the output frequency we can create. On the other hand, checking a switch for high/low is a very fast process. It would be a simple matter to wire in another switch, similar to the wave shape switch, which would increment a global variable at each push of the button. This variable would then be used by the delayMicroseconds() function as follows:

    unsigned int g_period=100; // init at 100 microseconds
    
    // set data and wait one sample period
    PORTD = p[u++];
    delayMicroseconds( g_period );
    

    When the frequency switch is activated, the delay time would be incremented like so:

    BumpPeriod( 1 );
    

    The BumpPeriod() function “hides” the global variable and also allows for constraining its range with wrap-around at the extremes:

    void BumpPeriod( char c )
    {
          g_period += c;
    
          // wrap within range, c could be negative
          if( g_period < 1 )      g_period = 200;
          if( g_period > 200 )    g_period = 1;
    }
    

    A delay time of 1 microsecond produces a period of 256 microseconds (ignoring processing overhead) for a maximum frequency of about 3.9 kHz. Similarly, a delay of 200 microseconds yields a minimum frequency of about 20 Hz. Remember, g_period is a global variable so it is defined outside of and prior to all functions. In this way all functions can access it.

    While we could copy and paste the wave shape code for the frequency switch (with edits, of course), there is a practical problem with it. Every push of the button will change the sample period by just one microsecond. We have a 200:1 period range so the entire span would require 200 discrete button pushes. That’s probably not something most people would like. A common solution to this problem is to have the button “auto-increment” if it is held down for a long enough period of time. This hold-off time might be on the order of 500 milliseconds. We don’t want the auto-increment to run too fast so we might increment the period one unit for each following 100 milliseconds. That’s equivalent to 10 button pushes per second, or the equivalent of way too much espresso. At that rate we can cover the span in 20 seconds just by keeping a finger on the button. This process can be sweetened by adding a decrement button to go along with the increment button. The decrement switch code is virtually the same as what we’ll use for the increment except that the call to BumpPeriod() passes a negative value. If the span was much larger we might also consider adding “coarse” buttons for increment and decrement which would simply pass a larger value to BumpPeriod() (e.g., 10). Another option would be to pass a larger value to the function after a longer time period has elapsed (e.g., 1 each 100 milliseconds if the button is held between 0.5 and 2 seconds, and 10 if it’s held longer).

    Before we look at the auto-increment feature, we must remember to add code for the frequency switch initialization (along with the switch itself, of course). If we place this switch at Arduino pin 1 (port B.0), our initialization code now looks something like this:

    // all port B
    #define FREQBIT         0x01
    #define WAVEBIT         0x02
    #define SINEWAVE        0x04
    #define SQUAREWAVE      0x08
    #define RAMPWAVE        0x10
    
    void BumpPeriod( char c );
    unsigned int g_period=100; //init at 100 microsec
    
    void setup()
    {
          // set all of port D for output
          DDRD = 0xff;
    
          // set B.2:4 for output
          DDRB |= (SINEWAVE | SQUAREWAVE | RAMPWAVE);
    
          // set B.0:1 for input, enable pull-up
          DDRB &= (~(FREQBIT | WAVEBIT));
          PORTB |= (FREQBIT | WAVEBIT);
    }
    

    The processing of the switch itself is similar to the wave shape switch except that we will need some timing information. After all, the auto-increment feature needs to determine if the initial 500 millisecond hold-off time has passed, and once it has, it must only increment once each following 100 milliseconds. Therefore, along with the variables to hold the current and prior states of the switch, we’ll need variables to hold the time the switch was activated (time of switch transition), the current time, and the auto-increment or “bump” time:

    unsigned char freqswitch=FREQBIT, currentfreqswitch=FREQBIT;
    unsigned long transitiontime, currenttime, bumptime=500;
    

    The xxxtime variables must all be unsigned longs as they will be used with the millis() time function.

    Here’s how the algorithm works: First we obtain the current state of the switch. We do nothing unless the switch is “down”. If it’s down, we record the current time and see if the prior switch state was “up”. If so, this is the initial transition. We record the time of this transition and increment the period. We also set a variable (bumptime) to the hold-off time in milliseconds (500).

    // check if freq switch active
    currentfreqswitch = PINB & FREQBIT;
    
    if( !currentfreqswitch ) // selected (down)
    {
          currenttime = millis();
          if( currentfreqswitch != freqswitch ) // transition
          {
                transitiontime = currenttime;
                BumpPeriod( 1 );
                bumptime = 500;   // msec before auto inc
          }
    

    If the switch states are the same, then we know the switch is being held. Before we can start the auto-increment, we need to know if it’s been held long enough (bumptime, or 500 milliseconds initially). If we simply subtract the current time from the initial transition time we’ll know how long the switch has been held. If this difference is at least as big as our target then we can increment the period. Since we don’t want to increment it again until another 100 milliseconds pass, we need to increment bumptime by 100.

          else  // being held, is it long enough?
          {
                if( currenttime-transitiontime > bumptime )
                {
                      BumpPeriod( 1 );
                      bumptime += 100;
                }
          }
    }
    

    Lastly and regardless of the state of the switch, we update the prior frequency switch variable to the current state:

    freqswitch = currentfreqswitch;
    

    Notice that once the button is released, it is ignored until it is again depressed. At that instant a new transition time will be recorded and the bump time will be reset back to 500 milliseconds and the process will repeat as outlined above. If the button is released prior to the hold-off time, no auto-increment occurs, and the next button press will reinitialize the transition and bump times. As in the case of the wave shape switch, it is important to note that if you’re using a hardware debounce circuit that inverts, the “switch selected” logic will also be inverted (remove the “not”).

    The frequency switch processing code should be placed in line with the wave shape switch code, either immediately before or after it. The result inside the loop() function should look something like this:

          // select default sine and light corresponding wave shape LED
    
          while( 1 )
          {
                // only check switches at start of each cycle
                if( !u )
                {
                      // process wave switch
                      // process freq switch
                }
    
                // set data and wait one sample period
                PORTD = p[u++];
                delayMicroseconds( g_period );
          }
    }
    

    Edit, compile, transfer and test the code. If you’re feeling energetic, add a decrement button as well.


    This page titled 14.4: Parte Terza – Changing Frequency 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.