Seiten

Mittwoch, 15. Januar 2014

Lego Mindstorms EV3 Components: Motors - Part 2

In the first article of the Mindstorms component series I showed the most basic way to control motors which are connected to the EV3-brick. We had a robot with 1 motor connected which made the robot move forwards and backwards and one smaller motor which let three razorblades rotate (for fun).
In this second part I will explain how to use the superior level of abstraction to control a robot with two motors (each motor moves one wheel). The lejos-crew calls these robots  "wheeled vehicles".

So, first I built such a vehicle as shown in these pictures:


(For the wheels I used the construction manual of the GRIPP3R-robot)

DifferentialPilot-class

As said before, the DifferentialPilot class from the lejos api represents the superior level of abstraction when communicating with the motors. You can use it when you have a robot with two motors connected to the brick where each motor controls an own wheel.

It offers different methods like travel(), steer() or arc() which will be used in the program for the robot.

The parcours

First, I thought of a little demonstration parcours for the new robot. It should contain simple movements for- and backwards, rotations and turns / circles. As a result the parcours consists of 3 parts:


So, the first part of the parcours is pretty simple, the robot will move in a rectangular shape until it is back in its initial position.


In the second part, the robot then will move 90° in a turn. After the turn it will then drive a 90° turn backwards after which the robot will be in the position indicated transparent in the picture.


At the end of the second part the robot will first move forwards a bit. Then it will move in a circle. After the circle has finished, the robot will rotate by 180° (and then look like transparent indication in the picture). Then the robot will move backwards until it is back in the initial position from the beginning of the parcours.

The code

Let's have a look at the plain code of the finished program first.

import lejos.hardware.motor.Motor;
import lejos.robotics.navigation.DifferentialPilot;

public class Main {
    public static void main(String[] args) {

        final DifferentialPilot pilot = new DifferentialPilot(30.0, 185.0, Motor.C, Motor.B);
        pilot.setAcceleration(200);
        pilot.setRotateSpeed(50.0);
        pilot.setTravelSpeed(50.0);

        pilot.travel(350.0);
        pilot.rotate(90.0);
        pilot.travel(190.0);
        pilot.rotate(90.0);
        pilot.travel(350.0);
        pilot.rotate(90.0);
        pilot.travel(190.0);
        pilot.rotate(90.0);

        pilot.steer(65.0, 90.0);
        pilot.steer(-65.0, 90.0);

        pilot.travel(175.0);
        pilot.arc(-100.0,-360.0);
        pilot.steer(200.0,180.0);
        pilot.travel(-175.0);

    }
}


At the beginning we instantiate a new instance of a DifferentialPilot and configure the wanted movement speeds. The used constructor takes four parameters:
  • wheel-diameter: The diameter of the wheel connected to the motor. The used wheel has a 30mm diameter.
  • track width: The distance between the centres of both wheels. In my case ~185mm.
  • left motor: The plug connection where the left motor is plugged in.
  • right motor: The plug connection where the right motor is plugged in.
The diameter of the wheels and the track width are needed by the api to know how long the motors must rotate to perform correct rotations etc.

In the next block we perform the first part of the parcours which is pretty trivial. The travel-method makes the robot move forwards for the given distance (350mm). Recognize, that the distance unit must be the same as used for wheel diameter and track width in the constructor. That method is a blocking operation, so the program will wait here until the movement has finished. After that, the robot will rotate (in place) by 90° to the left (-90° would be  90° right). This happens four times until the rectangular movement has finished and the robot is back in its initial position.

In the next block there are to steer-method calls.
The steer()-method takes two parameters:
  • turnRate: The ratio of the two wheels' speeds (Integer between -200 and +200)
  • angle: When the robot turned by this degrees, the steering will stop.
This needs some more explainations:
turnRate: If the value is positive you are configuring the left wheel, if it's negative you are configuring the right wheel. Let's look at some positive values:
0 - means, that both wheels will rotate at the same speed, which causes the robot to move forwards (no steering!)
100 - means, that the left wheel won't move.
200 - means, that the left wheel will do the exact opposite of the right wheel.

angle: you can enter a value between 0 und 360°, positive and negative. Positive values will rotate the brick to the left, negative values cause right rotations.

So, back to our two method-calls. The first call says: Make the left wheel move 0.65 * the speed of the right wheel. This will cause the robot to turn leftwards while moving. The robot will then move until it has turned by 90° to the left.
The second method call: The first parameter is negative, so we tell the RIGHT wheel to move at 0.65 * the speed of the LEFT wheel. The robot will move until the robot turned 90° to the left. Recognize, that the robot can only turn to the left by moving "backwards" because the right wheel rotates slower than the left wheel.

In the last block the motor travels some centimeters fowards. Then there is a new method called arc. This methods tells the robot to move in a circle. The two parameters are:
  • radius of the circle
  • angle: When the robot turned by this degrees, the steering will stop.
Both parameters may be positive or negative.
A positive radius means, that the circle centre should be on the left of the brick. A negative respectively means that the circle centre is on the right side of the brick.
A positive angle means that the robot will rotate to the left, a negative angle respectively right.

So, the arc-method-call says: Drive a circle. The circle center is on the right side of the brick and has a radius of 10cm. Drive that circle by rotating rightwards. The only possibility to do that, is by driving the circle forwards. A positive angle of 360° would have caused the robot to drive the circle by rotating to the left, which could only have been realized by moving backwards.
The next steer-method-call is easy: It says: The left wheel should do the opposite of the right wheel. The robot should move until it has turned by 180°. Maybe you recognized it: It's a simple 180° in-place rotation. So we could have used pilot.rotate(180) here, too.
The last travel()-call makes the robot move backwards the last centimeters until it is back in it's initial position.

That's it, at last I just refactored and added some Console-ouput to the program for comfort:

import lejos.hardware.motor.Motor;
import lejos.robotics.navigation.DifferentialPilot;

public class Main {

    final static double NINETY_DEGREES = 90.0;

    public static void main(String[] args) {

        final DifferentialPilot pilot = new DifferentialPilot(30.0, 185.0, Motor.C, Motor.B);
        pilot.setAcceleration(200);
        pilot.setRotateSpeed(50.0);
        pilot.setTravelSpeed(50.0);

        System.out.println("Configured speeds and starting the first part of the parcours.");
        travelRectangle(pilot, 350.0, 190.0);

        System.out.println("Starting the second part of the parcours.");
        driveTurns(pilot);

        System.out.println("Starting the third part of the parcours.");
        driveCircleAndBackToInitialPosition(pilot);

    }

    private static void driveCircleAndBackToInitialPosition(final DifferentialPilot pilot) {
        System.out.print("    Driving half the way...");
        pilot.travel(175.0);
        System.out.println("done!");
        System.out.print("    Driving the circle...");
        pilot.arc(-100.0,-360.0);
        System.out.println("done!");
        System.out.print("    Turning 180 degrees...");
        pilot.steer(200.0,180.0);
        System.out.println("done!");
        System.out.print("    Driving backwards to initial position...");
        pilot.travel(-175.0);
        System.out.println("done!");
    }

    private static void driveTurns(final DifferentialPilot pilot) {
        System.out.print("    Driving turn forwards...");
        pilot.steer(65.0, NINETY_DEGREES);
        System.out.println("done!");
        System.out.print("    Driving turn backwards...");
        pilot.steer(-65.0, NINETY_DEGREES);
        System.out.println("done!");
    }

    private static void travelRectangle(final DifferentialPilot pilot, final double rectLength, final double rectWidth) {
        travelRectangleEdgeAndRotate(pilot, rectLength);
        travelRectangleEdgeAndRotate(pilot, rectWidth);
        travelRectangleEdgeAndRotate(pilot, rectLength);
        travelRectangleEdgeAndRotate(pilot, rectWidth);
    }

    private static void travelRectangleEdgeAndRotate(final DifferentialPilot pilot, final double distance) {
        System.out.print("    Driving " + distance + " mm...");
        pilot.travel(distance);
        System.out.println("done!");
        System.out.print("    Rotating 90 degrees...");
        pilot.rotate(NINETY_DEGREES);
        System.out.println("done!");
    }
}
and here is the result:



It isn't 100% exact, but still quite satisfying. I think this is because the wheel diameter and the track width I gave the constructor aren't perfectly correct.