Using encoders to make more accurate turns
|
Video
NOTE: One advantage of encoders that is not shown is that they are easier to program to be accurate. It took us 30 minutes of coding and tweaking to get the robot to use the timed turns in the rough and imprecise way shown above. By contrast, the encoder turns took less than 5 minutes to code and worked almost perfectly the first time. Also note that encoder turns are significantly more accurate at slower speeds (see the last clip in the video)
The Old Way
We have previously turned a certain distance by programming something like this:
motor[leftServo] = -50; motor[rightServo] = 50; wait1Msec(500); motor[leftServo] = 0; motor[rightServo] = 0; |
As you may have noticed, not only can this be very inconsistent, but the only way to tell how many degrees the robot has turned through is by a trial-and-error method, which may have to be redone when on a different surface or even at a different battery level. Using the Lego encoder, however, we can get the robot to reliably turn a specified number of degrees.
Three ways of using encoders to make turns
Now that the robot is equipped with encoders, we have a number of different methods of measuring a turn
1. We can turn the robot until one wheel's encoder has ticked far enough, and hope the other wheel has gone the same distance. If you notice that your wheels tend to go roughly the same speed (A good indication of this is how straight your robot went without the benefit of proportional control) then this might be the best option, as it is the simplest. However, many motors drive at different speeds when the same power is applied, and if your robot tends to veer in one direction or another when driving straight, this may not be the best choice as it shows one wheel outputs more speed per power.
2. We can control each wheel independently throughout the turn - that is, we separately monitor each wheel for when its encoder has passed the tick point. This will result in a robot that has turned exactly the correct angle, however if the robots have slightly different speeds, one wheel will be turning after the other has stopped, leading to a slight change in position of the robot.
3. We can use proportional control like in the previous lesson, to ensure the wheels travel at the same speed, and monitor the master encoder for when it passes the designated amount of ticks. However, this makes things much more complex than either of the other two options.
For this lesson, we are going to choose option 2 as it is best suited for our purposes, and a slight displacement of the robot at the end of each turn should not concern us too much.
The Test
Just like in the lesson where we found the ratio of encoder ticks to inches traveled, we are going to find the ratio of encoder ticks to degrees turned. To do this, we will perform an experiment of sorts. It is possible to determine this ratio with trigonometry, however our purposes are far more suited to an experimental method like this that is both easier and simpler.
The Program
The first thing we need to do is write a program which will make the robot turn through a preset number of encoder ticks. We will turn for 400 encoder ticks to get a little more than a 180 degree rotation from the wheels, which should put us near a 90 degree turn. We will also control each motor independently so that in the end we will have an accurate turn.
task main() { //Reset encoders SensorValue[leftEncoder] = 0; SensorValue[rightEncoder] = 0; //Wait so that the robot may be adjusted if plugging in the power moved it wait1Msec(5000); //Perform a point turn to the left. We will use lower power values for more accuracy. motor[leftServo] = -90; motor[rightServo] = 90; /*Since the wheels may go at slightly different speeds due to manufacturing tolerances, etc., we need to test both encoders and control both motors separately. This may result in one motor going for longer than another but it will ultimately result in a much more accurate turn.*/ while(SensorValue[rightEncoder] < 400 || SensorValue[leftEncoder] > -400) { if(SensorValue[rightEncoder] > 400) {motor[rightServo] = 0;} if(SensorValue[leftEncoder] < -400) {motor[leftServo] = 0;} } } |
The Experiment
To use this code, the first thing we need to do is place the robot on a markable surface (e.g. whiteboard, a large sheet of paper, etc.) and mark a line perpendicular to the robot, straight through its center, as seen in the below image:
Now, run the program. Be sure to use the five second's wait at the start to remove the USB cable from the robot.
Next, mark another perpendicular line in the same manner as you did before, only relative to the robot's new position.
Finally, use a protractor to measure the angle formed between these two lines, which should roughly correspond with the angle the robot turned.
The Results
Our test resulted in an angle of around 95 degrees. It is highly recommended that you perform the test a few times for accuracy purposes. Also, keep in mind that your results will probably differ from ours as your wheels may be a different distance apart, which will affect results.
Writing a function
Now that we know the angle our robot turned through with 400 encoder ticks, it will be simple to find the ratio of encoder ticks to degrees turned.
If the robot turned 95 degrees in 400 encoder ticks, then the ratio of encoder ticks to degrees turned must be
400 / 95 ≈ 4.2 encoder ticks per degree.
Since the RobotC firmware for Arduino UNO cannot handle floating-point operations, we will change that ratio to
42 encoder ticks per 10 degrees.
Since we want to be able to input 'degrees to turn' into the function, we will have to divide that 10 back out - but we will do it at the very end so that little accuracy will be lost. Thus, our formula stands as this:
Encoder ticks needed = (42 * degrees to turn) / 10.
Now, all we need to do is convert the previously used code into a function with two parameters (degrees to turn and speed) using the formula we just calculated. We will write two functions, one for turning left, one for turning right. While it would be possible to use only one function, it can get confusing and complex, and when calling the function you will have to remember the direction of rotation. Having two functions makes it much clearer, easier, and more useful, even though there might be a few more lines of code.
void turnLeftDeg(int degrees, int power) { //Reset encoders SensorValue[leftEncoder] = 0; SensorValue[rightEncoder] = 0; //Determine tickGoal int tickGoal = (42 * degrees) / 10; //Start the motors in a left point turn. motor[leftServo] = -1 * power; motor[rightServo] = power; /*Since the wheels may go at slightly different speeds due to manufacturing tolerances, etc., we need to test both encoders and control both motors separately. This may result in one motor going for longer than another but it will ultimately result in a much more accurate turn. */ while(SensorValue[rightEncoder] < tickGoal || SensorValue[leftEncoder] > -1 * tickGoal) { if(SensorValue[rightEncoder] > tickGoal) {motor[rightServo] = 0;} if(SensorValue[leftEncoder] < -1 * tickGoal) {motor[leftServo] = 0;} } //Make sure both motors stop at the end of the turn. motor[leftServo] = 0; motor[rightServo] = 0; } |
To turn right, we just change the function accordingly:
void turnRightDeg(int degrees, int power) { //Reset encoders SensorValue[leftEncoder] = 0; SensorValue[rightEncoder] = 0; //Determine tickGoal int tickGoal = (42 * degrees) / 10; //Start the motors in a left point turn. motor[leftServo] = power; motor[rightServo] = -1 * power; /*Since the wheels may go at slightly different speeds due to manufacturing tolerances, etc., we need to test both encoders and control both motors separately. This may result in one motor going for longer than another but it will ultimately result in a much more accurate turn.*/ while(SensorValue[leftEncoder] < tickGoal || SensorValue[rightEncoder] > -1 * tickGoal) { if(SensorValue[leftEncoder] > tickGoal) { motor[leftServo] = 0; } if(SensorValue[rightEncoder] < -1 * tickGoal) { motor[rightServo] = 0; } } //Make sure both motors stop at the end of the turn. motor[leftServo] = 0; motor[rightServo] = 0; } |
Test Program
Finally, we are going to test these functions by making the robot do four turns which should make it end up back at the starting position. All we need to do is put both functions in the program, then call them from within task main().
#pragma config(CircuitBoardType, typeCktBoardUNO) #pragma config(PluginCircuitBoard, typeShieldDFRobotMotor) #pragma config(UART_Usage, UART0, uartSystemCommPort, baudRate200000, IOPins, dgtl1, dgtl0) #pragma config(Sensor, dgtl8, rightEncoder, sensorQuadEncoder) #pragma config(Sensor, dgtl10, leftEncoder, sensorQuadEncoder) #pragma config(Motor, motor_5, rightServo, tmotorInternalHBridgeSinglePWM, openLoop, reversed, IOPins, dgtl5, dgtl4) #pragma config(Motor, motor_6, leftServo, tmotorInternalHBridgeSinglePWM, openLoop, reversed, IOPins, dgtl6, dgtl7) //*!!Code automatically generated by 'ROBOTC' configuration wizard !!*// void turnLeftDeg(int degrees, int power) { //Reset encoders SensorValue[leftEncoder] = 0; SensorValue[rightEncoder] = 0; //Determine tickGoal int tickGoal = (42 * degrees) / 10; //Start the motors in a left point turn. motor[leftServo] = -1 * power; motor[rightServo] = power; /*Since the wheels may go at slightly different speeds due to manufacturing tolerances, etc., we need to test both encoders and control both motors separately. This may result in one motor going for longer than another but it will ultimately result in a much more accurate turn.*/ while(SensorValue[leftEncoder] < tickGoal || SensorValue[rightEncoder] > -1 * tickGoal) { if(SensorValue[leftEncoder] > tickGoal) { motor[leftServo] = 0; } if(SensorValue[rightEncoder] < -1 * tickGoal) { motor[rightServo] = 0; } } //Make sure both motors stop at the end of the turn. motor[leftServo] = 0; motor[rightServo] = 0; } void turnRightDeg(int degrees, int power) { //Reset encoders SensorValue[leftEncoder] = 0; SensorValue[rightEncoder] = 0; //Determine tickGoal int tickGoal = (42 * degrees) / 10; //Start the motors in a left point turn. motor[leftServo] = power; motor[rightServo] = -1 * power; /*Since the wheels may go at slightly different speeds due to manufacturing tolerances, etc., we need to test both encoders and control both motors separately. This may result in one motor going for longer than another but it will ultimately result in a much more accurate turn.*/ while(SensorValue[leftEncoder] < tickGoal || SensorValue[rightEncoder] > -1 * tickGoal) { if(SensorValue[leftEncoder] > tickGoal) { motor[leftServo] = 0; } if(SensorValue[rightEncoder] < -1 * tickGoal) { motor[rightServo] = 0; } } //Make sure both motors stop at the end of the turn. motor[leftServo] = 0; motor[rightServo] = 0; } task main() { //Turn right 90 degrees with power 90 turnRightDeg(90,90); //Wait a bit so the robot's momentum does not effect the next turn wait1Msec(500); //Continue doing turns like the above turnLeftDeg(270,90); wait1Msec(500); turnRightDeg(90,90); wait1Msec(500); turnLeftDeg(270,90); //robot should end up facing the same way it started } |
Using these functions is a good way to consistently get very close to the correct degree when turning. While the robot may not turn on a dime or quite get to precisely the right spot (owing to the rounding we did to create the formula) it will get you to within a degree or two of your intended destination, which is usually good enough for the tasks the robot will be performing.