10. Timer Compare: Motor PWM
Important
This activity may be completed in groups of 2 students as an RSLK is required.
10.1. Purpose
This activity introduces the implementation and use of Timer Compare modules and their use in generating Pulse Width Modulation (PWM) to control wheel speed.
10.2. Hardware and Tools
TI-RSLK Robotic Car
10.3. Description
This portion of the activity will serve as a guide to implement Pulse Width Modulation, or PWM, in order to control the RSLK drive wheels’ speeds. The motors on the car are connected through an H-bridge chip (DRV8838) such that no hardware needs to be built for this application. The connections on the RSLK are as shown in the figure below.
Wiring of motors on RSLK
The pins for Enable and Direction were of course initialized for use in Laboratory 1; where fixed speed control was built into the template project. The speed control for this activity and future laboratories must be implemented using two PWM outputs generated from a Timer_A module, one for each wheel. While the DRV8838 and motors will work using a huge range of frequencies, for the purposes of this class a PWM period of 24 kHz should be used.
10.3.1. PWM Generation
Implementation of PWM is fairly straight forward as long as it is clear how to configure and use a timer in order to track time. PWM is generated by using a timer’s count to trigger an output to change value when a specific count value is reached. This additional functionality is supported by a Timer_A module’s Capture Compare Register, or CCRn; configured to operating in Compare Mode. A CCRn is capable of directly driving a linked GPIO pin, as opposed to requiring code to explicitly change the pin value, as described in the figure below. In the below figure, the Timer_A module is configured to operate in Up Mode, where the value for Nperiod, or CCR0 (Capture Compare Register 0), is specified by the Up Mode configuration’s .timerPeriod
field.

Timer_A OUTn PWM behavior
As shown, CCR0 is used to control the timer period (true for Up Mode and Up-Down Mode) and does not need to be explicitly configured to do so. A second CCRn is then configured in the Compare Mode (using the struct type Timer_A_CompareModeConfig
) to generate the PWM output, denoted as the OUTn signal. The figure shows two different variations on the generated PWM: RESET_SET and SET_RESET, where one is the inverse of the other. These would be selected with the .compareOutputMode
field values of either TIMER_A_OUTPUTMODE_RESET_SET
or TIMER_A_OUTPUTMODE_SET_RESET
, respectively (among other modes).
Once configured appropriately, the PWM would be generated without intervenion; that is, the PWM signal will be continuously generated until the program instructs it to be changed or stopped. This implies that no timer interrupts are necessary to maintain said operation. To change the PWM duty cycle, a new value for the CCRn compare value must be computed and assigned to the CCRn via the function Timer_A_setCompareValue()
.
10.3.2. PWM Output Routing
As noted, the CCRn module will control an output signal OUTn. By default, this OUTn signal is not passed to a GPIO pin. This is changed by configuring the applicable GPIO pin into an Alternate Function mode (GPIO Alternate Functions). It should be noted that each GPIO pin is capable of being linked to only a few signals; therefore, it is necessary that the correct GPIO pin is selected to support the generated output signal. Conversely, if PWM must be generated on a specific pin, as is the case with the RSLK, then the appropriate Timer_A module and CCRn must be configured to generate the PWM.
The OUTn signals associated with each Timer_A module and CCRn are denoted as TAm.n in GPIO Alternate Functions; where m in TAm.n indicates the timer module and n indicates the CCRn number. For example, P5.6 is noted to support the alternate function TA2.1 within the GPIO Alternate Functions table; indicating P5.6 associates with CCR1 of Timer_A Module 2 (TIMER_A2_BASE
). Of course, as the PWM is an Output, the GPIO alternate function must be set as an output as well.
10.4. Instructions
This activity consists of two distinct parts:
LED manipulation via a timer output with frequency and duty cycle control and
Motor speed control via timer generated PWM.
10.4.1. Part 1: LED Control
Unfortunately, the two controllable LEDs the Launchpad Board (Red LED1 and RGB LED2) are not connected to a GPIO pin that have timer OUTn signal (TAm.n) support; these instructions will perform the signal manipulations manually within the timer interrupt. Part 2 will have these operations performed directly via the timer output module in controlling the motors.
Ensure the RSLK is on a foam block before powering on. This will prevent cars from driving off the benches and becoming damaged.
Import a new
Template Project
and name it appropriately.Within a
GPIOInit()
function, configure:P1.0 (LED1) to be an output
Bumpers [BMP0,BMP1,BMP4,BMP5] for use (pins [P4.0,P4.2,P4.6,P4.7]).
Within a
timerInit()
function, configure a timer for UP mode usage:Initialize Timer A2 to use ACLK clocksource (32.768 kHz) and not SMCLK, with a clock divider of 2,
Configure the timer to have an initial period of 1 second, and
Enable the associated TAIE interrupt.
Also within
timerInit()
function, configure the CCR1 module of the timer such that it is in Compare Mode:Create the initialization struct (type
Timer_A_CompareModeConfig
) and populate the fields….compareRegister
: Select CCR1..compareInterruptEnable
: Enable this to cause the CCR1 Compare Event to trigger an interrupt..compareOutputMode
: Since this setup will manually be manipulating the LED, the configuration of the module Output Mode is inconsequential. YOu may just set the associated field to0
(or any other of the defined values)..compareValue
: Set this value to be half the timer period.Apply the CCRn configuration with the function
Timer_A_initCompare()
.
Finally within
timerInit()
:Register a function
void timerISR()
(created in next step) to be triggered by both enabled interrupts. Note that theTIMER_A_CCRX_AND_OVERFLOW_INTERRUPT
is triggered by the timer overflow/reset event and all CCR1-CCR4 events.Start the timer.
Add
GPIOInit()
andtimerInit()
to themain()
function at the appropriate place.Create a function
void timerISR()
and register both enabled interrupts to trigger this function. Note that theTIMER_A_CCRX_AND_OVERFLOW_INTERRUPT
is triggered by the timer overflow/reset event and all CCR1-CCR4 events.Within
void timerISR()
, both of the enabled interrupt sources need to be checked and addressed. Do so following this pseudocode:Check if timer overflow/reset occurred. If so: Clear the associated interrupt flag Turn the LED on Check if the CCR1 event occurred. If so: Clear the associated interrupt flag Turn the LED off
Note
This control of the LED effectively configures the output as a RESET_SET type output: Reset (set low) on compare, Set (set high) on timer overflow.
Compile and test the code. If working properly, the LED should be blinking such that it is on for 0.5 s and off for 0.5 s.
Within the
main()
functionwhile(1)
loop, check to see if each of the bumpers have been pressed and adjust the LED behavior accordingly:BMP0 Pressed: Increase blink frequency by 10 % to a minimum period of 0.1 seconds
BMP1 Pressed: Decrease blink frequency by 10 % to a maximum period of 2.0 seconds
BMP4 Pressed: Increase blink duty cycle by 10 % to a maximum of 100 %
BMP5 Pressed: Decrease blink duty cycle by 10 % to a maximum of 0 %
if(!GPIO_getInputPinValue(4,GPIO_PIN0){ // BMP0 Pressed __delay_cycles(240e3); while(!GPIO_getInputPinValue(4,GPIO_PIN0)); __delay_cycles(240e3); // Add code to increase blink frequency by 10 % } // Code for BMP1 (P4.2) // BMP1 Pressed: decrease blink frequency by 10 % // Code for BMP4 (P4.6) // BMP4 Pressed: decrease blink duty cycle by 10 % // Code for BMP5 (P4.7) // BMP5 Pressed: increase blink duty cycle by 10 %The most straight-forward way to complete the above is to create two variables to store both the timer period (or frequency) in seconds (or Hz) and duty cycle (0-100) and perform math on these, then update the timer. For example, to increase the timer frequency by 10 %, the following process could be used:
period -= 0.1*period; // same as... period *= 0.9 // Enforce limits on period (if below: set to minimum, if above: set to maximum) // Math to convert "period" to CCR0 value (same as .timerPeriod calculation) Timer_A_setCompareValue(...); // Update the CCR0 value to update period // Math to convert new "period" and "dutycyle" to CCR1 value Timer_A_setCompareValue(...); // Update the CCR1 value to maintain duty cycleSimilarly, the duty cycle could be increased by 10 % like so:
dutycycle += 10; // duty cycle is stored as a percentage in this example // Math to convert "dutycycle" to CCR1 value Timer_A_setCompareValue(...); // Update the CCR1 value to maintain duty cycleNote
As shown in the example processes above, both CCR0 (controlling the timer period) and CCR1 (ultimately controlling the duty cycle) must be updated when the period changes. This is because the duty cycle is a function of the ratio between CCR0 and CCR1 and not solely by the CCR1 value, where CCR1 actually controls the LED’s “pulse width” as described above and not the duty cycle directly.
Compile and test the code. If working properly, you should be able to adjust both the LED blink frequency and duty cycle independently.
Save the code to be submitted to the Gradescope assignment.
10.4.2. Part 2: Motor Speed Control
Copy and rename the project from Part 1 OR import another
Template Project
and name it appropriately. If copying from part 1: most, if not all, code from Part 1 will be modified or removed. The LED functionality is not expected to persist in Part 2.Determine the required GPIO pins to output the PWM signal on (see motor schematic above). From this information, determine the Timer_A instance to use and the Capture Compare Register (CCRn) modules needed to generate the PWM on the appropriate pins. See here: GPIO Alternate Functions. Note that the syntax TAm.n is used in the alternate functions table to denote CCRn of Timer_A instance m.
Within a
GPIOInit()
function, configure the necessary outputs:The Direction and Enable lines for each motor must be configured as normal outputs. Set these outputs to both enable the motors and set the direction to forward.
Note
The motors will travel in the “forward” direction if the DIR pin is set to low.
The PWM outputs must use the necessary alternate function configurations as described here: GPIO Alternate Functions
All bumpers [BMP0-BMP5] for use (pins [P4.0,P4.2,P4.3,P4.5,P4.6,P4.7]).
Within a
timerInit()
function, initialize the required Timer_A instance such that it resets at the desired PWM frequency (25 kHz) using a clock divider of 1. Up Mode should be used for this class. No reset interrupt is need.Note
The period require for this timer 1/(25 kHz) is much smaller than any other period generated in the course so far. As such, it would make sense to use a much smaller divider on SMCLK (e.g., /1) to generate the timer input clock in order to allow for higher resolution PWM; that is, have more timer counts in one reset period, allowing for more flexibility and finer control of changes in setting of the PWM duty cycle.
Also within
timerInit()
: configure the applicable CCRn modules: Create the initialization struct (typeTimer_A_CompareModeConfig
), populate the fields, and apply the configuration:.compareRegister
: Select the appropriate CCRn to generate the PWM. Only one CCRn may be initialized at a time; therefore, the formTIMER_A_CAPTURECOMPARE_REGISTER_1 | TIMER_A_CAPTURECOMPARE_REGISTER_2
cannot be used here..compareInterruptEnable
: No compare interrupt is needed for PWM..compareOutputMode
: Set the output mode to match the RESET_SET functionality. While the SET_RESET mode would work, the instructions below assume RESET_SET..compareValue
: Set an initial compare value that corresponds with 0 % duty cycle PWM.Apply the CCRn configuration with the function
Timer_A_initCompare()
.Repeat steps 1-5 for additional CCRns to be configured.
Start the timer within
timerInit()
after all configurations have been applied.Add
GPIOInit()
andtimerInit()
to themain()
function at the appropriate place.Within the
main()
functionwhile(1)
loop, check to see if each of the bumpers have been pressed and adjust the motor behaviors:BMP0 Pressed: Increase the right wheel CCRn compare value by a value \(N\)
BMP1 Pressed: Decrease the right wheel CCRn compare value by a value \(N\)
BMP2 Pressed: Reverse the direction of the right wheel
BMP3 Pressed: Increase the left wheel CCRn compare value by a value \(N\)
BMP4 Pressed: Decrease the left wheel CCRn compare value by a value \(N\)
BMP5 Pressed: Reverse the direction of the left wheel
where the value for \(N\) is set such that it is one-tenth of the your calculated \(N_{period}\) (the number of counts in the timer period).
The setup for this button press logic could follow the same procedure as used in Part 1: here.
In addition to the above, ensure that whenever a compare value is being updated, ensure that its duty cycle stays within the range 10 % and 90 %:
if new compare value is > 90 % Set new compare value to be 90 % if calculated compare value is < 10 % Set new compare value to be 10 % apply compare value to CCRn with Timer_A_setCompareValue()
Test the code. If operating correctly, the user should be able to cause the left and right wheels to accelerate/decelerate and flip directions independently of the other.
Important
Remember that the car needs charged batteries and must be on for the motors to run. The USB cable only powers the MSP432 and the components directly connected to it (e.g., the LEDs). The Blue LED near the power button must be lit brightly. The car batteries must be replaced/added if the LED does not light, or is lit dimly, when the power button is pressed and motors are “running”.
If desired, the car may be unplugged from the computer and placed on the floor to test. Likely: it will be seen that the car does not travel in a straight line but instead turns with a gradual arc.
Submit both Part 1 and Part 2 codes to the Gradescope assignment.