Project 2: Digilent Pro MX7 and Delays

Software Timing Delays

The purpose of this project is to investigate methods of creating software time delays to pace processor operations. The timing methods that control when a process is to be executed can be implemented by using polling techniques that sample a free running clock or by executing code that requires a fixed amount of time. This project also demonstrates the use of time delays for signal conditioning.


Pmod STEP Parts Layout

Determining Delay Constants

MPLAB X Stopwatch

Time Delay Application


Prerequisites

Inventory


Time Delays: How Long Does It Take?

Usually, the greater the amount of time required to execute software, the greater the detriment to system performance. However, there are instances when the program actually runs too fast and we are challenged with developing ways of slowing it down. Each instruction that the processor executes requires a finite amount of time. Sometimes we can use the inherent code execution time to our advantage by using program loops to generate a delay. Consider the program shown in Listing 1 that adds two integer variables and assigns the result to a third integer variable:

Listing 1.

/* 
 * File:   P1.c
 * Author: Richard Wall
 *
 * Created on July 5, 2013, 12:56 PM
 */
 
#include <plib.h>
#include "../config_bits.h"
 
int main(void)
{
   int a = 5, b = 10, c;
 
   c = a+b;
   return (EXIT_SUCCESS);
}

Each C source code statement is implemented by translating it into instructions that a given processor understands. The processor then acts on those instructions. The actual processor-level instructions that implement this statement can be represented in assembly language. Unlike C, which is considered a “high level” language that is independent of hardware, assembly language is specific to the particular processor on which it runs. The assembly language instructions are subsequently converted to a machine language that the processor actually uses. Although it is beyond the scope of this project to teach assembly language programming, we will consider a little bit of assembly code because it is instructive in terms of timing considerations. You do not need to be especially concerned if some of the details seem a bit mysterious. The C statement, c = a+b;, in the program shown in Listing 1 is implemented as a sequence of four assembly language instructions as shown in Listing 2.

Listing 2. Assembly language code generated for the add statement.

9D000038  8FC30010  lw    v1,16(s8) ; Load word Reg V1 indirect
9D00003C  8FC20014  lw    v0,20(s8) ; Load word Reg V0  indirect
9D000040  00621021  addu  v0,v1,v0  ; Unsigned Integer Add
9D000044  AFC20018  sw    v0,24(s8) ; Store word indirect

The first two columns of Listing 2 are 32-bit numbers expressed in hexadecimal notation. The first column shows the memory address where the machine code is stored. The second column is the actual machine code that the processor uses to implement the instruction shown in third and fourth columns. The third column gives the type of assembly language instructions used and the processor memory registers that are involved are given in the fourth column. Any text that follows a semicolon is processed as a comment, as illustrated by the fifth column.

To determine how many cycles each instruction takes, we need to look at the assembly language for the MIPS architecture that the PIC™32MX uses. The conversion of C instructions to assembly code can be different depending on the selection of compiler optimization options. If the PIC32MX is running the core clock at 80 MHz, one CPU cycle is 12.5 ns (i.e., the period of one clock cycle is the inverse of the clock frequency).

Due to the pipeline architecture used by the MIPS core, it is difficult to isolate a segment of code and determine its execution time strictly based upon the number of instructions. By using the Stopwatch diagnostics tool embedded in the MPLAB® X IDE to time the four instructions in Listing 2, we find that 31 CPU cycles are required. (See the Determining Delay Constants tab on the right for instructions on how to use the MPLAB X Stopwatch feature.) This equates to 387.5 ns when the core clock is running at 80 MHz. Setting aside the difficulties of determining time to execute from lines of assembly code, the sheer number of instructions is a relative indicator. The execution time required to complete any particular C instruction is determined by the complexity of the C statement, the assembly code that the C instruction is translated into, and the speed at which the processor is running.

It is sufficient to say that each C instruction can require multiple assembly language instructions to implement and each assembly instruction requires time to execute. The bottom line is that the execution of C code requires time and we can use this time to generate predictable time delays. An alternate method to generate a time delay is to use one of the timer resources of the processor. We will investigate both of these two methods of creating a delay function in this project.


Pacing the Computer

When the processor executes code faster than the application requires, microprocessor based systems have time to spare. Say, for example, that there is an application where an LED is to be toggled between on and off at a one-second rate. The LATGINV instruction used to toggle the I/O pin only requires four processor clock cycles accounting for 50 ns of the desired one second period. Since the microprocessor always wants to be executing the next line of code at a rate of 80 million instructions per second, additional instructions must be executed to implement a time delay operation. In most cases, the time delay function has no other purpose than to kill time.

Two approaches for developing a delay function will be explored in this project. One of them requires no special hardware but instead relies strictly upon the execution time of a software loop. We will refer to this approach as the software delay method. The other uses one of the processor's built-in timers to generate a delay by counting clock cycles. We call this approach a hardware-assisted software delay method. Delay functions have two controlling interrelated elements: the resolution and the range. The resolution is defined by the smallest possible delay and the range by the longest possible delay. To some extent these parameters are determined by the method chosen to implement the delay.


Pure Software Delays

First, let us focus on the software delay method by considering a software loop to generate a delay function. There are three ways to write a software loop: a “for” loop, a “while” loop, and a “do -while” loop. An example of using a for loop is: for (i = 0; i < COUNTS_PER_MS; i++);. In this program statement, the value of the constant “COUNTS_PER_MS” is chosen to result in a unit delay (delay of one unit) to be one millisecond. Listing 3 shows how to generate a software delay of specified time using a fixed number of for loop iterations. This for loop is then repeatedly executed for each desired millisecond of delay using a while loop. The speed at which the processor executes code affects value of the constant COUNTS_PER_MS. The larger the nominal value of the constant COUNTS_PER_MS, the higher the resolution of the period, hence the more accurately the delay period can be set. The lines of code that are annotated with the comment “ / / SW Stop breakpoint” identify the lines of code where break points are inserted to allow execution timing using the MPLAB X stopwatch tool. (See the third tab on the right for information on using the stopwatch.)

Listing 3. Software delay function.

/* The following define statements is declared in chipKIT_PROM_MX.h  
#define LEDA      BIT_2    // IOPORT B
*/
 
/* The following define statement is declared in Project2.h 
#define COUNTS_PER_MS   1000     // Initial guess
*/
 
void sw_msDelay (unsigned int mS)
{
   int i;
   while(mS --)  // SW Stop breakpoint
   {
      for (i = 0; i< COUNTS_PER_MS; i++)  // 1 ms delay loop
      {
         // do nothing
      }
      LATBINV = LEDA; // Toggle LEDA each ms for instrumentation
   }
}  // SW Stop breakpoint

Given that the “mS” variable that specifies the number of milliseconds to delay is an unsigned integer, the range, or longest possible delay, is 4,294,967,295 ms or 49.71 days. The disadvantage of the software delay loop is that the value assigned to COUNTS_PER_MS depends upon the processor speed. If the processor speed changes, then the delay function is no longer calibrated correctly.


Calibrating Time for Software Delays

When using the “sw_msDelay” function shown in Listing 3 above, a couple of questions come to mind. “What value should be used for COUNTS_PER_MS and how accurate is the delay?” Subsequently, you must address the issue as to how best to determine the value to be used for the COUNTS_PER_MS constant. As previously suggested, it is difficult to simply accumulate the instruction times, so an alternate method will be used.

We suggest that software code be added to allow the software operation to be observable. One way to accomplish this is by toggling an I/O pin on the microprocessor. This requires that external instrumentation be connected to the pin of the board connected to that I/O port for the bit being toggled. Suitable instrumentation for this test includes frequency counters, oscilloscopes, and logic analyzers. Now one must consider the appropriate place to insert the code to set the I/O pin high or low realizing that inserting of such code may affect the accuracy of the delay function. It is advisable to place the instrumentation code where it will be executed the minimum number of times. It should be noted that the LATxINV instruction is used to toggle specific bits in the LAT register without modifying the other LAT register bits. This is an important consideration when the I/O pins for a specific port are used to control independent outputs and different times. A detailed process for determining the value of COUNTS_PER_DELAY is presented in the second tab on the right.

An effective alternative for measuring delay time is to use the MPLAB X Stopwatch tool described in the third tab on the right.


Hardware-Assisted Delays

The hardware-assisted software delay approach to generating a delay function makes use of one of the processor's hardware timers. For timer-based delays, common units of time are related to cycles of the CPU crystal, timer ticks, or milliseconds. The chipKIT Pro MX7 board uses an 8 MHz crystal. That, in conjunction with programmable PIC32 multipliers and dividers, results in the PIC32MX795F512 processor being capable of operating at the maximum core frequency of 80 MHz. In the config_bits.h file, the statement “#pragma config FPLLIDIV=DIV_2” first divides the frequency of the 8 MHz oscillator by 2. The statement #pragma config FPLLMUL= MUL_20 then multiplies the output of the input divider by 20, resulting in 80 MHz. Finally, the statement #pragma config FPLLODIV = 1 divides the 80 MHz by 1, resulting in the core frequency of 80 MHz. Since the power that a processor consumes is related to the processor speed, the combinations of the multiplier and two dividers allow the developer to set the core frequency that best suits the timing and power requirements of a particular application. Refer to the Section 6 of the PIC32MX5XX/6XX/7XX technical reference manual for information concerning configuration options for the core oscillator.

The PIC32MX processor family has several internal timers. For this project, we will use the core timer to generate delays of arbitrary periods of time. The core timer ticks once for every two times the system clock does. Therefore, the number of core oscillator counts per millisecond equals one half of the system clock frequency divided by 1000. The first three #define statements in Listing 4 establish the number of core oscillator cycles in a millisecond. The while loop in the “hw_DelayMs” function shown in Listing 4 implements the total millisecond delay operation.

Listing 4. Hardware-assisted software delay function.

/* The following define statements are declared in chipKIT_PROM_MX.h */
// #define GetSystemClock()   (80000000ul)   // Hz 
// #define GetInstructionClock() (GetSystemClock()/2) 
// #define CORE_TICKS_per_MS  (GetInstructionClock()/1000)
// #define LEDA         BIT_2       // IOPORT B
void hw_DelayMs(unsigned int mS)
{
   unsigned int tWait, tStart;
   tStart=ReadCoreTimer(); /* Read core timer - SW Start breakpoint */
   tWait= (CORE_MS_TICK_RATE * mS); /* Set time to wait */
   while((ReadCoreTimer() - tStart) <= tWait); /* Wait for the time to pass */
   LATBINV = LEDA;   /* Toggle LED at end of delay period */
}  /* SW Stop breakpoint */

In Listing 4, the start time is immediately taken directly from the core timer using the ReadCoreTimer() function. The variable “mS” passed to hw_DelayMs is the total number of milliseconds to wait. The variable, tWait, is assigned to the number of ticks per millisecond multiplied by the number of milliseconds to wait, mS. Line 11 of Listing 4 contains a while loop that continuously reads the core timer and subtracts the initial core timer value saved in the variable tStart. The delay is complete when this difference is equal to or greater than the value computed for tWait. Repeatedly reading the core timer value is referred to as “polling” the core timer.

The delay period resolution of the hardware-assisted delay method is established by the core oscillator frequency. The range of delay is limited by the largest value that an unsigned integer data type used for the variable, tWait , can represent. For an 80 MHz system frequency, this results in 232/CORE_TICKS_per_MS=232/40000000, which is equal to 107,374 ms, or 1.79 minutes


Handling Timer Rollover for Hardware-Assisted Delays

We will be referencing the instrumentation test points provided on the PmodSTEP™ for this project. Specifically, we will use the test points associated with LEDA and LEDB, as identified on the parts layout shown in the top tab on the right. You are to write a single program with the two methods of time delay. The program is compiled so that only one of the two time delay methods is used at a time. For the program using the code in Listing 3, you must determine an appropriate value for the COUNTS_PER_MS constant that is defined in Project1.h. Hint: try starting at 5000. The Project2.h program will look like Listing 5 and Project2.c like Listing 6. No other bits should be altered when toggling pins RB2 and RB3 for timing purposes. Determine the relative accuracy for delays in different duration of the delay over the range of 1 ms to 1000 ms. (See Project Testing below.)

Listing 5.

/********************** Project 2 *********************************
 *
 * File:    Project2.h
 * Author:  Richard Wall 
 * Date:    May 22, 2013
 *
 */
 
/* Software timer definition */
#define COUNTS_PER_MS   5000  /* Exact value is to be determined */
 
/* Function Prototypes */
void system_init (void);      /* hardware initialization */
void sw_DelayMs (unsigned int mS);  /* Software only delay */
void hw_DelayMs (unsigned int mS);  /* Hardware assisted SW delay */

Listing 6.

/******************************** Project 2 ****************************
 *
 *  File:        Project2.c
 *  Author name: Richard Wall 
 *  Rev. Date:   May 22, 2013
 *
 *  Project Description:  The purpose of this project is to investigate
 *  the characteristics and limitations of two types of polling delay.
 *
 **********************************************************************/
 
#include <plib.h>
#include "Project2.h"
#include "config_bits.h"
#include "chipKIT_PRO_MX7.h"
 
int main()
{
   int mS = 1;      /* Set total delay time - change as needed */
   system_init ();  /* Setup system Hardware. */
   while(1)
   {
      LATBINV = LEDB;   /* Toggle LEDB each delay period */
      /* Run with only one of the two following statements uncommented */
      sw_DelayMs (mS);  /* Software only delay */
      // hw_DelayMs (mS);  /* Hardware assisted SW delay */
 
   }
   return 0;  /* Returning a value is expected but this statement
               * should never execute */
}
 
/* system_init FUNCTION DESCRIPTION ***********************************
 * SYNTAX:      void system_init (void);
 * KEYWORDS:    initialization system hardware
 * DESCRIPTION: Sets up the configuration for Port B to control LEDA
 *        - LEDH.
 * RETURN VALUE:   none
 * END DESCRIPTION *****************************************************/
void system_init(void)
{
   /* Setup processor board */
   chipKIT_PRO_MX7_Setup();
   PORTSetPinsDigitalOut(IOPORT_B, SM_LEDS);/* Set PmodSTEP LEDs outputs */
   LATBCLR = SM_LEDS;         /* Turn off LEDA through LEDH */
}
 
/* sw_DelayMs Function Description ************************************
 * SYNTAX:      void sw_DelayMs(unsigned int mS);
 * DESCRIPTION: This is a millisecond delay function that will repeat
 *        a specified number of times. The constant "COUNTS_PER_MS"
 *        must be calibrated for the system frequency.  
 * KEYWORDS:    delay, ms, milliseconds, software delay
 * PARAMETER1:  mS - the total number of milliseconds to delay
 * RETURN VALUE:   None:
 * Notes:    The basic loop counter "COUNTS_PER_MS" is dependent on
 *        the CPU frequency. LEDA will toggle at 500 Hz.
 *END DESCRIPTION ******************************************************/
void sw_DelayMs(unsigned int mS)
{
   /* Use code from Listing 3 */
}
 
/* hw_DelayMs Function Description **************************************
 * SYNTAX:         void hw_DelayMs (unsigned int mS);
 * DESCRIPTION: This is a millisecond delay function uses the core time
 *        to set the base millisecond delay period. Delay periods
 *        of zero are permitted. LEDA is toggled each millisecond.
 * KEYWORDS:    delay, ms, milliseconds, software delay, core timer
 * PARAMETER1:  mS - the total number of milliseconds to delay
 * RETURN VALUE:   None:
 * END DESCRIPTION ******************************************************/
void hw_DelayMs(unsigned int mS)
{
   /* Use code from Listing 4 */
}
// End of Project2.c

Project Testing

Use the LATBINV = LEDA; and LATBINV = LEDB ; instructions to toggle the two LEDs. You need both instructions to implement the two different delay programs. Connecting an oscilloscope or logic analyzer to the test point for LEDB (RB3) will allow you to measure the total delay being called for from the main function. Connecting the oscilloscope / logic analyzer probe to the test point for LEDA (RB2) will measure the millisecond for loop delay but only for the sw_msDelay function. (Remember to connect the oscilloscope or logic analyzer ground to the PmodSTEP ground pin. See the first tab on the top right for the pin locations.)

Figure 1 is an example of the instrumentation to determine the timing for a 20 ms delay. The LEDA trace toggles each 1 ms resulting in a frequency of 500 Hz. The LEDB trace toggles each 20 ms resulting in a frequency of 25 Hz.

Figure 1. Screen capture for 20 ms delay.

Figure 1. Screen capture for 20 ms delay. Screenshot of Digilent WaveForms running on Microsoft Windows 7.

Complete the data entries for Table 1 using the signal provided by instruments connected to the test point for LEDB and then the MPLAB X Stopwatch tool. When using the MPLAB X Stopwatch tool, place the Stopwatch Start for the first executable line of code in the delay function and set the Stopwatch Stop for the delay function closing brace as noted in the comments for Listing 3 and Listing 4.

Design Total Delay — ms Software Delay — Measured — ms Hardware Delay — Measured — ms
LEDB Stopwatch LEDB Stopwatch
1 1 0.99775 1 1.00066
10 10 9.97491 10 10.00359
100 100 99.74654 100 100.03284
1000 1000 Not valid 1000 Not valid

(Solution values are in red and should be removed prior to distribution to students.)


Try It on Your Own!

Now that you've completed this project, you should take a look at the following questions and problems:

  1. Report on your observations and conclusions based on the results recorded in Table 1.

  2. ANSWER: The MPLAB X Stopwatch tool appears to provide better resolution than using the logic analyzer. A high resolution measurement on long delays, at least compared to the degree provided by the Stopwatch, is difficult to obtain using oscilloscopes or logic analyzers. The Stopwatch has an upper limit on how long of a delay can be measured in which cases the logic or oscilloscope is needed.

  1. When and why you would use the software delay function written for this assignment and when and why you would implement a software delay function using the core timer?

  2. ANSWER: Reading the core timer has greater accuracy because the 32-bit core timer has greater resolution and is directly related to the oscillator, which is usually a crystal or resonator. The software loop is a simple delay that can be implemented on any processor. Both delays are dependent on the speed at which the processor is running. A couple of the things the software delay method has going for it is that it is easy to understand the concept, and it is easy to write the code to implement. However, beyond the scope of this project, software interrupts can also be preempted by an interrupt. This results in extending the delay period. The disadvantage is that the processor is completely tied up just incrementing a number unless interrupted. The bottom line is that they are simple by consume processor time.

  3. When creating periodic delays, what determines the stability and accuracy of the delay period?

  4. ANSWER: When attempting to calibrate the software delay loop by measuring a periodic event (such as toggling a bit), one must consider the time required to execute code outside the delay loop. Longer delays will tend to be more accurate because the time spent in the software loop relative to the time needed to execute code outside the loop is greater, thus having less impact on the total period. Should the software delay loop be interrupted, as will be the case in Project 5, then the delay period for pure software delay loops will always be extended. However, the delay period may or may not be extended when the hardware-assisted software delay is used.

  5. What is the delay reported by the Stopwatch tool in MPLAB X for a one ms delay using the sw_DelayMs function?

  6. ANSWER: Placing a breakpoint on the first line of code in the sw_msDelay function and the second breakpoint of the last brace of the sw_msDelay function, the stopwatch reports a cycle count of 79820. Based on the processor running at 80 MHz, this equates to 0.000998 ms.

  7. What determines the resolution for the two methods of implementing polling-based time delay presented in this project?

  8. ANSWER: For the purely software method, the resolution is one part in the value determined for COUNTS_PER_MS. For the hardware-assisted delay method, it is one part out of the instructions cycle frequency (SYSTEM_FREQUENCY/2). One conclusion is that the lack of range for hardware-assisted delay is compensated for by the increased resolution.

  9. Why do LEDA and LEDB toggle at the same rate when using the hw_msDelay function?

  10. ANSWER: The hw_msDelay function does not execute the delay as multiple one millisecond delays. The delay is always a total delay.

  11. How can the limited range be extended for the hw_msDelay function shown in Listing 4?

  12. ANSWER: This approach extends the delay range to 4,294,967,295 ms or 49.71 days.