11. Timer Capture: Motor Encoders
Important
This activity may be completed in groups of 2 students as an RSLK is required.
11.1. Purpose
This activity introduces the implementation and Encoders to measure wheel speed and drive distance via Timer Capture functionality.
11.2. Hardware and Tools
TI-RSLK Robotic Car
11.3. Description
PWM speed control of the motors on the RSLK works well but the control is not accurate with respect to absolute speed. This causes there to be a slight speed difference between the left and right motors on the car as they are independent drives; causing the cars to turn or drive along an arc when motors are receiving equivalent pulse widths. Therefore, it is useful to have a measure of the actual speed of the motors in order to adjust the PWM as necessary to achieve the desired speed. This functionality will not be accomplished in this activity but will instead be completed in Laboratory 3.
This activity guides through implementing a timer to Capture the motor speed and distanced traveled; which is indicated by an attached magnetic encoder. A Hall effect sensor produces a digital signal corresponding to the rotation of the magnetic encoder. The RSLK has two Hall effect sensors per encoder/wheel, providing two digital outputs per side:
The two sensor locations are noted as the green and yellow marks as shown in the animation above. The outputs of said sensors are provided in the plot on the right as the wheel rotates. The rotating red/blue disk is the magnetic encoder rotating with the DC motor. It is clear that the encoder is rotating much faster than the wheel: the motor/encoder are connected to the wheel through an effective 360:1 gear train. For purposes of demonstration, the video above shows a gear train of 10:1.
Only one hall effect sensor is required to measure the wheel speed; two are included to support detection of spin direction. Measurement of direction is not required in this activity.
Measurement of speed can be accomplished by tracking the timing between the rising and/or falling edges of the sensor output. This, of course, can be supported by a Timer_A instance; this time using the Capture Mode.
11.4. Timer_A Capture Description
The Timer_A Capture functionality behaves akin to the Compare Mode (used to generate PWM) but in reverse: instead of producing external events when the timer reaches a certain value, the Capture mode uses an external event to cause the CCRn to save the current timer value. The external signal producing said events is named the CCInA for Capture Compare Input n Line A. This functionality is described in the figure below.

Timer_A capture operation in continuous mode, triggering on rising edge of CCInA.
We will use Timer_A in continuous mode when using the encoders. This provides for the largest sample period range, allowing for fewer timer resets during each measurement (more efficient). Continuous mode operates exactly the same as Up Mode; however, the timer only resets due to integer overflow after counting past its maximum value of 65535. This frees CCR0 to be use used in capture mode as opposed to it being used to control the timer period.
When an external event occurs on CCInA, triggering a timer compare, the current count value is stored within the CCRn attached to the external signal. Both rising and falling edges may be used to trigger the compare event. These events may be directly used to calculate distance traveled for a wheel: each event indicates a rotation of 1/360 of the wheel circumference since the last event (rising-to-rising or falling-to-falling); therefore, counting the events effectively serves as a distance measurement. Likewise, the speed of the rotation may be calculated through the time difference between the events.
Measuring the time difference between two capture events that happen within the same timer period is straight forward: subtract the difference between the captured timer counts and calculate the time knowing the timer clock:
where \(N_\mathrm{cap}(k)\) is the timer capture value for sample \(k\), and \(f_\mathrm{TCLK}\) is the timer clock.
This becomes slightly more difficult when the events span across timer reset periods. For example, if the timer overflows once between capture events, then the calculation becomes:
where \(65536-N_\mathrm{cap}(k-1)\) is the number of counts between the previous capture event and the overflow event.
This math can be extended to multiple overflows between capture events by simply adding \(65536\) for each overflow that has occurred, leading to:
where \(N_\mathrm{overflows}(k)\) is the number of overflows that have occurred between two capture events.
11.5. Timer_A Capture Implementation
As noted above, the Timer_A instance should be set up to operate in Continuous Mode. Again, this mode is effectively the same as Up Mode except that the timer will always count to 65535 prior to overflow instead of to the value stored within CCR0. This requires using the initialization struct Timer_A_ContinuousModeConfig
instead of Timer_A_UpModeConfig
. Note that these structs only differ in that the continuous mode version does not have a .timerPeriod
field. To match this change, when starting the timer using startCounter()
, the timerMode
argument must be TIMER_A_CONTINUOUS_MODE
.
With the Timer_A instance configured for continuous mode, the CCRns may be configured to support Capture Mode. This is done by creating a Timer_A_CaptureModeConfig
struct and populating the fields appropriately. Make sure to read the description for each field as provided in the linked documentation: some fields should always take the same value. Some additional notes:
A capture event must be acted upon in a timely manner. Therefore, it is necessary to enable the associated interrupt for each CCRn used in capture mode.
Likewise, the number of timer resets/overflows must be tracked as well. This requires that the overflow (TAIE) interrupt be enabled as well.
It does not matter which encoder edge is selected to trigger the capture event; however, both edges should not be used together (at least for this course).
Within the Timer_A interrupt(s), several things must be tracked:
The number of timer overflows since the last compare event: this is necessary to support calculation of the time between successive compare events,
The timer value of the last compare event: this is also required to calculate the time between successive compare events,
The total number of compare events: to track total rotation or distance.
Using these quantities, the time difference between successive compare events may be calculated from this equation.
Note
As you will see by the instructions below, these three points are incorperated, but the actual implementation does not use independent variables for the first two points; it is condensed into one counter variable.
11.6. Instructions
The following instructions will guide you towards configuring the RSLK to be used as a crude “tape measure”, where the RSLK is manually pushed over a distance to measure. Only one of the RSLK motor encoders will be used.
Import a new
Template Project
and name it appropriately.Create a
GPIOInit()
function and initialize one of the pins P10.4 (right wheel encoder) or P10.5 (left wheel encoder) to the appropriate Alternative Function. Take note of the required Timer_A instance number and CCRn register required for the pin selected. The pin must be configured as an Input. Note that the syntax TAm.CCInA is used in the alternate functions table to denote the CCRn input of Timer_A instance m.Create a
timerInit()
function and initialize the required Timer_A instance to Continuous Mode:The timer should be using a divider of 1.
The interrupt must be enabled.
Still within
timerInit()
: configure the required CCRn for capture mode using the struct typeTimer_A_CaptureModeConfig
.The output mode field (
.captureOutputMode
) is not used, so set to0
or any other of the defined values.All other fields are needed and must be specified.
The interrupt must be enabled.
Register both interrupts (
TIMER_A_CCRX_AND_OVERFLOW_INTERRUPT
andTIMER_A_CCR0_INTERRUPT
) to trigger avoid encoderISR()
function.Note
Remember that each Timer_A module has two interrupts. One is triggered by a reset or overflow and a capture/compare event from CCR1-CCR4. The other is only triggered by CCR0. If using CCR1-CCR4 only one
Timer_A_registerInterrupt()
call is required. Two calls are required if using CCR0. While the two interrupts are inherently different, they can be configured to trigger the same function.Finally, start the timer towards the end of
timerInit()
.Create four global variables, one each to:
uint32_t enc_total
Keep track of total encoder events, useful in measuring distance traveled.int32_t enc_counts_track
Keep track the timer counts since the capture event (must be signed!).int32_t enc_counts
Final value of timer counts between capture events.uint8_t enc_flag
A flag to denote that a capture event has occurred.
Create the
Encoder_ISR()
function. Within this function, implement the following pseudocode:Check to see if timer reset interrupt has occurred. If yes: Clear the reset interrupt flag Add 65536 to enc_counts_track Check to see if a capture event ocurred. If yes: Clear the capture event interrupt flag Increment enc_total (total number of capture events that have occurred) Calculate enc_counts as enc_counts_track + capture value Set enc_counts_track to -(capture value) Set enc_flag to 1
Note
Comments on the pseudocode above:
Use
Timer_A_getEnabledInterruptStatus()
to check the overflow/reset status.Use
Timer_A_getCaptureCompareEnabledInterruptStatus()
to check the CCR status.The math for
enc_counts
andenc_counts_track
is taken directly from the numerator of this equation. The equation is built piece-by-piece:enc_counts_track
is set equal to \(-N_\mathrm{cap}(k-1)\) (second to last line of pseudocode).Each overflow is added into
enc_counts_track
one-by-one (\(65536 N_\mathrm{overflows}(k)\))The most recent count value, \(N_\mathrm{cap}(k)\), is combined with
enc_counts_track
to produceenc_counts
.
Refer to Timer Interrupt Handling for the functions to check and set the interrupt flags.
The capture value may be obtained using the function
uint16_t Timer_A_getCaptureCompareCount( uint32_t timer , uint16_t compareRegister )
Just prior to the
main()
functionwhile(1)
, add the print statement:printf("\r\nDistance\tEnc_Counts\tDelta T\t\tAng.Speed\tLin.Speed\r\n"); // Column headers
Within the
main()
functionwhile(1)
, add the segment of code:if(enc_flag){ // Check to see if capture occurred enc_flag = 0; // reset capture flag uint16_t distance = ... ; // Calculate distance travelled in mm float delta_t = ... ; // Calculate the time between previous and current event uint16_t speed_rpm = ... ; // Calculate the instantaneous wheel speed in rpm uint16_t speed_mm = ... ; // Calculate the instantaneous car speed in mm/s printf("%5u mm\t%6u\t\t%.4f s\t%5u rpm\t%5u mm/s\r\n",distance,enc_counts,delta_t,speed_rpm,speed_mm); }
Finish the calculation for
delta_t
variable, which is the time measured between the previous and current encoder events. Note that the numerator for this equation was generated withinencoderISR()
asenc_counts
.Finish the calculation for the
distance
variable. Each compare event corresponds to 1/360 of a wheel rotation. You must measure the wheel diameter or circumference.\[c = 2 \pi r\]Finish the calculation for the
speed_rpm
variable. This is an instantaneous value that should be calculated from just theenc_counts
variable, knowing there are 1/360 compare events for each full rotation. rpm is revolutions per minute.\[\omega\left[\mathrm{rpm}\right]= \frac{1}{360}\frac{1}{\Delta t}\frac{60~\mathrm{s}}{1~\mathrm{min}}\]Finish the calculation for the
speed_mm
variable. This is an instantaneous value in mm/s that should be calculated from theenc_counts
variable and the wheel dimensions.\[v = \omega\left[\mathrm{rad/s}\right] r = \frac{2 \pi}{360}\frac{1}{\Delta t} r\]Test the code by pushing the car GENTLY across the table surface. Be careful to not touch the encoders while pushing the car as it may break the gear train or cause the clutch to slip. Verify the terminal output of distance by measuring the distance traveled.
Take a screenshot of the terminal output after the RSLK has been pushed some distance. Submit this screenshot and the final code to the corresponding Gradescope assignment.