Controlling NXT motors
Being able to control the NXT motors is crucial for most robots. Only very few models work without moving parts. So read on to learn all you need about the class NXTMotor.
If you want to execute the examples from this tutorial directly in the command window, I recommend this little sequence of commands to establish a connection to your NXT:
COM_CloseNXT all h = COM_OpenNXT('bluetooth.ini'); COM_SetDefaultNXT(h);
Now all following examples should work right away by copy-pasting...
To operate the motors, we need to create motor objects, i.e. instances of the class NXTMotor. Those objects are capable of sending various commands to one or two motors, or can be used to retrieve data from them. To create such an object, the simplest way is just
mA = NXTMotor('A')
Now the object mA provides properties and methods to control the NXT motor on port A. First, we probably just want to see if the motor works at all. Ok then, let's go:
mA.Power = 50; % let's start with half the maximum power mA.SendToNXT(); % this is actually the moment we start the motor
Motor A keeps on running... To stop it, we use
This will turn off the power, causing the motor to come to a soft stop (so called "coasting"). If we wanted to brake all of a sudden at once, we'd call
In this case, the NXT's active brake will be enabled, causing the motor to stop hard at the instance it receives the command. The brake alsp keeps the motor actively at its position. It's surprisingly strong, however it also consumes a lot of power, so we better turn it off again. The motor is now completely turned off just as in the beginning of our tutorial session:
You've already seen the basic concept of motor control which consits of these 3 basic steps:
- Create an NXTMotor object
- Set up the parameters as you need them
- Send the command to the NXT using SendToMotor()
You've already seen how to specify the motor settings by setting properties.
mB1 = NXTMotor(); % create a blank motor object mB1.Port = MOTOR_B; % this is the same as using mB.Port = 'B'; mB1.Power = -100; % full power backwards
The example above shows two things:
- You can use the old-fashioned motor constants (i.e. MOTOR_C) to specify port names, or do it by giving strings (like 'C').
- To reverse the direction the motor should turn, set negative power values (as in electronics you'd swap the sign of the current)
A shorter way to create exactly the same object as mB1 is to use NXTMotor's constructor. The first argument always has to be the port you want to use, followed by combinations of 'Property', value as you might know from other MATLAB commands:
mB2 = NXTMotor('B', 'Power', -100, 'SpeedRegulation', false);
As said before: mB1 and mB2 do exactly the same, so use whichever way you prefer. To inspect motor objects on the command line, just type their name:
Knowing how we control the motor's speed by setting a certain value to Power is not enough in most cases. One could try to move the motor a certain distance by using something like this:
mA = NXTMotor('A', 'Power', 50); mA.SendToNXT(); pause(3); % wait exactly 3 seconds mA.Stop('brake');
Using this technique we get the motor running 3 seconds, but due to several influences (for example Bluetooth lag or subtle differences in NXT motors) this won't lead to the same distance travelled by the motor. Also we can't manage different loads on the motors. This is why we have to specify the distance the motor should turn in degrees. The according property is called TachoLimit.
mA.TachoLimit = 360; mA.SendToNXT();
Now the motor will make exactly 1 whole turn and then stop at about 360 degrees (accurate to +/- 1 degree in most cases). It is very important to understand that the motor does this movement "on its own", while MATLAB can execute the next command right away. If we say:
mB = NXTMotor('B', 'Power', 50, 'TachoLimit', 1000); mB.SendToNXT(); NXT_PlayTone(440, 500);
then the NXT will start turning motor B and beep right away! If we want to beep only when the motor has reached its position, we have be patient and wait for the motor:
mB.SendToNXT(); mB.WaitFor(); % this command will hold MATLAB a while NXT_PlayTone(440, 500); % now the motor has stopped!
This brings us to the next important point: In order for a motor to accept a command with SendToNXT(), it must be idle, i.e. at rest (and not carrying out a movement already). We can achieve this by either:
- Waiting for the last motor command to finish by using WaitFor() before calling the next SendToNXT(). If the motor is currently idle, WaitFor() won't wait at all and continue with the next statement right away.
- Abort the current motor movement using the Stop() method.
If we don't follow this rule, the NXT will ignore the second motor command and tell you it has just dropped a command by a beeping signal. Example:
% imagine we want our motor to make 2 turns of 360 degrees mB = NXTMotor('B', 'Power', 50, 'TachoLimit', 360); mB.Stop('off'); % Just to make sure motor is ready! mB.SendToNXT(); % Motor is busy now mB.SendToNXT(); % THIS DOES NOT WORK!!! MOTOR IS BUSY % This example is wrong. % We have to use a .WaitFor between the two .SendToNXT()
To switch back to unlimited movement, just set TachoLimit back to 0.
So setting a value to TachoLimit specifies the angle the motor should spin to, and by the sign of Power we can decide the turning direction. We still have some more options to control how the motor should brake. The property is called ActionAtTachoLimit. Quoting possible parameters from the documentation of NXTMotor:
- In 'Coast' mode, the motor(s) will simply be turned off when the|TachoLimit| is reached, leading to free movement until slowly stopping (called coasting). The TachoLimit won't be met, the motor(s) move way too far (overshooting), depending on their angular momentum.
- Use 'Brake' mode (default) to let the motor(s) automatically slow down nice and smoothly just before the TachoLimit. This leads to a very high precision, usually the TachoLimit is met within +/- 1 degree (depending on the motor load and speed of course). After this braking, power to the motor(s) is turned off when they are at rest.
- 'Holdbrake' is similar to 'Brake', but in this case the active brake of the motors stays enabled (careful, this consumes a lot of battery power), causing the motor(s) to actively keep holding their position.
There are two more options left we'll shortly discuss here. For further details you should consider the documentation of NXTMotor.
- SpeedRegulation - this property is set to true by default, but you should carefully evaluate wether you need it or not for every motor object. It basically tries to keep your motor running at a constant speed, no matter how much load you put on it. If a motor has heavy lifting to do, the Firmware will internally increase the power value (if possible) to keep the motor running fast enough. While this is a nice thing to have, be careful: The torque of your motor will be dynamically adjusted. Imagine you've got a robotic arm with a limited area of operation and a sensitive gearing. If you enable speed regulation and somehow your arm get's stuck (for example by a programming error of yours) or moves too far, the Firmware will increase the power value to overcome the obstacle. This behavior could destroy your gears or the arm. Sometimes it's desirable to have a constant torque. In that case, better deactivate speed regulation. Another thing to mention: With low speeds, speed regulation sometimes achieves the opposite of what you want: the motor keeps cogging or stuttering. In these cases, speed regulation should also be turned off.
- SmoothStart - enable this if you want your motor(s) to accelerate softly. Desirable especially for driving robots so that their wheels won't slip when they start driving. The motor will increase its power value in the beginning of movement stepwise until the full speed is reached. This option does only work when a TachoLimit > 0 is set. It doesn't work with ActionAtTachoLimit = 'Coast' either.
Until now we've always talked about motor objects controlling one single motor. You could create two of these objects to start a driving robot (where the wheels are attached to ports B and C):
mB = NXTMotor('B', 'Power', 50); mC = NXTMotor('C', 'Power', 50); mB.SendToNXT(); mC.SendToNXT();
There are two problems with this simple approach:
- The commands are sent one after each other to the NXT, causing motor C to start a tiny bit after motor B. This will result in your robot making a little turn in the beginning.
- The motors are running at slightly different speeds, since they aren't perfectly unique. This causes the bot to drive a curve, rather than a straight line. The solution: Use a single motor object to control two motors at the same time. They will be operated in "synchronization mode". This means the NXT makes sure that they run at both the same speed for your bot to drive a straight line. This also means that speed regulation does not work when driving two motors synced. But let's look at an example first:
mBC = NXTMotor('BC', 'Power', 50); mBC.SendToNXT();
Easy, huh? We can also use the commands WaitFor() or stop as we're used to. And the property Port can of course be accessed as usual. Another example:
myMotors = NXTMotor(); myMotors.Port = [MOTOR_B; MOTOR_C]; % same as myMotors.Port = 'BC'; myMotors.Power = 50; myMotors.TachoLimit = 1000; myMotors.SmoothStart = true; myMotors.SpeedRegulation = false; % don't forget this! % If we tell the constructor that we use two ports, speed regulation will % automatically turned off, i.e.: % m = NXTMotor('BC'); % <-- m.SpeedRegulation = false is set already! myMotors.Stop('off'); % make sure motors are off before we start. myMotors.SendToNXT();
There is one certain behavior you should get familiar with. Let's assume you use the following code:
mBC = NXTMotor('BC', 'Power', 50, 'TachoLimit', 1000); mBC.ActionAtTachoLimit = 'Coast'; mBC.SendToNXT(); mBC.WaitFor(); pause(3);
During the final pause of 3 seconds, we can notice a certain high-pitched noise coming from the motors. Also they seem to be powered up and slightly moving or vibrating. This is the motor synchronization in action. If you try to move one wheel, the other will be moved automatically, too. This ensures the wheels are (roughly) in the same position, as if they were connected through an axle. We can manually turn off this synchronization with Stop():
Using the knowledge we now have about NXTMotor, it's time to get used working with multiple motor objects. In the following example, we set up a basic set of objects. To
% Set some parameters: leftWheel = MOTOR_B; rightWheel = MOTOR_C; bothWheels = [leftWheel; rightWheel]; drivingPower = 60; turningPower = 40; drivingDist = 1000; % in degrees turningDist = 220; % in degrees % now create the objects for straigt driving: mForward = NXTMotor(bothWheels, 'Power', drivingPower, 'TachoLimit', drivingDist); mReverse = mForward; % clone object mReverse.Power = -mForward.Power; % just swap the power sign
Now we can already drive our bot a certain distance forward or reverse. We just have to send the objects to the NXT and wait until they complete:
% let the bot drive forward mForward.SendToNXT(); mForward.WaitFor(); % and now return to the origin mReverse.SendToNXT(); mReverse.WaitFor(); % done! NXT_PlayTone(440, 500);
In order to make nice turns (by 90 degrees for example), it's best to use a 2-way motion: First let one wheel spin one direction (now the bot has made half of its turn and faces 45 degrees), then turn the other wheel into the opposite direction. Now the 90-degree-turn should be complete.
% for turning the bot, we have two objects each: mTurnLeft1 = NXTMotor(leftWheel, 'Power', -turningPower, 'TachoLimit', turningDist); mTurnLeft1.SpeedRegulation = false; % don't need this for turning % for the 2nd part of turning, use first part's settings and modify: mTurnLeft2 = mTurnLeft1; % copy object mTurnLeft2.Port = rightWheel; % but use other wheel mTurnLeft2.Power = -mTurnLeft1.Power; % swap power again % the right-turn objects are the same, but mirrored: mTurnRight1 = mTurnLeft1; % first copy... mTurnRight2 = mTurnLeft2; mTurnRight1.Power = -mTurnRight1.Power; % now mirror powers mTurnRight2.Power = -mTurnRight2.Power; % Instead of mirroring the powers, we could've also changed % the ports (swapped left and right wheels).
You can now use these objects like this:
% make a left-turn mTurnLeft1.SendToNXT(); mTurnLeft1.WaitFor(); mTurnLeft2.SendToNXT(); mTurnLeft2.WaitFor(); % wait here a moment pause(1); % turn back to the origin mTurnRight1.SendToNXT(); mTurnRight1.WaitFor(); mTurnRight2.SendToNXT(); mTurnRight2.WaitFor();
Apart from sending commands to the motors, we can also retrieve information from them, the most important being the current position in degrees. For more documentation on this, see the help texts for ReadFromNXT and ResetPosition. These are the commands we'll use in this section.
So, when calling ReadFromNXT() on a motor object, a MATLAB struct is returned, containing some useful fields. We'll just focus on Position, i.e. where the motor has currently turned to. This rotation counter keeps on tracking every movement, wether you turn the motor by hand or by a commend, it always tells you the exact position of the axle at the time the NXT receives your request. The change of Position corresponds to the turning direction: If the motor turns the way as it would if operated with a positive Power, Position increases. Otherwise (spinning reverse), Position decreases (and can become negative). Enough of the theory, a simple example:
% We need a motor object to get access to the methods % also we do a little driving... m = NXTMotor('A', Power, '50'); % clear position counter: m.ResetPosition(); m.SendToNXT(); pause(1); m.Stop('off'); % motor is still coasting at the moment... % now retrieve motor info data = m.ReadFromNXT(); % show it to the user: disp(sprintf('Motor A is currently at position %d', data.Position)); % wait until the motor doesn't move anymore pause(2); % and display the position again: data = m.ReadFromNXT(); disp(sprintf('Motor A is finally at position %d', data.Position));
Please note that all other properties of the motor object except Port do not matter to use the method the methods ReadFromNXT() and ResetPosition. Also the returned data-structure from ReadFromNXT() only reflects the current state of the NXT motor, and does not necessarily have something to do with the motor object's properties you used to retrieve the data (e.g. TachoLimit and Power of the retrieved data and of the object can be different).
Now let's imagine we want to control a robotic arm. For the sake of this example, we want it to move up and down, alternatingly for 10 times. Let the arm just have one joint, so we only control one motor. A little problem is gravity: The arm has a certain weight. So if we aren't careful, moving it downwards will sometimes result in a little lower position than we expect, and moving upwards won't move the motor as far as we hope all the time. After all, the motor control is only accurate to a couple of degrees (+/- 1 most of the time, but not necessarily equally distributed). So without reading the motor's position and accounting for those inaccuracies, the errors would accumulate over time and cause a significant displacement of the arm. By simply retrieving the arm's position and making sure that it moves a bit more upwards if necessary, or a bit less downwards, we can avoid the accumulating errors and have precise movement even after a long period of operation. Here is how its done:
% Example which should move a motor (or robotic arm) 10 times back and % forth (up and down), as precisely as possible without blindly trusting % the motor commands. Very simple error compensation (i.e. trying absolute % movements instead of always relative). % Prepare MATLAB COM_CloseNXT all close all clear all % Connect to NXT, via USB or BT h = COM_OpenNXT('bluetooth.ini'); COM_SetDefaultNXT(h); % set params power = 30; port = MOTOR_A; dist = 200; % distance to move in degrees % create motor objects % we use holdbrake, make sense for robotic arms mUp = NXTMotor(port, 'Power', power, 'ActionAtTachoLimit', 'HoldBrake'); mDown = NXTMotor(port, 'Power', -power, 'ActionAtTachoLimit', 'HoldBrake'); % prepare motor mUp.Stop('off'); mUp.ResetPosition(); % repeat 10 times for j=1:10 % where are we? data = mUp.ReadFromNXT(); pos = data.Position; % where do we want to go? % account for errors, i.e. if pos is not 0 mDown.TachoLimit = dist + pos; % move mDown.SendToNXT(); mDown.WaitFor(); % now we are at the bottom, repeat the game: % where are we? data = mUp.ReadFromNXT(); % doesn't matter which object we use to read! pos = data.Position; % pos SHOULD be = dist in an ideal world % but calculate new "real" distance to move % based on current error... mUp.TachoLimit = pos; % this looks very simple now, but it comes from % TachoLimit = dist + (pos - dist); % i.e. real distance + error correction % Imagine it this way: We are currently at pos, % and want to go back to 0, so this is exactly the distance % to go! mUp.SendToNXT(); mUp.WaitFor(); end%for % mode was HOLDBRAKE, so don't forget this: mUp.Stop('off') % clean up COM_CloseNXT(h);
As nice and easy as working with NXTMotor is, the class has its limitations (you can read more about it in the documentation of NXTMotor). One minor problem is that you can't change the speed (i.e. setting a different Power and then call SendToNXT()) of an already running motor. You have to use WaitFor() or Stop() before updating the power, which might not do what you want (changing the speed smoothly during runtime).
Well, there is one exception: If a motor is running infinitely with TachoLimit = 0 set, you actually can send a motor command with a new power, and it will work.
A sometimes bigger problem is latency: Motor commands are not necessarily sent to the NXT at once. Sometimes SendToNXT() takes roughlpy up to 20ms for the actual motor instruction to be executed. Same goes for WaitFor() or Stop(). This is the price you have to pay for the great accuracy that NXTMotor gives you. The reason has to do with the embedded NXC program MotorControl, which is running on the NXT and handles high precision motor movement for you.
What to do if you need fast motor control instead of high precision (i.e. when building a Segway)? Well, you can trade accuracy for performance. The answer is: Use DirectMotorCommand. The last chapter explains how.
DirectMotorCommand is a remainder from previous toolbox versions 1.x and 2.x (it was called SendMotorSettings before). It works without a program running the brick by sending "direct commands" (a term from the LEGO NXT Mindstorms communication protocol) only. First of all:
Warning: Do not use DirectMotorCommand and NXTMotor.SendToNXT() for the same motor at one time together (or only if you know exactly what you're doing). It can mess up your motor control and lead to unexpected results! You can still use NXTMotor.ReadFromNXT() if you like.
Direct motor command gives access to some advanced features:
- Ramp up & ramp down: You can let motors linearly accelerate or decelerate over a distance of your choice.
- TurnRatio: You can run motors synchronized, but with different speeds of each motor. This feature doesn't work reliable however.
There are however more problems:
- Movement is not accurate anymore. Basically you only have something like ActionAtTachoLimit = 'Coast', but with even less quality.
- Sometimes the NXT's error correction does changes to your TachoLimit which seem hard to understand. Read the section Troubleshooting of this toolbox documentation for more on that.
The big advantage is execution speed: Nothing else is faster than DirectMotorCommand or NXT_SetOutputState. Latency via USB connections is usually about 3ms, and via Bluetooth it can be between 5ms and 30ms, or even more for synchronized driving.
Make sure to read the documentation page for DirectMotorCommand.
I might help to also read the documents "Executable File and Bytecode Reference" (LEGO MINDSTORMS NXT Executable File Specification) and "Bluetooth Developer Kit (BDK)" from http://mindstorms.lego.com/Overview/nxtreme.aspx ; it may help you getting familiar with the communication protocol. Other useful commands are NXT_GetOutputState and NXT_SetOutputState.
Proceed to next chapter.