C++ Robot Lab 2 – Motor Control and Fast Pulse Width Modulation
Table of Contents
Introduction
This lab is designed to introduce you to how pulse width modulation (PWM) is used to control the speed of an electric motor, how the timers of the Atmega32U4 can be configured for fast PWM mode to produce a desired PWM signal, and how to apply that to your line following algorithm.
What is New?
C++ Flow Control
To the C++ terminology and concepts learned in Lab 1; Lab 2 adds flow control using if-else statements. Before you begin I would recommend watching Lynda’s Fun with C++, part of the Learning C++ lecture series.
if (condition) { statement block } else { statement block }
Arduino Built-In Functions
To the terminology and concepts learned in Lab 1; Lab 2 adds the following Arduino Built-in function. If you have any questions on this information, refer back to my lecture on Pulse Width Modulation and Lynda’s lecture on Arduino Pulse Width Modulation. If you decide to do the first of the recommended “Power Tips” you should also read up on Arduino Serial Communications.
analogWrite(pin_number, PWM-value); // Define timer pin and output PWM digital waveform. Serial.println(val, format); Serial.read();
Improving Control of the Motors
Up to this point, you have been simply turning the motors ON and OFF. This is not the best solution for following a path and results in the robot ping-ponging from one side of the path to the other or more likely driving off the path entirely. In this lab, our main goal is to get better control of our robot by applying what we learned in the Pulse Width Modulation lecture.
Using the Timers in Fast PWM Mode
As you learned in the lecture, the timers can be configured to generate the desired PWM values and output them on specific pins. Those pins are indicated in Figure 1. If you plan to use this feature for the timers, you must use one of the five available PWM pins. You will notice that we are already using PD7 and PB6 (Arduino pins D6 and D10) for our PWM 3DoT v10 Block Diagram control signals for the motor driver.
Errata: AIN1, AIN2/PWMA, BIN1, PIN2/PWMB, NSLEEP wired to 5V_VM
The Timer with PWM lecture goes into detail about how this entire system works and you should understand that before we move forward. For the actual implementation, you can use the built-in function of analogWrite(). All of the configurations that were described in the lecture are handled for you in the analogWrite() function and the specific limitations are described here.
takeAStep
Open Lab1 in the Arduino IDE. Save As Lab 2. At the end of loop(), create a new function named takeAStep(). Move your path follower code into this new function. To the now-empty loop() function add a call to takeAStep(). Your program should now look something like this.
void loop() { takeAStep(); } void takeAStep() { // path follower code from Lab 1 }
At the present time, your robot can follow a path. You may have also discovered that it also slows down at an intersection. This follows from your algorithm which stops a motor when its associated sensor is over a line. At an intersection, both sensors at some point will detect a line and therefore stop their respective motors momentarily, with the other motor taking the robot over the line.
If you have not done so already, verify that your robot slows down at an intersection.
Find the Maximum Speed
There is a high probability that your two motors are not equal. This is typically due to a difference in the internal friction of the gears and motors, which translates into a difference in the speed of rotation at a given voltage or duty cycle. This will cause your robot to veer towards the right or left. In order to address this issue, we will start by finding the ideal maximum speed for the fastest motor such that the robot travels in a straight line (or at least as close as possible).
This portion of the lab involves a lot of trial and error. With a pencil and yardstick, make a light straight line on a white surface that is about three feet long (i.e., the back of your maze). You will be manually controlling the speed of each motor using the analogWrite() function.
Temporarily comment out the call to takeAStep() and add analogWrite() functions to control the motors.
Begin with both motors running at full speed (PWM value of 255) and record in which direction the robot drifts from the path. If it drifts to the right, then the left motor is faster than the right. Decrease the speed of the faster motor by lowering the duty cycle (PWM) of the faster motor. Repeat this process until the motors are in sync. Define the maximum speed for both motors as a constant integer in the 3DoTConfig.h file.
const uint8_t motorRightHIGH = ____; // Synchronize motors max const uint8_t motorLeftHIGH = ____;
Power Tips
If you want to save time you can also write a bit of code and simply use the Serial.read() and Serial.println() functions to find the maximum PWM value for the faster motor. As an alternative, you can use a potentiometer with the wiper (middle terminal) connected to analog pin A4 wired to connector pin 6 on 3DoT Header J5 and the outside leads to 3.3V and ground (GND) wired to connector pins 3 and 4 on 3DoT Header J2, you can find the maximum and minimum speeds quickly by adapting the example script provided for the analogWrite() instruction.
Find the Minimum Speed
The objective of this section is to find the minimum speed that will keep the motors synchronized and provide a safe margin above the stall speed for both. A motor will stall when there is insufficient power to the motor to overcome the internal friction of the gears and motor (i.e., nothing is moving). The motor is ON at this point and it is consuming power but there is no physical motion. This may damage the motor and should be avoided. By obtaining the minimum speed to get the motor moving, we can keep the motor ON and barely moving while the robot smoothly gets back on track.
Using trial-and-error determine the minimum speed for each motor. Specifically, slowly decrease the duty cycle (PWM value) until the minimum speed is reached with a margin of safety. If you want to save time you can again write a bit of code and simply use the Serial.read() and Serial.println() functions to find this minimum safe PWM value or use a potentiometer as described in the previous section.
Next, run both motors at their minimum speed and note how the robot deviates from the line. Increase the speed of the slower motor. Repeat this process until the motors are in sync. Define the minimum speed for both motors as a constant integers in the 3DoTConfig.h file.
const uint8_t motorRightMin = ____; // Synchronize motors min const uint8_t motorLeftMin = ____;
We will be experimentally determining the motorRightLOW and motorLeftLOW constants in the next section.
A More Majestic Step
Now that you have identified all of the values that are needed to synchronize the speed of the motors, you can update the path following the algorithm to have the robot follow a path with fewer corrections. In a perfect world, the robot should move forward and never veer off a straight line with this change. We do not live in a perfect world.
Delete the analogWrite() functions and restore the call to takeAStep() in the main loop.
void loop() { takeAStep(); }
/* Write to the motors */
In takeAStep(), replace the digitalWrite(PWMA, motorLeft); and digitalWrite(PWMB, motorRight); functions with the corresponding analogWrite() calls.
/* Write to the motors */
analogWrite(PWMA, motorLeft);
analogWrite(PWMB, motorRight);
Integer variables motorLeft and motorRight, which in lab 1 were assigned values of simple LOW (i.e., 0) and HIGH (i.e., 1) will now contain PWM values between 0 and 255.
/* Run the path following algorithm */
Replace the two (2) assignment statements in the “Run the path following algorithm” block with if-else conditional expressions setting each motor’s max (motorLeftHIGH, motorRightHIGH) and min (motorLeftLOW, motorRightLOW) PWM value as a function of its’ corresponding sensor input value (sensorLeft, sensorRight).
/* Run the path following algorithm */
if (condition) {
// Statement(s) to execute if condition is true
} else {
// Statement(s) to execute if condition is false
}
:
Binary Search Algorithm
In lab 1 we simply changed direction by turning a motor off. In this section, we want to experimentally determine the speed to set the motor that was previously turned off to a value that will make the most gentle correction possible. If the value is too low then the robot will continue to ping-pong as it moves along the line; too high and the robot could not correct its’ course in time and may leave the path. Once experimentally determined these values will be assigned to integer constants motorLeftLOW and motorRightLOW in the 3DoTConfig.h file. These constants will have values between their HIGH and MIN values. The fastest way to experimentally determine the optimal speed is to use a binary search algorithm. Start by setting the motor LOW constants to a value between their HIGH and LOW values.
guess low = (max + min)/2
const int motorRightHIGH = ____; // Synchronize motors max const int motorLeftHIGH = ____; const int motorRightMin = ____; // Synchronize motors max const int motorLeftMin = ____; const int motorRightLOW = ____; const int motorLeftLOW = ____;
Upload and run the line following code. If the robot leaves the path then you need to decrease the LOW value. Guess a value between your last guess and the minimum value. If the robot stays on the path then you need to increase the LOW value. Guess a value between your last guess and the maximum value. Repeat these steps until you find the speed that results in the most gentle correction possible without leaving the path. If you want to save even more time you can again write a bit of code and simply use the Serial.read() and Serial.println() functions or use a potentiometer as described earlier.
Design Challenge – pwmWrite
You can skip the remainder of this lab if you are happy receiving a passing or even a good grade on the lab. If you want to receive an excellent grade you will need to accept the challenge(s). Specifically, the maximum grade you can receive on lab 2 if you do not accept the challenge(s) is 20 points out of 25 (80%).
The Arduino IDE is designed for non-engineers and therefore, as much as possible, it hides what is going on within the MCU. As engineers, we would like to understand how the MCU really works and for this lab how the registers within the Timer 4 peripheral subsystems can be programmed to replace the analogWrite() functions.
Open the 3DoTConfig.ino tab and add the following function definition.
void initTimer4(){ // add code to initialize timer 4 here. };
Add a call to the initTimer4() function to the init3DoT() function.
void init3DoT() { // initialization code from lab 1. initTimer4(); }
Write a function named pwmWrite(motor, value) to replace your analogWrite(pin, value) calls. Place this function after the takeAStep() function definition.
Lab 2 Deliverable(s)
This lab will be turned-in with Lab 3. |