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 OR classroom provided benchtop oscilloscope, multimeter, or frequency counter.

  • LP-MSPM0G3507 Launchpad Development Board, OR RPI-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 MSPM0G3507 TIMG modules in this activity to build a basic “countdown timer”, which will start at a predefined number of seconds/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 TIMG module to do this, we will use the EmCon HAL as introduced in Activity 5 instead of using registers.

The EmCon HAL is more complicated for the TIMG module as compared to the GPIO (and the other timers, for that matter). There are many different modes the timer can operate in, several possible selections for the input clock, two configuration values for the input clock divider, etc. For this activity, we will operate in Periodic Up Mode, where the timer counts from 0 to a specified value, resets back to 0, then continues counting; or Periodic Down Mode, which does the same except it counts down from a specific value to 0.

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

Important

Timer Selection: The first argument for all EmCon HAL timer functions (functions applicable to TIMG and TIMA) is the timer module to configure and/or use: GPTIMER_Regs *timer.

The MSPM0G3507 has 6 TIMG modules and 2 TIMA modules; and can be selected using the predefined values, all of type GPTIMER_Regs *: TIMG0, TIMG6, TIMG7, TIMG8, TIMG12, TIMA0, and TIMA1.

7.3.1. Timer Configuration

See also

The description provided below is mostly copied from Timer Base Configuration.

Configuration of the timer’s counting behavior requires the creation of a Timers_TimerConfig type struct. The table below shows the fields for this configuration struct.

Timers_TimerConfig struct fields

Field

type

Possible Values

.mode

TIMER_MODE (enum)

TIMER_MODE_ONE_SHOT_DOWN

TIMER_MODE_PERIODIC_DOWN

TIMER_MODE_ONE_SHOT_UP

TIMER_MODE_PERIODIC_UP

TIMER_MODE_ONE_SHOT_DOWN

TIMER_MODE_PERIODIC_UP_DOWN

.clksrc

TIMER_CLOCK_SRC (enum)

TIMER_CLOCK_BUSCLK

TIMER_CLOCK_2X_BUSCLK

TIMER_CLOCK_MFCLK

TIMER_CLOCK_LFCLK

TIMER_CLOCK_DISABLE

.clkdivratio

TIMER_CLOCK_DIV (enum)

TIMER_CLOCK_DIVIDE_x

where x may be 1-8

.clkprescale

uint8_t

0 to 255

.period

uint16_t

0 to 65535

Field Descriptions:
  • .mode set the counting mode of timer. Each timer is capable of counting in DOWN mode, UP mode, and UP_DOWN mode. Additionally, for each of these modes, the timer can be set to count only once and then stop, using _ONE_SHOT_, or count continuously, using _PERIODIC_. The _ONE_SHOT_ option is not used in this class.

  • .clksrc indicates what the timer module should use as its counting base. For the purposes of this course, we will typically select BUSCLK, which is set to be 32 MHz.

  • .clkdivratio allows for the selected .clksrc to be “divided” in frequency. For example, if this field was set to TIMER_CLOCK_DIVIDE_4, the timer would would increment at a rate of 32 MHz / 4 = 8 MHz.

  • .clkprescale allows for the divided clock (determined from .clkdivratio) to be further divided. The effective divider used for this instance is the value of the field +1. This number is of type uint8_t and can take any value in that range.

  • .period specifies the maximum number that the module will count to before resetting in UP or UP_DOWN modes. In DOWN mode, this specifies the number at which to start counting. This number is of type uint16_t and can take any value in that range. Note that TIMG12 is capable of counting over a 32-bit range as opposed to 16-bit range. This range is not supported for the current version of the HAL; with TIMG12 being artificially limited to a 16-bit range.

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

void Timers_initTimer( GPTIMER_Regs *timer , Timers_TimerConfig *config )

The first argument to this function, GPTIMER_Regs *timer, is the timer module to use (see above). The second argument is the configuration struct as described above. Note that the * within the argument definitions indicates that the arguments must be pass as pointers, which is simply done by adding a & before the struct variable name to be passed; however, this is not required for the TIMGx and TIMAx values as they are already defined as pointers.

An example usage of this function is provided below, where the required configuration struct (of type Timers_TimerConfig) is created and (partially) populated. The configuration is then applied by the function. The configuration struct must be specified prior to calling Timer_A_initTimer; changing the values of the example tmr_cfg after the function has been called will have no effect.

Timers_TimerConfig tmr_cfg;    // Initialize timer configuration struct
tmr_cfg.mode = TIMER_MODE_PERIODIC_DOWN; // Set mode to down counting and repeating
tmr_cfg.clksrc = ...;
... // Configure the rest of the fields within the configuration struct
Timers_initTImer(TIMG0,&tmr_cfg); // Apply the configuration
... // Start the timer

7.3.2. Timer Run Control

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

void Timers_startTimer( GPTIMER_Regs *timer )

Likewise, the counter may be paused using

void Timers_stopTimer( GPTIMER_Regs *timer )

The timer counter may also be reset to 0 via

void Timers_clearCounter( GPTIMER_Regs *timer )

The period of the timer may be changed using

void Timers_setPeriod( GPTIMER_Regs *timer , uint16_t period )

7.3.3. Timer Status

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

uint16_t Timers_getCounter( GPTIMER_Regs *timer )

This function will return the value of the current timer count, where the timer module is specified by timer.

Resets of the timer may also be detected by checking for the Zero or Load events. This may be done through the function

uint32_t Timers_getAllPendingInterrupts( GPTIMER_Regs *timer )

Which returns the event/interrupt flag values for all of the events/interrupts for the requested timer. To check for a single event, the output of the function must be bitmasked with the appropriate value; either TIMER_INTSRC_ZERO for the Zero event or TIMER_INTSRC_LOAD for the Load event. For example:

if(Timers_getAllPendingInterrupts(TIMG0) & TIMER_INTSRC_ZERO){

will check to see if the Zero event had occurred.

To clear an event flag (set back to 0), use the function:

  • void Timers_clearInterrupt( GPTIMER_Regs *timer , uint32_t interruptMask )

where uint32_t interruptMask is the desired event to clear, in this case, either TIMER_INTSRC_ZERO or TIMER_INTSRC_LOAD again.

7.4. Instructions

This homework is two parts. Part 1 is a set or problems and part 2 is hands-on work. These may be done in any order.

7.4.1. Part 1

Solve each question given below and submit your work and your answer. Note: answers without any supporting or sufficient work may be given a 0.

  1. An 8-bit timer is configured to count to its maximum possible value, producing a reset period of 50 ms. After each timer reset, variable uint16_t Treset is incremented. Assuming Treset started at 0 and its current value is 32, approximately how much time has passed? Further, what must be the frequency of the timer clock, \(f_\mathrm{TIMCLK}\)?

  2. In the previous problem, the word “approximately” was used as the correct answer for elapsed time will likely not be exact. What are the error bounds on your answer; that is, what is the maximum possible error (both negative and positive) between the actual time and calculated value? Use \(error = actual-calculated\).

  3. A 32-bit timer is configured in down-counting one-shot mode to accurately measure the time between two events. The timer is sourced by a system clock of 216 MHz and configured with a divider of 2. The first event occurs, triggering the timer to start counting from its max value. The second event causes the timer to stop, in this instance at a value of 237980995. How much time elapsed between the two events? Provide your answer to 4 significant digits.

  4. For the previous problem, what is the maximum error expected from this measurement, specifically due to the timer? This ignores other causes of error, for example: latency in starting the timer once the first event occurs.

  5. Considering one of the MSPM0G3507 16-bit TIMG timers, what is the maximum possible timer reset period if using TIMER_CLOCK_BUSCLK as the input source, valued at 48 MHz?

7.4.2. Part 2

  1. First, download and import the course template project: TemplateProject.zip. You should rename the imported project to something like “Activity-Timer”.

  2. Create four global variables:

    1. One Timers_TimerConfig type struct variable, which will be used to configure the timer. Note that the variable type definition for TimersTimerConfig already exists within the HAL 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 resets.

    Note

    Although the Timers_TimerConfig struct variable is only used in Timers_initTimer() (next step), the variable should be a global variable 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.

  3. Add a TimerInit() function. Within this function, configure TIMG8 to count either in Up or Down mode (but not Up-Down mode) using BUSCLK and an effective divider of 40. Further, set the period such that the timer will elapse once every 10 ms. Do not forget to apply the struct field configuration to the timer by using Timers_initTimer() after specifying values to the Timers_TimerConfig struct.

    Warning

    The effective clock divider value for a TIMG module is the combination of the values specified in the .clkdivratio and .clkprescale fields. Be aware, however, that the divider contribution to the total clock division is .clkprescale+1 such that the effective divider value is (.clkdivratio)*(.clkprescale+1).

    Hint

    While these instructions are asking for a period of 10 ms, specifying the .period 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.

  4. Use Timers_startTimer() to start the timer within the TimerInit() function. Not that this function must occur after the timer is fully configured.

  5. Add the TimerInit() function to the appropriate location in the main() function.

  6. Also create a GPIOInit() function to initialize PA0 (LED1) and PB13 as Outputs, with both initially low. Call this function at the appropriate location in the main() function.

  7. 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 the timer a reset (e.g., zero event) has occurred, and then clearing the flag that signaled that the reset occurred. This may be done using the uint32_t Timers_getAllPendingInterrupts() and Timers_clearInterrupt() functions, respectively.

    Warning

    Do not use uint32_t Timers_getPendingInterrupts() here, as this function only returns information for enabled interrupt sources; and no interrupt sources are enabled in these instructions. You must use uint32_t Timers_getAllPendingInterrupts().

  8. Add code to toggle PB13 each time the timer completes a period. Additionally, toggle PA0 (LED1) such that it is on for 1 second and off for 1 second. Template code is provided below to aide in implementing this. A short printf() or putchar() may also be useful for debugging.

    // Toggle PB13
    counter++; // timer reset counting variable
    if(counter == 123){ // change number here corresponding to 1 s
        // Toggle LED1 (P1.0)
        counter = 0;
    }
    
  9. Run the code and verify that pin PB13 toggles at a rate of 10 ms using your USB Test Device or the equipment in the classroom (oscilloscopes, benchtop multimeters, or frequency counters). Note that since PB13 is intended to toggle every 10 ms, the period of the resulting square wave on this pin 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. Once working properly, take a screenshot of the USB Test Device screen with the period measurement shown OR a picture of the benchtop equipment verifying the period.

    1. Change the clock division to a different value greater than 40 and measure the period again. Does the change make sense with respect to equations given in the lecture? Take another screenshot or picture. The USB Test Device or benchtop instrument is no longer needed after this step.

    2. Set the clock division back to 40.

  10. Create a function uint8_t UpdateCountdown(), which will be called each time one second has passed (same timing as LED1 toggle). 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 \r instead will cause the cursor to return to the beginning of the current line and overwrite what is currently there.

  11. Add a call to uint8_t UpdateCountdown() in the main function such that it is called once every second (an additional if statement may be necessary).

  12. Check the returned value from the function after the call. If it returned 1, the countdown timer has completed and should be stopped using void Timers_stopTimer(). Add a print statement to denote that the countdown has completed.

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

  14. 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).

  15. Submit your code and the two required screenshots/photos to the corresponding Gradescope assignment.