EECS 373 Lab 5: Clocks, Timers, and Counters

Copyright © 2010 - Thomas Schmid, Matt Smith, Ye-Sheng Kuo, and Prabal Dutta.

Schedule

See posted lab schedule for due dates, in-lab, and post-lab due dates.

Objectives

The purpose of this lab is to learn about the importance of timers and clocks in embedded systems.

  1. Understand how clocks provide a time source
  2. Compare units to trigger events at specific times
  3. Capture units to measure the time of events

Background

Timers are some of the most fundamental peripherals found in embedded microcontrollers. They are used in many application scenarios, like driving motors, measuring frequencies, or measuring the event time of a particular interrupt.

Additional Material

Overview

Pre-Lab Assignment

  1. Read through the lab 5 notes.
  2. Read Chapter 11: Timer_A from MSP430x1xx Family User's Guide

In-Lab Assignment

  1. Clock Sources and Counters
  2. Timed Interrupts
  3. Pulse Width Modulated Signals
  4. Capturing Time

In-Lab Assignment

Clock Sources and Counters

You have encountered the clock system of the SmartFusion before. The bus system AHBlite or APB3 both use the FAB_CLOCK configured in the MSS Configurator's Clock Management block. The Clock Management has the possibility to configure several different clocks. One of them is the clock going into the MSS (free running clock FCLK) and clocking most of the MSS Peripherals (exception is the 10/100 Ethernet which has its own clock). The other clocks can be fed into the FPGA. While the MSS clock FCLK has a recommended maximum of 100 MHz, the FPGA can use faster clocks, depending on the logic.

In this lab, we will create a fast timer unit that runs at 120 MHz. For that purpose, first create a new project in Libero and add a MSS component to it. Then, open the MSS Configurator and click on the Clock Management block. Configure the clocks as shown below. Note that you first have to enable the GLC clock signal before you can change it's MUX. (Click image to see larger version).

Your clocks should now be

Next, configure your MSS as shown below (click to see larger version)

Don't forget to add the APB master interface and the MSS2FABRIC reset. We will add some more components later, once we need them.

On the Firmware tab, generate drivers for the HAL_0, GPIO, UART, Watchdog, and CMSIS. Uncheck all the other drivers. Once done, save the configuration and hit generate. If you get any errors, make sure that you relocated your vault in case you are on a shared computer!

Next, add a CoreAPB3 Bus Interface to your top level design with 1 slot and 32-bit data bus width. The timer we will create will use this slot.

The last part for the FPGA is the actual timer block. Use the following verilog timer component to start with.

// timer.v

module timer(
  PCLK,
  PENABLE,
  PSEL,
  PRESETN,
  PWRITE,
  PREADY,
  PSLVERR,
  PADDR,
  PWDATA,
  PRDATA,
  TCLK,
  test_out);

// APB Bus Interface
input PCLK,PENABLE, PSEL, PRESETN, PWRITE;
input  [31:0] PWDATA;
input  [3:0] PADDR;
output [31:0] PRDATA;
output PREADY, PSLVERR;

// Timer Interface
input TCLK;

// Test Interface
output [3:0] test_out;

// Clock Domain synchronization
reg PREADY;
reg [1:0] fsm; //state machine register for pclk domain
reg fsm_tclk;  // state machine register for tclk domain
reg [31:0]   PRDATA;

// Timer
reg [31:0] Counter;
reg [31:0] Load;
reg TimerEn;   // Timer enable
reg LoadEnReg; // Register Load enable

// Handshaking signals
reg reg_load_en0, reg_load_en1, reg_load_en2;
reg reg_load_ack0, reg_load_ack1, reg_load_ack2;

// Test Outputs
wire [3:0] test_out;
assign test_out[0] = 1'b0;
assign test_out[1] = 1'b0;
assign test_out[2] = 1'b0;
assign test_out[3] = 1'b0;

assign PSLVERR = 1'b0;         

// Handshaking signal PCLK signals to TCLK
always@(posedge PCLK or negedge PRESETN)
if(~PRESETN)
  begin
    reg_load_en1 <= 1'b0;
    reg_load_en2 <= 1'b0;
  end
else
  begin
    reg_load_en1 <= reg_load_en0;
    reg_load_en2 <= reg_load_en1;
  end
  
// Handshaking signal TCLK signals to PCLK
always@(posedge TCLK or negedge PRESETN)
if(~PRESETN)
  begin
    reg_load_ack1 <= 1'b0;
    reg_load_ack2 <= 1'b0;
  end
else
  begin
    reg_load_ack1 <= reg_load_ack0;
    reg_load_ack2 <= reg_load_ack1;
  end
  
// Handle APB Bus read and writes and inform TCLK clock domain
always@(posedge PCLK or negedge PRESETN)
if(~PRESETN)
  begin
      fsm <= 2'b00;
      PREADY <= 1'b1;
      reg_load_en0 <= 1'b0;
      end
else
  begin
  case (fsm)
     2'b00 :  begin
                  if (~PSEL)
                        begin
                          // not for us
                          fsm <= 2'b00;
                        end
                  else        
                        begin
                          fsm <= 2'b01; // advance to next state
                          PREADY <= 1'b0; // signal we are not ready
                          reg_load_en0 <= 1'b1; // inform other clock domain
                        end
               end

     2'b01 :  begin            
                if(reg_load_ack2 == 1'b1)
                  begin
                    fsm <= 2'b10;
                    reg_load_en0 <= 1'b0;
                  end
              end

     2'b10 :  begin            
                if(reg_load_ack2 == 1'b0)
                  begin
                    fsm <= 2'b11;
                    PREADY <= 1'b1; // we are ready
                  end
              end

     2'b11 :  begin            
                fsm <= 2'b00;
              end

    default : fsm <= 2'b00;
  endcase
end

always@(posedge TCLK or negedge PRESETN)
if(~PRESETN)
  begin
    fsm_tclk <= 1'b0;
    reg_load_ack0 <= 1'b0;
    LoadEnReg <= 1'b0;
    TimerEn <= 1'b0;
    Load <= 32'h00000000;
  end
else
  begin
    case (fsm_tclk)
      1'b0: begin
              if(reg_load_en2 == 1'b1) // signal from other clock domain
                begin
                  if(PWRITE)
                    begin // it's a write
                        case(PADDR[3:2])
                          2'b00: begin // Timer Load register
                                    Load <= PWDATA;
                                    LoadEnReg <= 1'b1;
                                 end
                          2'b01: begin // Timer Value, no write
                                    LoadEnReg <= 1'b0;
                                 end
                          2'b10: begin // Timer Control
                                    LoadEnReg <= 1'b0;
                                    TimerEn <= PWDATA[0];
                                 end
                          2'b11: begin // spare
                                    LoadEnReg <= 1'b0;
                                 end
                        endcase
                    end
                  else
                    begin // it's a read
                        case(PADDR[3:2])
                          2'b00: begin // Timer Load register
                                    PRDATA <= Load;
                                 end
                          2'b01: begin // Timer Value, no write
                                    PRDATA <= Counter;
                                 end
                          2'b10: begin // Timer Control
                                    PRDATA[31:1] <= 31'h00000000;
                                    PRDATA[0] <= TimerEn;
                                 end
                          2'b11: begin // spare
                                 end
                        endcase
                    end
                  fsm_tclk <= 1'b1; // advance state
                  reg_load_ack0 <= 1'b1; // signal other domain
                end
            end
      1'b1: begin
              if (reg_load_en2 == 1'b0)
                begin
                  fsm_tclk <= 1'b0;
                  reg_load_ack0 <= 1'b0;
                  LoadEnReg <= 1'b0;
                end
            end
      default: fsm_tclk <= 1'b0;
    endcase
  end

  
always@(posedge TCLK or negedge PRESETN)
if(~PRESETN)
  begin
    Counter <= 32'h00000000;
  end
else
  begin
    if(LoadEnReg == 1'b1)
      begin
        Counter <= 32'h00000000;
      end
    else if(TimerEn == 1'b1)
      begin
        if(Counter == Load)
          begin
            Counter <= 32'h00000000;
          end
        else
          begin
            Counter <= Counter + 1;
          end
      end
  end

endmodule

Go through the code and try to understand it. As you can see, it does have an APB bus interface. However, it uses two clocks! Because we do not know what the phase between the clocks is, we have to be extremely careful in passing data from one clock domain (the bus) to the other (the timer).

Finish your FPGA design by wiring up the timer as shown below. Note that we didn't connect the test_out signal to anything. If you wish, connect them to the 4 user I/O lines of the eval board. They can become handy to debug your code later on:

Note that we connected the FAB_CLK to the bus interface's PCLK and the GLC clock to the timer clock TCLK.

Passing Clock Domains

Passing between different clock domains is always dangerous and prone to glitches, i.e., one domain is updating a value, while the other domain starts already reading it. This will eventually lead to corrupted data and information. We have seen such a problem already in the last lab where we had to synchronize the switch I/O line to the clock system using 3 signals.

This time, we have to synchronize the APB bus domain clocked by PCLK to the timer domain clocked by TCLK. We achieve this by a hand-shaking mechanism.

Find the two hand-shaking blocks at the beginning of timer.v. Notice how one of the blocks is sensitive to PCLK, while the other one is sensitive to TCLK. Similar to the switch synchronization, we also use 3 signals each way, i.e., reg_load_enX to signal from the bus to the timer domain, and reg_load_ackX from the timer to the bus domain.

The signals are used to synchronize the two state machines in timer.v.

Draw the state diagrams for both state machines. Whenever a signal influences the state of the other state machine, draw an arrow from that variable to the state in the other state machine.

Our timer is still very simple and has only 3 registers defined:

OffsetTypeResetNameDescription
0x0W0LoadTimer Load
0x4R0CounterTimer Value
0x8R/W0 Timer Control
Bit 0: Enable bit

The configuration bit 0 enables or disables the incrementing counter. When enabled, the counter will increment up to the value stored in the Load register. Once reached, it resets to 0 and starts counting again. The Value registers contains the current value of the counter.

Now it's time to generate your smart design, synthesize it, place and route it, and then program your FPGA with it.

A Simple MyTimer Library

We will now write a simple timer library that you can use in your application. Create a new SoftConsole Project and import the CMSIS and drivers folders from the firmware directory.

Add files for main.c, mytimer.c, and mytimer.h.

Following is the content for main.c:

#include <stdio.h>
#include "drivers/mss_uart/mss_uart.h"
#include "drivers/mss_watchdog/mss_watchdog.h"
#include "mytimer.h"

int main()
{
    /* Watchdog Disabling function */
    MSS_WD_disable();

    /* Setup MYTIMER */
    MYTIMER_init();
    MYTIMER_load((1<<31));
    MYTIMER_enable();

    while(1)
    {
        uint32_t i;
        printf("Time: %lu\r\n", MYTIMER_value());
        for(i=1e6; i>0; i--);
    }
    return 0;
}
Following is the content for mytimer.c:
#include "mytimer.h"

void MYTIMER_init()
{
    // we don't have to do anything.
}

void MYTIMER_enable()
{
    MYTIMER->control |= MYTIMER_ENABLE_MASK;
}

void MYTIMER_disable()
{
    MYTIMER->control &= ~MYTIMER_ENABLE_MASK;
}

void MYTIMER_load(uint32_t load)
{
    MYTIMER->load = load;
}

uint32_t MYTIMER_value()
{
    return MYTIMER->value;
}
Following is the content for mytimer.h:
#ifndef MYTIMER_H_
#define MYTIMER_H_

#include "CMSIS/a2fxxxm3.h"

#define MYTIMER_BASE (FPGA_FABRIC_BASE + 0x0)

typedef struct
{
    __IO uint32_t load;
    __I  uint32_t value;
    __IO uint32_t control;
} mytimer_t;

#define MYTIMER_ENABLE_MASK 0x00000001UL

#define MYTIMER ((mytimer_t *) MYTIMER_BASE)

/**
 * Initialize the MYTIMER
 */
void MYTIMER_init();

/**
 * Start MYTIMER
 */
void MYTIMER_enable();

/**
 * Stop MYTIMER
 */
void MYTIMER_disable();

/**
 * Set the limit to which the timer counts.
 */
void MYTIMER_load(uint32_t load);

/**
 * Read the counter value of the timer.
 */
uint32_t MYTIMER_value();


#endif /* MYTIMER_H_ */

Note how the three files play together, and how we defined the timer library. Using a simple structure, we index all the registers available on the timer peripheral. This is a very typical way of writing peripheral drivers. For example, look at the files in drivers/mss_uart. You will see that they follow a similar structure.

What is the purpose of the __I and __IO in the typedef of mytimer_t? Are there any other modifiers? Explain in two sentences.

Don't forget to define the ACTEL_STDIO_THRU_UART to allow printf to work. See Lab 4 if you are unsure on how to do this. You will also have to add the linker script to the GNU Linker setting of the project, before you will be able to compile your code.

Compile your code and run the application. Connect the serial terminal application to the serial port of the eval board. You should now see numbers being printed on your terminal.

You can see that the numbers overflow after a certain amount of time. Measure that time using a stop watch and calculate the clock frequency. Write down your result. Does it agree with the frequency specified in the MSS Configurator Clock Manager block? Explain in two sentences.

Timed Interrupts

So far, your timer unit provides you time through a software interface. We will now add some more functionality. One of the basic functions of a timer is a timed interrupt. You program the timer unit with a specific time, and an interrupt will fire upon reaching that time.

In the last lab, you learned how to create interrupts on the FPGA, and how to process them in the interrupt service routine. Let's add two interrupt sources to our timer, both sharing the same interrupt line FABINT. An interrupt register will indicate in software which interrupt actually fired. This is very similar to the last lab where we had two interrupt sources, the two switches, sharing the FABINT interrupt line.

The two interrupt sources should fire when the timer reaches two specific values:

  1. When the timer overflows and goes back to 0, i.e., when it reaches the Load value
  2. When the timer reaches a specific Compare value
The first interrupt is easy to implement. For the second interrupt, you will have to add a new register to your timer that contains the Compare value.

Together with the interrupts, we will also need some more control bits

  1. InterruptEnable: Enables interrupts
  2. CompareEnable: Enable the Compare interrupt
  3. OverflowEnable: Enable the Overflow interrupt

The one thing we will have to be careful about is the different clock domains. As in the last lab, the timer should be considered asynchronous to the fabric clock. Thus, you will have to use a similar technique to provide a synchronous interrupt to the fabint.

In summary, we will make the following modifications to the code:

  1. Add the FABINT interrupt inside the MSS Configurator
  2. Add an output interrupt port to the timer.v module
  3. Add a pulsed and synchronized to PCLK interrupt to the module.
  4. Add two new registers to the module. Note that we will have to widen the address bus for this. One register will be the interrupt status, the other the Compare register.
  5. Add the control signals to enable/disable the two interrupts
  6. Add the overflow interrupt logic, making sure to check for the interrupt enable signals.
  7. Add the reset interrupt register and maaking sure that it gets reset upon a read from the Cortex-M3.
  8. Add the compare interrupt and update the interrupt status register.
  9. Update the timer unit in the smart designer, re-add the bus definition, and connect the FABINT.

The perform all the steps above, use the following verilog code: timer_with_interrupts.v. Replace your previous timer.v file with this implementation and update your smart designer correspondingly. Don't forget to add the FAB_INT to your MSS component!

SoftConsole Code

Having more functionality in your peripheral necessitates to update your timer driver. Add the following additional functions form this header file to your library. Make sure that the different enable and disable functions really toggle the right bits in the configuration register.:

/**
 * Enable all interrupts
 */
void MYTIMER_enable_interrupts();

/**
 * Disable all interrupts
 */
void MYTIMER_disable_interrupts();

/**
 * Enable compare interrupt
 */
void MYTIMER_enable_compare();

/**
 * Disable compare interrupt
 */
void MYTIMER_disable_compare();

/**
 * Set Compare value
 */
void MYTIMER_compare(uint32_t compare);

/**
 * Enable overflow interrupt
 */
void MYTIMER_enable_overflow();

/**
 * Disable overflow interrupt
 */
void MYTIMER_disable_overflow();

 /**
  * Interrupt status
  */
uint32_t MYTIMER_interrupt_status();

Implement the missing MYTIMER functions in your timer.c file.

Once implemented, replace your main function with the following code. Notice that we added the Fabric interrupt handler too:

__attribute__ ((interrupt)) void Fabric_IRQHandler( void )
{
    uint32_t time = MYTIMER_value();
    uint32_t status = MYTIMER_interrupt_status();

    printf("Interrupt occurred at %lu FABINT \n\r", time);

    if(status & 0x01)
    {
        printf("Overflow latency %ld\n\r", 0-time);
    }
    if(status & 0x02)
    {
        printf("Compare latency %ld\n\r", (1<<29) - time);
    }
    NVIC_ClearPendingIRQ( Fabric_IRQn );
}

int main()
{
    /* Watchdog Disabling function */
    MSS_WD_disable();

    /* Setup MYTIMER */
    MYTIMER_init();
    MYTIMER_load((1<<30));
    MYTIMER_compare((1<<29)); // 50%

    MYTIMER_enable_overflow();
    MYTIMER_enable_compare();
    MYTIMER_enable_interrupts();

    NVIC_EnableIRQ(Fabric_IRQn);

    MYTIMER_enable();

    while(1){}
    return 0;
}
Program your eval board with the application and connect the serial terminal. Observe the latency and convert it to seconds, either in your output by modifying the printf functions, or on paper.

How does the latency compare to the latency measured with the oscilloscope in Lab 4? Explain in two sentences.

Pulse Width Modulated Signals

We have almost all the pieces together for a Pulse Width Modulated (PWM) signal generator. PWM signals are very common in embedded systems to deliver a variable amount of energy to an end system, such as LEDs, robotic servos, heating plates, etc. There are two parameters to a PWM system: frequency and duty-cycle. The frequency indicates how often we have a positive edge, while the duty-cycle describes the amount of high time vs. low time of the signal, and is usually expressed in percent. For example, a system that has a pulse every second, then stays on for 100 milliseconds, before going low for 900 milliseconds would have a duty cycle of 10%.

With the timer unit we have, it is really easy to generate a PWM system. We just have to set a signal to high when we get to the Compare value, and set it back to low when we reach Load. In that respect, the Load value will determine the period, or frequency, of our signal, while the Compare value determines the duty cycle.

Modify the timer unit and add the PWM output. Add a control register bit that can enable and disable that output. Connect the PWM output to one of the LEDs on your eval board.

Add PWM capabilities to your timer library, and write an application that generates a PWM signal at 100Hz and changes the duty cycle from 0 to 100% over 1 second. Observe the signal on an oscilloscope and the LED. Describe your observation in a few sentences and include a screenshot of the oscilloscope.

Capturing Time

The compare capabilities of a timer allow timed events to happen. The Capture capabilities allow the inverse, i.e., they measure the time of external events. As you saw in the last lab, the handling of interrupt service routines can have significant latency and jitter. Thus, we can not just read the time in software once we entered an interrupt service routine. The capture unit instead grabs the current time inside the timer unit and stores it in a register. It then signals to the core using the timer interrupt that a capture happened. The software can then, at its own convenience, read the capture register.

Let's add capture capabilities to our timer unit:

Use the following file as a reference to implement the above capabilities: timer_with_captures.v. Make sure you connect one of the switches to the capture input. Synthesize, place and route, and program the eval board with your new code.

We now have to add the new capabilities to our timer library. Implement the following new functions in your mytimer.c file.

/**
 * Enable Capture
 */
void MYTIMER_enable_capture();

/**
 * Disable Capture
 */
void MYTIMER_disable_capture();

/**
 * Read the synchronous capture value
 */
uint32_t MYTIMER_sync_capture();

/**
 * Read the asynchronous capture value
 */
uint32_t MYTIMER_async_capture();

/**
 * Returns 1 if the capture has been overwritten.
 */
uint8_t MYTIMER_capture_overwrite();

Write a little application that upon pressing the switch outputs the synchronous and asynchronous capture times, including the difference between them.

What do you observe between the synchronous and asynchronous capture values? Explain in a couple of sentences.

Change the GLC clock to 2.5 MHz, instead of 120 MHz and repeat the last experiment. Again, explain your findings.

Write a small reaction game. At a specific time, using the PWM output, toggle the LED on. The user has to hit the push button when he sees the light turning on. Then, output the time difference between LED toggle and button push on the serial terminal. How good is your reaction time in seconds?

Timing Analysis

Did you notice the symbol during the place and route of your timer unit in Designer? If not, open up the Designer and look at the Layout button. What does it mean? Click on the Timing Analyzer. Which signals are problematic? Describe in a few sentences what the problem is, and two different ways to solve it.

Post-Lab Assignment

During the timer lecture, we talked about virtualizing a hardware timer in software, such that it can be shared by several software routines. Implement the timer virtualization interface provided during the lecture, and write an application using it. The application should toggle the 8 LEDs. But every LED should toggle at a different rate, and it should be toggled from its own timer handler.

Using the notation provided during the timer lecture, your main.c function should looks something like this. Note that left out the toggling of LED code and the initialization of your hardware timer from this pseudo code for brevity reasons:

#include "myvirtualtimer.h"

void led0() {
    // toggle LED 0
}

void led1() {
    // toggle LED 1
}

...

void led7() {
    // toggle LED 7
}

int main() {

    initTimer();

    startTimerContinuous(&led0, 200);
    startTimerContinuous(&led1, 32768);
    ....
    startTimerContinuous(&led7, 1e8);


    while(1){}

    return 0;
}

For reference, following is the proposed myvirtualtimer.h file:

typedef void (*timer_handler_t)(void);

typedef struct timer
{
    timer_handler_t handler;
    uint32_t        time;
    uint8_t         mode;
    timer_t*        next_timer;
} timer_t;


/* initialize the virtual timer */
void initTimer();

/* start a timer that fires at time t */
error_t startTimerOneShot(timer_handler_t handler, uint32_t t);

/* start a timer that fires every dt time interval*/
error_t startTimerContinuous(timer_handler_t handler, uint32_t dt);

/* stop timer with given handler */
error_t stopTimer(timer_handler_t handler);
And following is a proposed myvirtualtimer.c file including pseudo code:
#include "myvirtualtimer.h"

timer_t* current_timer;

void initTimer() {
    setupHardwareTimer();
    initLinkedList();
    current_timer = NULL;
}

error_t startTimerOneShot(timer_handler_t handler, uint32_t t) {
    // add handler to linked list and sort it by time
    // if hardware timer not running, start hardware timer 
}

error_t startTimerContinuous(timer_handler_t handler, uint32_t dt) {
    // add handler to linked list for (now+dt), set mode to continuous
    // if hardware timer not running, start hardware timer
}

error_t stopTimer(timer_handler_t handler) {
    // find element for handler and remove it from list
}

__attribute__((__interrupt__)) void Timer1_IRQHandler() {
    timer_t * timer;
    MSS_TIM1_clear_irq();
    NVIC_ClearPendingIRQ( Timer1_IRQn );
    timer = current_timer;

    if( current_timer->mode == CONTINUOUS ) {
        // add back into sorted linked list for (now+current_timer->time)
    }

    current_timer = current_timer->next_timer;   

    if( current_timer != NULL ) {
        // set hardware timer to current_timer->time
        MSS_TIM1_enable_irq();
    } else {
        MSS_TIM1_disable_irq();
    }

    (*timer->handler))(); // call the timer handler

    if( timer->mode != CONTINUOUS ) {
        free(timer); // free the memory as timer is not needed anymore
    }
}
This pseudo code uses the MSS System Timer 1 as timed interrupt source. Feel free to use your own timer implementation from this lab. Or, you can use the System Timer 1, though you will have to figure out how to initialize it in your initTimer function.

Deliverables

You may work with a partner for this assignment. Submit a zip file of your SoftConsole workbench, and a second zip file of your Libero project for your post lab assignment to eecs373@gmail.com with the subject f10-lab5:uniquename1,uniquename2. Hand in your answers from the questions asked during the lab. You can use the following Answer Sheet. Before handing your solution in, show your application to a lab staff and get a signature on your title page.