.. _activity_encoders: ***************************** Timer Capture: Motor Encoders ***************************** .. important:: This activity may be completed in groups of 2 students as an |rslk| is required. Purpose ******* This activity introduces the implementation and **Encoders** to measure wheel speed and drive distance via Timer **Capture** functionality. Hardware and Tools ****************** * |rslk_long| 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: .. raw:: html 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**. 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. .. figure:: ../_static/activity_10/encoder_timing.png :align: center :width: 600px 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: .. math:: \Delta t (k) = \frac{N_\mathrm{cap}(k) - N_\mathrm{cap}(k-1)}{f_\mathrm{TCLK}} where :math:`N_\mathrm{cap}(k)` is the timer capture value for sample :math:`k`, and :math:`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: .. math:: \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 :math:`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 :math:`65536` for each overflow that has occurred, leading to: .. _time_dif: .. math:: \Delta t (k) = \frac{\left(N_\mathrm{cap}(k) - N_\mathrm{cap}(k-1)\right) + 65536 N_\mathrm{overflows}(k)}{f_\mathrm{TCLK}} where :math:`N_\mathrm{overflows}(k)` is the number of overflows that have occurred between two capture events. 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 :code:`Timer_A_ContinuousModeConfig` instead of :code:`Timer_A_UpModeConfig`. Note that these structs only differ in that the continuous mode version does not have a :code:`.timerPeriod` field. To match this change, when starting the timer using :code:`startCounter()`, the :code:`timerMode` argument must be :code:`TIMER_A_CONTINUOUS_MODE`. With the **Timer_A** instance configured for continuous mode, the **CCRn**\ s may be configured to support :ref:`Capture Mode `. This is done by creating a :code:`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 :ref:`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. 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 :download:`Template Project <../_static/ccs_projects/TemplateProject.zip>` and name it appropriately. #. Create a :code:`GPIOInit()` function and initialize one of the pins P10.4 (right wheel encoder) or P10.5 (left wheel encoder) to the appropriate :ref:`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 :code:`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 :code:`timerInit()`: configure the required **CCRn** for capture mode using the struct type :code:`Timer_A_CaptureModeConfig`. * The output mode field (:code:`.captureOutputMode`) is not used, so set to :code:`0` or any other of the defined values. * All other fields are needed and must be specified. * The interrupt must be enabled. #. Register both interrupts (:code:`TIMER_A_CCRX_AND_OVERFLOW_INTERRUPT` and :code:`TIMER_A_CCR0_INTERRUPT`) to trigger a :code:`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 :code:`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 :code:`timerInit()`. #. Create four global variables, one each to: 1. :code:`uint32_t enc_total` Keep track of total encoder events, useful in measuring distance traveled. 2. :code:`int32_t enc_counts_track` Keep track the timer counts *since the capture event* (must be *signed*!). 3. :code:`int32_t enc_counts` Final value of timer counts *between capture events*. 4. :code:`uint8_t enc_flag` A flag to denote that a capture event has occurred. #. Create the :code:`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 :code:`Timer_A_getEnabledInterruptStatus()` to check the overflow/reset status. * Use :code:`Timer_A_getCaptureCompareEnabledInterruptStatus()` to check the **CCR** status. * The math for :code:`enc_counts` and :code:`enc_counts_track` is taken directly from the numerator of :ref:`this equation `. The equation is built piece-by-piece: 1. :code:`enc_counts_track` is set equal to :math:`-N_\mathrm{cap}(k-1)` (second to last line of pseudocode). 2. Each overflow is added into :code:`enc_counts_track` one-by-one (:math:`65536 N_\mathrm{overflows}(k)`) 3. The most recent count value, :math:`N_\mathrm{cap}(k)`, is combined with :code:`enc_counts_track` to produce :code:`enc_counts`. * Refer to :ref:`Timer Interrupt Handling ` for the functions to check and set the interrupt flags. * The capture value may be obtained using the function :code:`uint16_t Timer_A_getCaptureCompareCount( uint32_t timer , uint16_t compareRegister )` #. Just prior to the :code:`main()` function :code:`while(1)`, add the print statement: .. code-block:: C printf("\r\nDistance\tEnc_Counts\tDelta T\t\tAng.Speed\tLin.Speed\r\n"); // Column headers #. Within the :code:`main()` function :code:`while(1)`, add the segment of code: .. code-block:: C 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 :code:`delta_t` variable, which is the time measured between the previous and current encoder events. Note that the numerator for :ref:`this equation` was generated within :code:`encoderISR()` as :code:`enc_counts`. #. Finish the calculation for the :code:`distance` variable. Each compare event corresponds to 1/360 of a wheel rotation. You must measure the wheel diameter or circumference. .. math:: c = 2 \pi r #. Finish the calculation for the :code:`speed_rpm` variable. This is an instantaneous value that should be calculated from just the :code:`enc_counts` variable, knowing there are 1/360 compare events for each full rotation. **rpm is revolutions per minute**. .. math:: \omega\left[\mathrm{rpm}\right]= \frac{1}{360}\frac{1}{\Delta t}\frac{60~\mathrm{s}}{1~\mathrm{min}} #. Finish the calculation for the :code:`speed_mm` variable. This is an instantaneous value in **mm/s** that should be calculated from the :code:`enc_counts` variable and the wheel dimensions. .. math:: 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 :xref:`Gradescope` assignment.