This tutorial describes how to use the NXT motors with pbLua. These motors are a big improvement over the standard motors that were used in the original RCX kit. They are more powerful and include tacho-feedback sensors to make precise speed control and movements possible. The tutorial also describes how to synchronize the speed of two motors so that they turn together at some fixed ratio to each other.
This tutorial will discuss the pbLua Output API and how to use it to get basic control of the motors.
Note that some of the examples we'll be going over are not the best way to do things. For instance, the busy-wait function will prevent any other code from running until the condition clears. In later tutorials, we'll be showing ways around this problem using the build-in cooperative multitasking feature of Lua.
As a reminder, there is an excellent online version of Programming in Lua that is a complete description of Lua along with many programming examples, and an even better idea is to purchase the latest Programming in Lua book .
By the end of this tutorial, you'll be able to turn the motors on and off and use regulation to make them run at a constant speed. You'll also be able to use the synchronizing mode.
Before using a motor with the Motor Output API you need to know the port number the port will be plugged into, and what you want the motor to do. We'll be providing plenty of examples of ways you can use the motors to power your robotic creations.
The NXT has 3 motor ports along the top face of the housing. They are numbered from A to C, and you'll need to specify them as 1 to 3 when using the Output API.
Each port has the capability of driving the motor ports with either positive or negative polarity, which affects the direction of the motor. The port can also read a tacho output from the motor, which gives 360 counts per revolution of the motor.
The nxt.OutputGetStatus() function returns 8 values, which seems like a lot, until you realize that you don't need to use all of them. The following example will simply show the value of the tacho sensor in the motor until you press the orange button on the NXT. Turn the motor by hand and watch the readings change.
-- Read motor port until the orange button is pressed
function MotorRead(port)
repeat
print( nxt.TimerRead(), nxt.OutputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function with a motor plugged into port A...
MotorRead(1)
Each line should look something like this:
123456 0 70 70 0 0 70 0 0
The first value is the current value of the millisecond timer, and the other 8 are the results of nxt.OutputGetStatus(). Note that the second, third, and sixth values change in unison - they are the three tachometer readings that are available to your programs.
The first tachometer reading is the one used for accurate step counts, the other readings are available for your own use in calculations. We'll discuss this in the tutorial on advanced motor control.
The very next thing you'll want to do is turn the motor on, and it's instructive to see how this affects the results of nxt.OutputGetStatus(). The next example uses a few more of the buttons on the NXT to control the motor operation.
When the function is running, pressing the left arrow reduces the speed and pressing the right arrow increases the speed. The motor starts at speed 0, so pressing the left button makes the speed negative which changes the direction of the motor.
-- Read motor port until the orange button is pressed
-- Left arrow decreases speed
-- Right arrow increases speed
function MotorSpeed(port)
local speed = 0
repeat
if 4 == nxt.ButtonRead() then
if speed >= -95 then
speed = speed - 5
nxt.OutputSetSpeed( port, 32, speed )
end
end
if 2 == nxt.ButtonRead() then
if speed <= 95 then
speed = speed + 5
nxt.OutputSetSpeed( port, 32, speed )
end
end
print( nxt.TimerRead(), nxt.OutputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
-- Remember to turn the motor off!
nxt.OutputSetSpeed( port, 0, 0 )
end
-- And using the function with a motor plugged into port A...
MotorSpeed(1)
Hey, what's wrong with this? The motor seems to lock in at either 100 or -100 for the speed. I'll give you a few minutes to think about what's wrong....
The answer is that the program runs so fast, that you can't possibly hold the button down for a small enough time to only change the speed by a small step. We can change the program to either sample the buttons less frequently or to only sample the buttons when no buttons are pressed, thus requiring individual presses to make a change.
Here's the program updated to handle button presses individually:
-- Read motor port until the orange button is pressed
-- Left arrow decreases speed
-- Right arrow increases speed
function MotorSpeed(port)
local speed = 0
local oldButton = 0
local newButton = 0
repeat
newButton = nxt.ButtonRead()
if 0 == oldButton then
-- Only check buttons if no buttons are pressed!
if 4 == newButton then
if speed >= -95 then
speed = speed - 5
nxt.OutputSetSpeed( port, 32, speed )
end
end
if 2 == newButton then
if speed <= 95 then
speed = speed + 5
nxt.OutputSetSpeed( port, 32, speed )
end
end
end
oldButton = newButton
print( nxt.TimerRead(), nxt.OutputGetStatus(port) )
until( 8 == newButton )
-- Remember to turn the motor off!
nxt.OutputSetSpeed( port, 0, 0 )
end
-- And using the function with a motor plugged into port A...
MotorSpeed(1)
That's better! You'll hear a whining sound until the absolute value of the motor speed is about 55 or 60, and then the motor starts turning. You'll notice that the motor is very easy to stop by hand at low speed settings.
This is the most basic mode of operation for a motor, and it's useful in many cases, but what if you really want the motor to "hold" its speed given different load conditions? That's when you want speed regulation, which is the next section.
When you want the motor to turn at a more or less constant speed whatever the load is, then regulation will help. The NXT motor driver firmware that is used by pbLua takes care of this if you tell it that you want to use regulation and what mode to use.
I've discussed the basics of regulation in general terms before. For the NXT, if the motor is turning slower than the target speed, then the speed setting is increased until the target speed is reached.
There are two types of speed regulation that the nxt.OutputSetRegulation() function knows about. The first is single motor regulation where the firmware tries to hold the speed of the motor constant. The other is synchronized regulation where the firmware tries to hold the speed of two motors constant relative to each other.
In addition, there are two modes of regulation, float (0) and brake (1). In float mode, the motor driver circuit allows the motor to coast or "float" between pulses, and the speed will be more affected by loads. In brake mode, the motor driver will try to stop the motor between pulses. This will make the motor speed more accurate under loaded conditions. It's also better for position regulation as we'll see in the section on advanced motor control.
We'll just add two parameters to the test function that will tell us what regulation state and mode to use so that we can try out the difference:
-- Read motor port until the orange button is pressed
-- Left arrow decreases speed
-- Right arrow increases speed
function MotorSpeed(port, state, mode )
local speed = 0
local oldButton = 0
local newButton = 0
nxt.OutputSetRegulation( port, state, mode )
repeat
newButton = nxt.ButtonRead()
if 0 == oldButton then
if 4 == newButton then
if speed >= -95 then
speed = speed - 5
nxt.OutputSetSpeed( port, 32, speed )
end
end
if 2 == newButton then
if speed <= 95 then
speed = speed + 5
nxt.OutputSetSpeed( port, 32, speed )
end
end
end
oldButton = newButton
print( nxt.TimerRead(), nxt.OutputGetStatus(port), collectgarbage("count") )
until( 8 == newButton )
-- Remember to turn the motor off!
nxt.OutputSetSpeed( port, 0, 0 )
end
-- Now try it out with regulation in float mode...
MotorSpeed( 1, 1, 0 )
-- Now try it out with regulation in brake mode...
MotorSpeed( 1, 1, 1 )
-- And with no regulation at all...
MotorSpeed( 1, 0, 0 )
You can see that the motor speed is actually higher than the setpoint at low speeds, and the motor speed tends to fluctuate quite a bit at lower values. That's because the driver has to set the speed to about 60 just to get the motor to turn, but then it has to slow down because 60 is too fast. When this occurs, we say that the motor set speed oscillates around the target speed.
If you try to slow down the motor by applying a load, you'll see the speed value go up to an even higher value just to try to keep the motor moving at the correct speed. That's because it has to apply more and more "speed" to get the motor to turn against the resistance.
Besides regulating the speed of a single motor, the pbLua firmware now supports synchronized motor operation. This means that you can set two motors to stay synchronized with each other to some fixed difference or ratio.
The easiest way to see this in action is to connect two motors to each other through a differential, but with the motors set up facing each other, as shown in this picture. The synchronisation feature is set up so that the difference is speed between the motors is a constant.
Let's call the two motors we're syncronizing Motor 1 and Motor 2. The motor ports on the NXT are labelled "A", "B", and "C". No matter which ports the motors are plugged into, the port with the lowest letter is Motor I and the other port is Motor II. You'll see why I'm using Roman numberals in a second...
For example, if we're trying to use the sync operation on motors plugged into ports B and C, we refer to them as motor number 2 and 3 respectively in the output API. This gets confusing if we use letters or numbers in describing the motors being synchronized.
The motor in port B (number 2 in the API) is motor I, and the motor in port C (number 3 in the API) is motor II. Clear? As mud? Let's move on to some examples.
Here's the basic code for syncing two motors. Make sure you've got the motors plugged into ports "B" and "C" for this test. It runs until you press the orange button on the NXT.
-- Sync Motors B (I) and C (II) - the speed is s and the difference is t function MotorSync(s,t) nxt.OutputResetTacho(2,1,1,1) nxt.OutputResetTacho(3,1,1,1) nxt.OutputSetRegulation(2,2,1) nxt.OutputSetRegulation(3,2,1) nxt.DisableNXT( 1 ); nxt.OutputSetSpeed(2,0x20,s, 0, t ) nxt.OutputSetSpeed(3,0x20,s, 0, t ) nxt.DisableNXT( 0 ); repeat until( 8 == nxt.ButtonRead() ) nxt.OutputSetSpeed(2) nxt.OutputSetSpeed(3) end -- And using the function - press the orange button on the NXT to stop it -- Motor I and II try to stay sunchronized MotorSync(75,0) -- Motor I turns a bit slower than Motor I MotorSync(75,20) -- Motor I stops - all power goes to Motor I MotorSync(75,50) -- Motor I turns a bit slower than Motor 1 - in the opposite direction MotorSync(75,60)
Notice the calls to nxt.DisableNXT() - they are there to disable the 1 msec interrupt handler that updates the NXT drivers. Previous versions of this code would occasionally have flaky sync behaviour because the interrupt handler would run between setting the sync mode for motor I and motor II.
The operation of the "turn" parameter is best left to the following chart. Play around with the turn parameter until you undedrstand what's really going on
The following code is an example of a generic line follower that uses the light sensor to track a dark line on a light background. You may need to modify the code a bit to make it work, depending on how you've set up the light sensor relative to the turning radius of your robot.
-- Line Follower!
function LineFollow(target,delay,n)
local port = 1
nxt.InputSetType(port,5)
nxt.OutputSetRegulation(1,1,1)
nxt.OutputSetRegulation(3,1,1)
local idx = 1
local speed = 0;
-- initialize the raw array to "grey"
local raw = {}
for i=1,n do
raw[i] = target
end
repeat
t = nxt.TimerRead()
while t+delay > nxt.TimerRead() do
-- nothing
end
-- get a new raw reading
raw[(idx%n)+1] = nxt.InputGetStatus(port)
-- calculate the average
local sum = 0
for i=1,n do
sum = sum + raw[n]
end
local avg = sum/n
print( avg )
if avg > target then
speed = 20 + ((avg - target)/2)
if speed > 50 then speed = 50 end
nxt.OutputSetSpeed(1,0x20,20)
nxt.OutputSetSpeed(3,0x20,speed)
else
speed = 20 + ((target - avg)/2)
if speed > 50 then speed = 50 end
nxt.OutputSetSpeed(1,0x20,speed)
nxt.OutputSetSpeed(3,0x20,20)
end
idx = idx + 1
until( 8 == nxt.ButtonRead() )
nxt.OutputSetSpeed(1,0,0)
nxt.OutputSetSpeed(3,0,0)
nxt.InputSetState(port,0,0)
end
-- And using the function - press the orange button on the NXT to stop it
LineFollow(760,780)
This code is very simple to understand, but it lacks a bit of the sophistication that would make it much better. It would be pretty simple to modify the code to set the speeed difference between the motors to be proportional to the "distance" of the light sensor reading from the target grey value.
That way, the robot would tend to mek more gentle corrections if it is moving along the line, rather than get way off the line before making drastic spinning moves to find the line again.
Mechanically, you want to set things up so that the light sensor is right along the turning radius of the robot, so that the small turns don't get amplified into very large motions by the light sensor.
Basic motor control is straighforward with pbLua, so the next thing to do is figure out how to get the more advanced functions like precise positional control working. That's covered in the tutorial on advanced motor control.