7. Timers: Countdown Timer

7.1. Purpose

This activity introduces basic use of hardware timers to measure the passage of time.

7.2. Hardware and Tools

  • [USB Test Device]: ADALM1000, ADALM2000, or Analog Discovery

  • MSP-EXP432P401R Launchpad Development Board, OR TI-RSLK Robotic Car

7.2.1. Project Template

7.3. Description

A fundamental feature of a microcontroller is the ability to keep track of the passage of time. This capability opens a broad range of possibilities; these include the obvious applications: providing system delays and precise sequencing of events; to other applications, such as fine control of motor outputs, time-based measurements (e.g., speed), and signal processing (e.g., filters).

We will be using one of the MSP432P401R Timer_A modules in this activity to build a basic “countdown timer”, which will start a predefined number of minutes and count down to zero. The program will update the terminal every second to display the current minutes and seconds left. To configure the Timer_A module to do this, we will use the DriverLib as introduced in Activity 5 instead of using registers.

The TI DriverLib is more complicated for the Timer_A module as compared to the GPIO (and the other timers, for that matter). There are many different modes the timer can operate in and for each mode, there is a required initialization struct. For this activity, we will operate in Up Mode, where the timer counts from 0 to a specified value and then resets.

All DriverLib definitions for structs, functions, and values are already defined and do not need to be recreated.

The first argument for all Timer_A DriverLib functions is the timer module to use: uint32_t timer. The MSP432P401R has 4 Timer_A modules and can be selected using the predefined values TIMER_A0_BASE, TIMER_A1_BASE, TIMER_A2_BASE, and TIMER_A3_BASE.

7.3.1. Up Mode Configuration

See also

The description provided below of the Up Mode is a snippet of the Timer documentation provided in the condensed DriverLib references here: Timer_A.

This configuration requires the creation of a Timer_A_UpModeConfig type struct. The table below shows the fields for this configuration struct.

Timer_A_UpModeConfig struct fields

Field

Possible Values

.clockSource

TIMER_A_CLOCKSOURCE_EXTERNAL_TXCLK

TIMER_A_CLOCKSOURCE_ACLK

TIMER_A_CLOCKSOURCE_SMCLK

TIMER_A_CLOCKSOURCE_INVERTED_EXTERNAL_TXCLK

.clockSourceDivider

TIMER_A_CLOCKSOURCE_DIVIDER_X

where X is one of:

1 2 3 4 5 6 7 8 10 12 14

16 20 24 28 32 40 48 56 64

.timerPeriod

0 to 65535

.timerInterruptEnable_TAIE

Ignore for now

.captureCompareInterruptEnable_CCR0_CCIE

Ignore for now

.timerClear

TIMER_A_DO_CLEAR

TIMER_A_SKIP_CLEAR

Field Descriptions:
  • .clockSource indicates what the timer module should use as its counting base. For the purposes of this course, we will always select the “Low-speed subsysem master clock”, or SMCLK, which is set to be 24 MHz.

  • .clockSourceDivider allows for the selected .clockSource to be “divided” in frequency. For example, if this field was set to TIMER_A_CLOCKSOURCE_DIVIDER_12, the timer would would increment at a rate of 24 MHz / 12 = 2 MHz.

  • .timerPeriod specifies the number of timer increments in one full counting cycle, and is of type uint16_t. Note that the rate of the increments is determined by both the previous fields.

  • .timerClear will cause the current count value, among others, to be reset to 0 upon configuration. It is best to request a timer clear during a (re)configuration such that the timer always starts counting from a known state.

With the configuration struct filled in, the timer can be initialized with the function:

void Timer_A_configureUpMode( uint32_t timer, Timer_A_UpModeConfig * config)

The two arguments to this function are timer: a declaration of the timer instance to be configured; and config: the configuration struct as described above. There are four timer instances on the MSP432 and can be selected via the name TIMER_Ax_BASE, where x is either 0, 1, 2, or 3 (e.g., TIMER_A2_BASE). Note that the * within the second argument indicates that this is passed as a pointer, which is simply done by adding a & before the struct variable name to be passed.

7.3.2. Timer Control

Configuration of the timer will not start the timer. This requires the function

void Timer_A_startCounter( uint32_t timer , uint16_t timerMode)

where timer is the same definition as above and timerMode can be either TIMER_A_CONTINUOUS_MODE, TIMER_A_UPDOWN_MODE, or TIMER_A_UP_MODE. Likewise, the counter may be paused using

void Timer_A_stopTimer( uint32_t timer )

7.3.3. Timer Status

The timer’s counter value can be monitored by calling the function

uint16_t Timer_A_getCounterValue( uint32_t timer )

This function will return the value of the current timer count, where the timer module is specified by timer. It should be noted, however, that this function is slow to evaluate: this function will take more than one timer increment to complete for all values of .clockSourceDivider other than TIMER_A_CLOCKSOURCE_DIVIDER_64. This means that if this function was called continuously, the values returned would skip values (e.g., 1,2,4,5,7,8) as it cannot read and return the timer’s count value fast enough.

Timer overflows or resets may detected with the function

uint32_t Timer_A_getInterruptStatus( uint32_t timer )

which will return TIMER_A_INTERRUPT_PENDING (value: 0x01) for if an overflow has occurred and TIMER_A_INTERRUPT_NOT_PENDING (value: 0x00) if not. When an overflow has been detected, the flag denoting this must be reset to detect the next overflow. This can be done through the function

Timer_A_clearInterruptFlag( uint32_t timer )

7.4. Instructions

  1. First, download and import the course template project: TemplateProject.zip. You should rename the imported project to something like “Activity-Timer”. If the TemplateProject.zip will not import properly, see here: Cannot import project ZIP archive.

  2. Create four global variables:

    1. One Timer_A_UpModeConfig type struct variable, which will be used to configure the timer. Note that the variable type Timer_A_UpModeConfig already exists within the DriverLib and does not need to be replicated.

    2. Three uint8_t variables: one to track minutes, another to track seconds, and a third to track the number of timer overflows.

Warning

Although the Timer_A_UpModeConfig struct variable is only used in Timer_Init() (next step), the variable must be a global due to how it is passed into Timer_A_configureUpMode(); that is, as a pointer. In some instances, passing local variables as pointers (by reference) will not work properly.

  1. Add a Timer_Init() function that will configure Timer A1 to count using SMCLK and a divider of 20. Further, set the period such that it will elapse once every 10 ms. You may ignore the “interrupt” specific fields in the initialization struct. Do not forget to apply the struct field configuration to the timer by using Timer_A_configureUpMode() after specifying values to the Timer_A_UpModeConfig struct.

Hint

While we are looking for a period of 10 ms, specifying .timerPeriod field as 10 (or 0.01) is incorrect. The unit for this field is not seconds, it is the number of timer increments in the desired period.

  1. Use Timer_A_startCounter() to start the timer within the Timer_Init() function. Not that this function must occur after the timer is configured.

  2. Add the Timer_Init() function to the appropriate location in the main() function.

  3. Also create a GPIO_Init() function to initialize P1.0 (LED1) and P2.4 as Outputs. Call this function at the appropriate location in the main() function.

  4. Within the main() function while(1) loop, check to see if the timer has completed a full period. This can be done by checking to see if a timer overflowed (or reset) has occurred, and then clearing the flag that signals that the overflow (or reset) occurred. This may be implemented using the uint32_t Timer_A_getInterruptStatus() and Timer_A_clearInterruptFlag() functions, respectively.

    Warning

    Do not use uint32_t Timer_A_getEnabledInterruptStatus() here, it will not work as the interrupt has not been enabled. You must use uint32_t Timer_A_getInterruptStatus().

  5. Add code to toggle P2.4 each time the timer completes a period. Additionally, toggle P1.0 (LED1) every 100 times the timer completes a period. Template code is provided below to aide in implementing this. A short printf() or putchar() may also be useful for debugging.

    counter++; // overflow counting variable
    if(counter == 100){
        // Toggle LED1 (P1.0)
        counter = 0;
    }
    
  6. Run the code and verify that pin P2.4 toggles at a rate of 10 ms using your USB Test Device. Note that since P2.4 is intended to toggle every 10 ms, the period of the resulting square wave on P2.4 should be measured as 20 ms. Likewise, the onboard LED1 should blink at a rate of 0.5 Hz (2 s period). If the timing is not correct, review your timer calculations and correct the code. Take a screenshot of the USB Test Device screen with the period measurement shown once working properly.

    1. Change the .clockSourceDivider to a different value greater than 10 and measure the period again. Does the change make sense with respect to equations given in the lecture? Take a screenshot of the USB Test Device screen with the period measurement shown.

    2. Set the .clockSourceDivider back to 20.

  7. Create a function uint8_t UpdateCountdown(), which will be called each time one second has passed. Within this function, write code that follows the below pseudocode:

    If the seconds tracking variable is 0
        reset the seconds tracking variable to 60
        reduce minutes remaining by 1
    Reduce seconds remaining by 1
    Print out the remaining time in the form M:S
    if time has expired: return 1
    otherwise: return 0
    

    Hint

    For a nicer printout, you can force printf() to print a minimum number of digits for a number, padded with either spaces or zeros on the left. For instance, the format code %02u means: print an unsigned integer number with at least two digits, padded with zeros on left. This could be used to produce the format MM:SS Further, you can avoid making a new line for each print by omitting the \n in the print. Using just \r will cause the cursor to return to the beginning of the current line and overwrite what is currently there.

  8. Call uint8_t UpdateCountdown() at the appropriate location in your code (when one second has passed).

  9. Check the returned value from the function after the call. If it returned 1, the timer has completed and the timer should be stopped using void Timer_A_stopTimer( uint32_t timer ). Also add a print statement to denote that the timer has completed.

  10. Set an initial value for minutes and seconds to count down from.

  11. Run and test the code. The terminal should show the countdown as programmed. Use a stop watch or another timer (e.g., on your phone) to ensure that the timer is decrementing at the right speed. The timer should also stop at 0:0 (or 00:00).

  12. If all is good, submit your code and two required screenshots to the corresponding Gradescope assignment.