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.
Field |
type |
Possible Values |
|---|---|---|
|
|
|
|
|
|
|
|
where |
|
|
|
|
|
|
- Field Descriptions:
.modeset the counting mode of timer. Each timer is capable of counting inDOWNmode,UPmode, andUP_DOWNmode. 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..clksrcindicates what the timer module should use as its counting base. For the purposes of this course, we will typically selectBUSCLK, which is set to be 32 MHz..clkdivratioallows for the selected.clksrcto be “divided” in frequency. For example, if this field was set toTIMER_CLOCK_DIVIDE_4, the timer would would increment at a rate of 32 MHz / 4 = 8 MHz..clkprescaleallows 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 typeuint8_tand can take any value in that range..periodspecifies the maximum number that the module will count to before resetting inUPorUP_DOWNmodes. InDOWNmode, this specifies the number at which to start counting. This number is of typeuint16_tand can take any value in that range. Note thatTIMG12is 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; withTIMG12being 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.
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 Tresetis incremented. AssumingTresetstarted 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}\)?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\).
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.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.
Considering one of the MSPM0G3507 16-bit TIMG timers, what is the maximum possible timer reset period if using
TIMER_CLOCK_BUSCLKas the input source, valued at 48 MHz?
7.4.2. Part 2
First, download and import the course template project:
TemplateProject.zip. You should rename the imported project to something like “Activity-Timer”.Create four global variables:
One
Timers_TimerConfigtype struct variable, which will be used to configure the timer. Note that the variable type definition forTimersTimerConfigalready exists within the HAL and does not need to be replicated!Three
uint8_tvariables: one to track minutes, another to track seconds, and a third to track the number of timer resets.
Note
Although the
Timers_TimerConfigstruct variable is only used inTimers_initTimer()(next step), the variable should be a global variable due to how it is passed intoTimer_A_configureUpMode(); that is, as a pointer. In some instances, passing local variables as pointers (by reference) will not work properly.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 usingTimers_initTimer()after specifying values to theTimers_TimerConfigstruct.Warning
The effective clock divider value for a TIMG module is the combination of the values specified in the
.clkdivratioand.clkprescalefields. Be aware, however, that the divider contribution to the total clock division is.clkprescale+1such that the effective divider value is(.clkdivratio)*(.clkprescale+1).Hint
While these instructions are asking for a period of 10 ms, specifying the
.periodfield 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.Use
Timers_startTimer()to start the timer within theTimerInit()function. Not that this function must occur after the timer is fully configured.Add the
TimerInit()function to the appropriate location in themain()function.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 themain()function.Within the
main()functionwhile(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 theuint32_t Timers_getAllPendingInterrupts()andTimers_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 useuint32_t Timers_getAllPendingInterrupts().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()orputchar()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; }
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.
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.
Set the clock division back to 40.
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 0Hint
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%02umeans: 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\nin the print. Using\rinstead will cause the cursor to return to the beginning of the current line and overwrite what is currently there.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).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.Set an initial value for minutes and seconds to count down from.
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(or00:00).Submit your code and the two required screenshots/photos to the corresponding Gradescope assignment.