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.

../_images/encoder_timing.png

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:

\[\Delta t (k) = \frac{N_\mathrm{cap}(k) - N_\mathrm{cap}(k-1)}{f_\mathrm{TCLK}}\]

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:

\[\Delta t (k) = \frac{\left(65536- N_\mathrm{cap}(k-1)\right) + N_\mathrm{cap}(k) }{f_\mathrm{TCLK}} = \frac{\left(N_\mathrm{cap}(k) - N_\mathrm{cap}(k-1)\right) + 65536}{f_\mathrm{TCLK}}\]

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:

\[\Delta t (k) = \frac{\left(N_\mathrm{cap}(k) - N_\mathrm{cap}(k-1)\right) + 65536 N_\mathrm{overflows}(k)}{f_\mathrm{TCLK}}\]

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.

  1. Import a new Template Project and name it appropriately.

  2. 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.

  3. 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.

  4. Still within timerInit(): configure the required CCRn for capture mode using the struct type Timer_A_CaptureModeConfig.

    • The output mode field (.captureOutputMode) is not used, so set to 0 or any other of the defined values.

    • All other fields are needed and must be specified.

    • The interrupt must be enabled.

  5. Register both interrupts (TIMER_A_CCRX_AND_OVERFLOW_INTERRUPT and TIMER_A_CCR0_INTERRUPT) to trigger a void 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.

  6. Finally, start the timer towards the end of timerInit().

  7. Create four global variables, one each to:

    1. uint32_t enc_total Keep track of total encoder events, useful in measuring distance traveled.

    2. int32_t enc_counts_track Keep track the timer counts since the capture event (must be signed!).

    3. int32_t enc_counts Final value of timer counts between capture events.

    4. uint8_t enc_flag A flag to denote that a capture event has occurred.

  8. 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 and enc_counts_track is taken directly from the numerator of this equation. The equation is built piece-by-piece:

      1. enc_counts_track is set equal to \(-N_\mathrm{cap}(k-1)\) (second to last line of pseudocode).

      2. Each overflow is added into enc_counts_track one-by-one (\(65536 N_\mathrm{overflows}(k)\))

      3. The most recent count value, \(N_\mathrm{cap}(k)\), is combined with enc_counts_track to produce enc_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 )

  9. Just prior to the main() function while(1), add the print statement:

    printf("\r\nDistance\tEnc_Counts\tDelta T\t\tAng.Speed\tLin.Speed\r\n"); // Column headers
    
  10. Within the main() function while(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);
    }
    
  11. 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 within encoderISR() as enc_counts.

  12. 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\]
  13. Finish the calculation for the speed_rpm variable. This is an instantaneous value that should be calculated from just the enc_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}}\]
  14. Finish the calculation for the speed_mm variable. This is an instantaneous value in mm/s that should be calculated from the enc_counts variable and the wheel dimensions.

    \[v = \omega\left[\mathrm{rad/s}\right] r = \frac{2 \pi}{360}\frac{1}{\Delta t} r\]
  15. 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.

  16. 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.