User Tools

Site Tools


lab_3

Interval Timers

Overview

Understanding time is essential in embedded systems. You generally want to know how much time certain code segments require for proper execution. There are lots of ways to infer how long it will take to execute some section of code but generally the most accurate way to determine execution time is to measure it.

New Stuff for this Lab

  1. Learn how to access low-level hardware (the timer hardware).
  2. Learn how to read commercial documentation.
  3. Write more complex 'C' code in the Xilinx SDK environment.
  4. Learn how to follow the coding standard to write reusable code.
  5. Learn how to write test code.

Objectives

  1. Learn how the interval-timer hardware functions.
  2. Gain experience working with commercial documentation.
  3. Write low-level code to communicate with the interval-timer hardware.
  4. Gain additional practice writing 'C' code in the Xilinx SDK environment.
  5. Write reusable code that will function as a tool in later lab assignments.
  6. Write test-code that tests your software implementation and that also documents how to use the interval timer driver.
  7. Practice applying the coding standard.

You will develop a software driver (including test code) that will communicate with three interval timers that I installed in the bit-stream that you use for this class. Interval timers are essentially hardware counters that you can easily stop, start, and read. Because they are hardware-based counters, they don't interfere with your program or add to execution time while they are running. Interval timers can be very effective for measuring execution time for any part of your software. Do a nice job on this assignment. You will likely use these timers extensively in the remaining labs in this class and later in ECEn 390 when you implement your laser-tag game.

Background

The hardware timers that you will access are LogiCORE IP AXI Timer (axi_timer) (v1.03a) provided by Xilinx. Here's a block diagram of the AXI timer taken from the Xilinx documentation. Please refer to the Xilinx documentation for functional details of the AXI timer (see Resources Section below).

axitimerblockdiagram2.jpg


I inserted three of these into the bit-stream and they are named: timer_0, timer_1, and timer_2, as shown below.

threeintervaltimerdiagram.jpg



Here's an excerpt from the xparameters.h file with definitions related to these three timers. As you should recall from Lab 2, the base address is your main concern.

NOTE: Do not copy the code below into your own code. Just include xparameters.h and use the defined values, e.g., XPAR_AXI_TIMER_2_BASEADDR.

/* Definitions for driver TMRCTR */
#define XPAR_XTMRCTR_NUM_INSTANCES 3
 
/* Definitions for peripheral AXI_TIMER_0 */
#define XPAR_AXI_TIMER_0_DEVICE_ID 0
#define XPAR_AXI_TIMER_0_BASEADDR 0x42800000
#define XPAR_AXI_TIMER_0_HIGHADDR 0x4280FFFF
#define XPAR_AXI_TIMER_0_CLOCK_FREQ_HZ 100000000
 
 
/* Definitions for peripheral AXI_TIMER_1 */
#define XPAR_AXI_TIMER_1_DEVICE_ID 1
#define XPAR_AXI_TIMER_1_BASEADDR 0x42840000
#define XPAR_AXI_TIMER_1_HIGHADDR 0x4284FFFF
#define XPAR_AXI_TIMER_1_CLOCK_FREQ_HZ 100000000
 
 
/* Definitions for peripheral AXI_TIMER_2 */
#define XPAR_AXI_TIMER_2_DEVICE_ID 2
#define XPAR_AXI_TIMER_2_BASEADDR 0x42880000
#define XPAR_AXI_TIMER_2_HIGHADDR 0x4288FFFF
#define XPAR_AXI_TIMER_2_CLOCK_FREQ_HZ 100000000

Resources

These timers are fully described in Xilinx's documentation.

If the highlighting does not appear, make sure to read at least the following pages: 1, 2-3 (through Characteristics), 4-5 (Characteristics), 7 (Figure 2), and 11-14 (Register Definitions).

Overview of Timer Hardware

The timer hardware is pretty complex and provides lots of functionality. However, we will only use them in their simplest mode as interval timers. As such, you need to carefully study at least the highlighted sections of the Xilinx documentation.

As discussed in the manual, the timer consists of two identical sections each containing a 32-bit counter and associated registers. The two counters can be cascaded to form a single 64-bit counter. This is referred to as cascade mode. In cascade mode, almost all of the control is done via a single control register (TCSR0) as the other control registers are mostly ignored in this mode. The counters can be configured to count up or down, you will configure them to count up. This makes the most sense for a simple timer.

You will need to access the following registers in the timer hardware:

  • TCSR0: this control/status register is used to control the cascaded 64-bit counter and to load values into the 32-bit counter.
  • TCSR1: you will use this control/status register only to load a 0 into counter 1 to reset it.
  • TLR0: you will need to set both 32-bit counters to zero in order to reset the timer. You do this for counter 0 by loading a 0 into this register and loading the contents of TLR0 into the counter.
  • TLR1: you load a 0 into this and load the contents of TLR1 into counter 1 to reset it and set it to 0 (just like counter 0).
  • TCR0: you read this register to find out the current value of counter 0.
  • TCR1: you read this register to find out the current value of counter 1.
  1. To initialize the counters, you should do the following:
    1. write a 0 to the TCSR0 register.
    2. write a 0 to the TCSR1 register.
    3. set the CASC bit and clear the UDT0 bit in the TCSR0 register (cascade mode and up counting).
  2. To store a 0 into counter 0, do the following:
    1. write a 0 into the TLR0 register.
    2. write a 1 into the LOAD0 bit in the TCSR0.
  3. To store a 0 into counter 1, do the following:
    1. write a 0 into the TLR1 register.
    2. write a 1 into the LOAD1 bit of the TCSR1 register.
  4. To start the cascaded counter:
    1. write a 1 to the ENT0 bit of the TCSR0 register. When you do this, you must not disturb the other bits in TCSR0.
  5. To stop the cascaded counter:
    1. clear the ENT0 bit in the TCSR0 register. Make sure not to clear out the CASC bit when you do this (or you will need to restore it).

Additional Notes:

  1. After loading the counters with 0s to reset them, you must re-initialize them (see Step 1 above). In particular, make sure that you clear the load bit for both counters.
  2. The process for reading the two 32-bit counters and reassembling them into a single 64-bit number is described on Page 5 of the documentation.
  3. Search the xparameters.h file to find out the frequency for the timers. Always use the provided #defines as they are provided by xparameters.h. Don't use the actual numerical constants.

Requirements

  1. You must write the intervalTimer.c file.
  2. You must use intervalTimer.h as given. No modifications are necessary or allowed.
  3. You must follow the coding standard.
  4. You must provide the following functions for the three interval timers (see below).
  5. You must use the Xilinx low-level access functions Xil_In32() and Xil_Out32() to access the registers in the timer hardware. These functions were discussed in a previous lab.
  6. You must define isr_function() in your code to avoid linking errors. Just use the same empty function from the helloWorld lab.
  7. Run the the final pass-off code (see below) for the TA to demonstrate the accuracy of your timer. Compare the time printed by the program to your watch to demonstrate accuracy.
intervalTimer.h
/*
 * intervalTimer.h
 *
 *  Created on: Apr 2, 2014
 *      Author: hutch
 */
 
// Provides an API for accessing the three hardware timers that are installed
// in the ZYNQ fabric.
 
#ifndef INTERVALTIMER_H_
#define INTERVALTIMER_H_
 
#include <stdint.h>
 
// Used to indicate status that can be checked after invoking the function.
typedef uint32_t intervalTimer_status_t;  // Use this type for the return type of a function.
 
#define INTERVAL_TIMER_STATUS_OK 1        // Return this status if successful.
#define INTERVAL_TIMER_STATUS_FAIL 0      // Return this status if failure.
 
#define INTERVAL_TIMER_TIMER_0 0
#define INTERVAL_TIMER_TIMER_1 1
#define INTERVAL_TIMER_TIMER_2 2
 
// You must initialize the timers before you use them the first time.
// It is generally only called once but should not cause an error if it 
// is called multiple times.
// timerNumber indicates which timer should be initialized.
// returns INTERVAL_TIMER_STATUS_OK if successful, some other value otherwise.
intervalTimer_status_t intervalTimer_init(uint32_t timerNumber);
 
// This is a convenience function that initializes all interval timers.
// Simply calls intervalTimer_init() on all timers.
// returns INTERVAL_TIMER_STATUS_OK if successful, some other value otherwise.
intervalTimer_status_t intervalTimer_initAll();
 
// This function starts the interval timer running.
// If the interval timer is already running, this function does nothing.
// timerNumber indicates which timer should start running.
void intervalTimer_start(uint32_t timerNumber);
 
// This function stops a running interval timer.
// If the interval time is currently stopped, this function does nothing.
// timerNumber indicates which timer should stop running.
void intervalTimer_stop(uint32_t timerNumber);
 
// This function is called whenever you want to reuse an interval timer.
// For example, say the interval timer has been used in the past, the user
// will call intervalTimer_reset() prior to calling intervalTimer_start().
// timerNumber indicates which timer should reset.
void intervalTimer_reset(uint32_t timerNumber);
 
// Convenience function for intervalTimer_reset().
// Simply calls intervalTimer_reset() on all timers.
void intervalTimer_resetAll();
 
// Runs a test on a single timer as indicated by the timerNumber argument.
// Returns INTERVAL_TIMER_STATUS_OK if successful, something else otherwise.
intervalTimer_status_t intervalTimer_test(uint32_t timerNumber);
 
// Convenience function that invokes test on all interval timers.
// Returns INTERVAL_TIMER_STATUS_OK if successful, something else otherwise.
intervalTimer_status_t intervalTimer_testAll();
 
// Use this function to ascertain how long a given timer has been running.
// Note that it should not be an error to call this function on a running timer
// though it usually makes more sense to call this after intervalTimer_stop() has
// been called.
// The timerNumber argument determines which timer is read.
double intervalTimer_getTotalDurationInSeconds(uint32_t timerNumber);
 
#endif /* INTERVALTIMER_H_ */

The functions that accept a timerNumber argument operate on a single timer. The timer number must be: 0, 1, or 2. Anything else should generate an error message. The intervalTimer_initAll(), intervalTimer_resetAll(), and intervalTimer_testAll() operate on all three timers. intervalTimer_getTotalDurationInSeconds() reads the 64-bit value from the timer and returns the number of seconds that have transpired since the counter was last reset and started.

intervalTimer_test() needs to see if the timers are working. In my code, I check things by:

  1. I reset the counter and see if it is reset by reading it.
  2. I start the counter and read it a couple of times to see if it is actually changing value.
  3. I stop the counter and read it a couple of times to see that it is not changing.

Hints and Helps

  1. The register access functions that you wrote for Lab 2 could be used here with little or no modification.
  2. It is considered good practice to wrap the low-level I/O functions (Xil_In32(), Xil_Out32()) inside a helper function as demonstrated in the code provided for the buttons/switches lab.
  3. It is considered very poor practice to expose these low-level I/O calls throughout your code because your code is less readable, is difficult to port (move to a different system) and is a pain to debug.
  4. Don't try to use file-names or directories/folder-names with spaces. The software will let you create them but the Xilinx software cannot handle them.

Pass Off and Credit

You will pass this lab off in two milestones:

  1. Milestone 1: Get the functions intervalTimer_init(), intervalTimer_start(), intervalTimer_stop(), and intervalTimer_reset() working for timer_0 (you can ignore the timer_1 and timer_2 for this milestone). Use the code shown below to demonstrate that these functions are working that it is working. Demonstration of this milestone is worth 25% of the total for this lab. Your program must work correctly each time it is executed. It must not depend upon the board being power-cycled.
  2. Milestone 2: Complete all of the lab requirements for all timers (timer_0, timer_1, and timer_2). Demonstration of this milestone (all lab requirements) is worth 50% of the total credit for this lab. Your program must work correctly each time it is executed. It must not depend upon the board being reset.
  3. Code quality and adherence to the coding standard is worth 25% of the total credit for this lab.

Submitting Source Code

Follow this procedure to submit your source code to learning-suite.

Notes to TAs

  1. Do not pass off milestones until you review the source code; source code must be compliant with the coding standard prior to pass off.
  2. You must run their test code multiple times without resetting the board. Code that requires the board to be reset in order to correctly run the test code cannot be passed off.
  3. Students must also demonstrate that their test works and that it performs the operations outlined above.
  4. For the first step of the test-code for the second milestone, test the accuracy of the timer for at least 60 seconds (wait that long after pressing BTN0 and then pressing BTN1).

Helps: Including Files From Previous Labs

You can easily include files from previous labs so there is no need to move them or to recopy them. Assuming that each lab was placed in its own enclosing folder, the following example demonstrates how to do this. Let's say that I am working on Lab5 and I wanted to include some files from Lab 2. Your include statement would look something like:

#include ”../Lab2/buttons.h”

The ”..” tells the compiler to go up to the next directory.



Milestone 1 and 2 Test Programs: Use This Code to Pass Off Milestone 1 and 2

#include "supportFiles/intervalTimer.h"  // Modify this to reflect the location of your intervalTimer.h
#include "supportFiles/buttons.h"        // Modify this to reflect the location of your buttons.h
#include "supportFiles/utils.h"
#include <stdio.h>
#include "xil_io.h"

#define TCR0_OFFSET 0x08  // register offset for TCR0
#define TCR1_OFFSET 0x18  // register offset for TCR1

// Reads the timer1 registers based upon the offset.
u32 readTimer1Register(uint32_t registerOffset) {
  uint32_t address = XPAR_AXI_TIMER_0_BASEADDR + registerOffset;  // Add the offset to the base address.
  return Xil_In32(address);  // Read the register at that address.
}

#define DELAY_COUNT 3
// Simple busy-wait function.
void waitALongTime() {
  volatile int32_t a = 0;        // Use volatile so that optimizer doesn't mess things up.
  int32_t i, j;  // Simple index variables.
  for (i=0; i<DELAY_COUNT; i++)        // Outer delay-loop.
    for (j=0; j<INT32_MAX; j++)  // Inner delay-loop.
      a++;
}

void milestone1() {
  printf("=============== Starting milestone 1 ===============\n\r");
  intervalTimer_init(INTERVAL_TIMER_TIMER_0);  // Init timer 0.
  intervalTimer_reset(INTERVAL_TIMER_TIMER_0); // Reset timer 0.
  // Show that the timer is reset.
  // Check lower register.
  printf("timer_0 TCR0 should be 0 at this point:%ld\n\r", readTimer1Register(TCR0_OFFSET));
  // Check upper register.
  printf("timer_0 TCR1 should be 0 at this point:%ld\n\r", readTimer1Register(TCR1_OFFSET));
  intervalTimer_start(INTERVAL_TIMER_TIMER_0);  // Start timer 0.
  // Show that the timer is running.
  printf("The following register values should be changing while reading them.\n\r");
  // Just checking multiple times to see if the timer is running.
  printf("timer_0 TCR0 should be changing at this point:%ld\n\r", readTimer1Register(TCR0_OFFSET));
  printf("timer_0 TCR0 should be changing at this point:%ld\n\r", readTimer1Register(TCR0_OFFSET));
  printf("timer_0 TCR0 should be changing at this point:%ld\n\r", readTimer1Register(TCR0_OFFSET));
  printf("timer_0 TCR0 should be changing at this point:%ld\n\r", readTimer1Register(TCR0_OFFSET));
  printf("timer_0 TCR0 should be changing at this point:%ld\n\r", readTimer1Register(TCR0_OFFSET));
  // Wait about 2 minutes so that you roll over to TCR1.
  // If you don't see a '1' or '2' in TCR1 after this long wait you probably haven't programmed the timer correctly.
  waitALongTime();
  // Check lower register.
  printf("timer_0 TCR0 value after wait:%lx\n\r", readTimer1Register(TCR0_OFFSET));
  // Check upper register.
  printf("timer_0 TCR1 should have changed at this point:%ld\n\r", readTimer1Register(TCR1_OFFSET));
}

#define TEST_ITERATION_COUNT 4
#define ONE_SECOND_DELAY 1000
void milestone2() {
  printf("=============== Starting milestone 2 ===============\n\r");
  double duration0, duration1, duration2;  // Will hold the duration values for the various timers.
  buttons_init();           // init the buttons package.
  intervalTimer_initAll();  // init all of the interval timers.
  intervalTimer_resetAll(); // reset all of the interval timers.
  // Poll the push-buttons waiting for BTN0 to be pushed.
  printf("Interval Timer Accuracy Test\n\r");     // User status message.
  printf("waiting until BTN0 is pressed.\n\r");   // Tell user what you are waiting for.
  while (!(buttons_read() & BUTTONS_BTN0_MASK));  // Loop here until BTN0 pressed.
  // Start all of the interval timers.
  intervalTimer_start(INTERVAL_TIMER_TIMER_0);
  intervalTimer_start(INTERVAL_TIMER_TIMER_1);
  intervalTimer_start(INTERVAL_TIMER_TIMER_2);
  printf("started timers.\n\r");
  printf("waiting until BTN1 is pressed.\n\r");  // Poll BTN1.
  while (!(buttons_read() & BUTTONS_BTN1_MASK)); // Loop here until BTN1 pressed.
  // Stop all of the timers.
  intervalTimer_stop(INTERVAL_TIMER_TIMER_0);
  intervalTimer_stop(INTERVAL_TIMER_TIMER_1);
  intervalTimer_stop(INTERVAL_TIMER_TIMER_2);
  printf("stopped timers.\n\r");
  // Get the duration values for all of the timers.
  duration0 = intervalTimer_getTotalDurationInSeconds(INTERVAL_TIMER_TIMER_0);
  duration1 = intervalTimer_getTotalDurationInSeconds(INTERVAL_TIMER_TIMER_1);
  duration2 = intervalTimer_getTotalDurationInSeconds(INTERVAL_TIMER_TIMER_2);
  // Print the duration values for all of the timers.
  printf("Time Duration 0: %6.2e seconds.\n\r", duration0);
  printf("Time Duration 1: %6.2e seconds.\n\r", duration1);
  printf("Time Duration 2: %6.2e seconds.\n\r", duration2);
  // Now, test to see that all timers can be restarted multiple times.
  printf("Iterating over fixed delay tests\n\r");
  printf("Delays should approximately be: 1, 2, 3, 4 seconds.\n\r");
  printf("Note that delays may be about 25%%-30%% lower if your compiler optimizations are set to -O2 or -O3\n\r");
  for (int i=0; i<TEST_ITERATION_COUNT; i++) {
    // Reset all the timers.
    intervalTimer_resetAll();
    // Start all the timers.
    intervalTimer_start(INTERVAL_TIMER_TIMER_0);
    intervalTimer_start(INTERVAL_TIMER_TIMER_1);
    intervalTimer_start(INTERVAL_TIMER_TIMER_2);
    // Delay is based on the loop count.
    utils_msDelay((i+1)*ONE_SECOND_DELAY);
    // Stop all of the timers.
    intervalTimer_stop(INTERVAL_TIMER_TIMER_0);
    intervalTimer_stop(INTERVAL_TIMER_TIMER_1);
    intervalTimer_stop(INTERVAL_TIMER_TIMER_2);
    // Print the duration of all of the timers. The delays should be approximately 1, 2, 3, and 4 seconds.
    printf("timer:(%d) duration:%f\n\r", INTERVAL_TIMER_TIMER_0, intervalTimer_getTotalDurationInSeconds(INTERVAL_TIMER_TIMER_0));
    printf("timer:(%d) duration:%f\n\r", INTERVAL_TIMER_TIMER_1, intervalTimer_getTotalDurationInSeconds(INTERVAL_TIMER_TIMER_1));
    printf("timer:(%d) duration:%f\n\r", INTERVAL_TIMER_TIMER_2, intervalTimer_getTotalDurationInSeconds(INTERVAL_TIMER_TIMER_2));
  }
  // Now, test for increment timing (start-stop-start-stop...)
  // Reset all the timers.
  intervalTimer_resetAll();
  for (int i=0; i<TEST_ITERATION_COUNT; i++) {
    // Start all the timers.
    intervalTimer_start(INTERVAL_TIMER_TIMER_0);
    intervalTimer_start(INTERVAL_TIMER_TIMER_1);
    intervalTimer_start(INTERVAL_TIMER_TIMER_2);
    // Delay is based on the loop count.
    utils_msDelay((i+1)*ONE_SECOND_DELAY);
    // Stop all of the timers.
    intervalTimer_stop(INTERVAL_TIMER_TIMER_0);
    intervalTimer_stop(INTERVAL_TIMER_TIMER_1);
    intervalTimer_stop(INTERVAL_TIMER_TIMER_2);
    // Print the duration of all of the timers. The delays should be approximately 1, 3, 6, and 10 seconds.
    printf("Delays should approximately be: 1, 3, 6, 10 seconds keeping in mind that you times may be lower due to compiler-optimization settings.\n\r");
    printf("timer:(%d) duration:%f\n\r", INTERVAL_TIMER_TIMER_0, intervalTimer_getTotalDurationInSeconds(INTERVAL_TIMER_TIMER_0));
    printf("timer:(%d) duration:%f\n\r", INTERVAL_TIMER_TIMER_1, intervalTimer_getTotalDurationInSeconds(INTERVAL_TIMER_TIMER_1));
    printf("timer:(%d) duration:%f\n\r", INTERVAL_TIMER_TIMER_2, intervalTimer_getTotalDurationInSeconds(INTERVAL_TIMER_TIMER_2));
  }

  printf("intervalTimer Test Complete.\n\r");
}

// main executes both milestones.
int main() {
  milestone1();  // Execute milestone 1
  milestone2();  // Execute milestone 2
}

void isr_function(){}

lab_3.txt · Last modified: 2018/05/18 11:08 by hutch