Note that the sensor API has changed significantly as of the Beta 18a version of pbLua. It also covers the new color sensor that's available in the NXT 2.0 set. Please review any existing code you may have and change it appropriately.
This tutorial describes how to use the three basic sensors that come with the NXT kits. The NXT 1.0 comes with touch, light, sound, and ultrasonic range finder sensors. The NXT 2.0 kit comes with 2 touch sensors, a color and an ultrasonic range sensor. We'll cover the ultrasonic sensor in a future discussion on the I2C capabilities of the NXT.
Just because we're calling them basic does not mean that you can't do anything interesting with them. The examples in this section demonstrate how to display the sound level in a real-time graph, and how to use the color sensor.
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 read the touch, light and sound sensors and write simple functions that use the data. We'll be building on the knowledge gained from the tutorial on first steps with pbLua and using the LCD display.
Before using a sensor with the Input Port API you need to know the port number the sensor will be plugged into, what type the sensor is, and optionally which mode you'd like to use the sensor value in. These things are described in detail for each sensor type in the next sections.
The NXT has 4 sensor ports along the bottom face of the housing. They are numbered from 1 to 4. The sensor type is set using the nxt.InputSetType() function. Each sensor port has two I/O pins which can be independently controlled using the nxt.InputSetDir() and nxt.InputSetState() functions. We won't be needing to set the pins directly in this tutorial.
The sensor port is read using the nxt.InputGetState() function which returns the raw A/D value, the current state of the two I/O pins, and a processed value if the sensor mode setting supports it.
The raw A/D value will range between 0 and 1023 - which is a range of 10 bits.
The simplest sensor of all is the touch sensor. The raw A/D value returned by the nxt.InputGetState() function changes as the button is pressed. The sensor type is 1, and setting the optional mode parameter to certain values lets you return different results depending on how you plan to use the sensor.
The following code example continuously samples the touch sensor connected to port 1 and prints out the current millsecond timer value as well as the raw A/D and I/O pin states until the orange button on the front of the NXT is pressed.
-- Read touch sensor until the orange button is pressed
function TouchRead(port)
nxt.InputSetType(port,1)
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
TouchRead(1)
In this example, we pass the port number to the function when we call it. When you run the TouchRead() function, watch the console display as it scrolls the readings by - they are moving very quickly indeed! This function reads the sensor and prints the results about 500 times per second.
Note the values returned by the touch sensor. My NXT running on NiMH batteries gives a value of 1023 for an open touch sensor, and a value of about 182 for a closed touch sensor.
The touch sensor has a number of additional modes of operation, as shown in the following examples. You are encouraged to download each of them to the NXT and press the touch sensor to see the effect of the mode value.
-- Read touch sensor in boolean mode until the orange button is pressed
function TouchRead(port)
nxt.InputSetType(port,1,0x20)
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
TouchRead(1)
-- Read touch sensor in transition count mode until the orange button is pressed
function TouchRead(port)
nxt.InputSetType(port,1,0x40)
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
TouchRead(1)
-- Read touch sensor in period count mode until the orange button is pressed
function TouchRead(port)
nxt.InputSetType(port,1,0x60)
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
TouchRead(1)
It's a simple matter to modify the above routine to write to the console only when a change of state is detected, and we'll add another parameter to the function that sets the threshold for detection.
-- Read touch sensor until the orange button is pressed - note that it's a
-- lot easier to simply read the sensor in boolean mode...
function TouchChange(port, thresh)
nxt.InputSetType(port,0)
local oldState,newState
repeat
if( thresh < nxt.InputGetStatus( port ) ) then
newState = 0
else
newState = 1
end
if newState ~= oldState then
print( nxt.TimerRead(), newState )
oldState = newState
end
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
TouchChange(1,500)
Note the use of local oldState, newState. This is done to make sure that these values are indeed local to the function and don't leave garbage in the global variable namespace.
The NXT low level firmware drivers that pbLua interfaces with already debounce the readings from the touch sensor. Normally you would have to take multiple readings and eliminate the contact bounce that comes with switches.
If you've been paying attention so far, you'll realize that the previous example is much more complex that simply using the boolean mode for the touch sensor.
The next sensor we'll look at is the sound sensor. It measures the sound pressure and has two sensitivity modes.
If you want normal sensitivity (the default) then set the device type to 8. For a higher sensitivity set the device type to 7. The second example describes how to do this.
The following sample code works just like the touch sensor example, and spits out sensor readings continuously until the orange button on the NXT is pressed.
-- Read sound sensor until the orange button is pressed
function SoundRead(port)
nxt.InputSetType(port,0,7)
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
SoundRead(2)
Note that the test function uses port 2, so be sure to connect your sound sensor to the correct port.
You'll see that the values tend to buzz by very quickly, about 500 samples per second, and that the raw numbers go down as the volume goes up. Let's use some Lua code to process the readings using the exact same algorithm as the standard NXT firmware uses.
The following C code is extracted out of the c_input.c file from the LEGO MINDSTORMS NXT Open Source Distribution:
#define VCC_SENSOR 5000L #define AD_MAX 1023L #define NEWSOUNDSENSORMIN (650L/(VCC_SENSOR/AD_MAX)) #define NEWSOUNDSENSORMAX ((AD_MAX * 4980L)/VCC_SENSOR) #define NEWSOUNDSENSORPCTDYN (UBYTE)(((NEWSOUNDSENSORMAX - NEWSOUNDSENSORMIN) * 100L)/AD_MAX)
There is a general function that converts the raw A/D readings of the sensor into a percentage of the full-scale range of the sensor. For the sound sensor, the formula turns out to be something like this:
For the case of the sound sensor, the mimimum value works out to 162, the constant works out to 83, the maximum works out to 1018, and the raw value is inversely proportional to the effect being measured. The following sample code processes the raw A/D readings and returns the result scaled to the full range of values.
Actually, it does a little more than that! If you read the code carefully you'll see that the SoundScale() function takes an additional parameter called sensitive. This additional parameter chooses the senitivity of the sound sensor and sets up the port accordingly.
Go ahead, run the program!
-- Read sound sensor and scale the results the hard way
function SoundScale(port,sensitive)
sensitive = sensitive or 0
if 0 == sensitive then nxt.InputSetType(port,8)
else nxt.InputSetType(port,7)
end
repeat
local raw = nxt.InputGetStatus(port)
-- Check if raw value is less than minimum, adjust if needed
if raw < 162 then raw = 0
else raw = raw - 162
end
-- Scale the result
raw = (raw*100)/83
-- Check if raw value is greater than maximum, adjust if needed
if raw > 1023 then raw = 1023
end
-- Invert the raw value
raw = 1023 - raw
-- Make a string that represents the volume...
print( nxt.TimerRead(), string.rep("*", raw/16 ) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
SoundScale(2,0)
Yes, it is cool. If everything works correctly, your console will show a rolling bar graph of the volume measured by the sound sensor, where the highest volume is 64 "*" characters. This is very useful for testing the range of values that you might want to process further.
Of course, you can also have the NXT do all that hard math for you automatically by simply using the percent full scale mode, like this:
-- Read sound sensor and scale the results the easy way
function SoundScale(port,sensitive)
sensitive = sensitive or 0
if 0 == sensitive then nxt.InputSetType(port,8,0x80)
else nxt.InputSetType(port,7,0x80)
end
repeat
local _,_,_,raw = nxt.InputGetStatus(port)
print( nxt.TimerRead(), string.rep("*", raw/2) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
SoundScale(2,0)
A few ideas for further development might be:
You can see that this is a pretty cool sensor, and that there a lot of you possibilities for integrating it into robots using the high speed processing capabilities of pbLua.
The last of the basic sensors that ships with the NXT 2.0 set is the color sensor. This replaces the light sensor from the NXT 1.0 kit and is a very versatile piece of equipment. You can use it in incative mode, as a 3 color lamp (read,green,blue) and as a full colour sensor that returns fixed values for the Zamor spheres in the NXT 2.0 kit.
The following examples will use port 3 for the color sensor...
The sensor can be used as a red lamp like this:
-- The color sensor turns on steady red...
function RedSensor(port)
nxt.InputSetType(port,14)
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
RedSensor(3)
Or a green lamp:
-- The color sensor turns on steady green...
function GreenSensor(port)
nxt.InputSetType(port,15)
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
GreenSensor(3)
Or a blue lamp:
-- The color sensor turns on steady blue...
function BlueSensor(port)
nxt.InputSetType(port,16)
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
BlueSensor(3)
In each of those cases, the sensor turns its internal light source to the specified color and returns values that seem to represent the reflected light of that color. For example, if you're using the sensor in "RED" mode, both white and red objects will appear to reflect a lot of light, while objects that are blue, green, or black will reflect very little.
You can also use the sensor as a full color device, and the value will tell you if the object is one of the following colors - black, blue, green, yellow, red, white. You'll notice that the result of the next code snippet prints 8 values, not 4. That's because in full color mode, you also get the raw red, green, blue, and ambient values (in that order) after the overall processed sensor value.
-- The color sensor is an active full color device - must use pct mode
function FullSensor(port)
nxt.InputSetType(port,13,0x80)
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
FullSensor(3)
The last of the basic sensors that ships with the NXT 1.0 set is the light sensor. It operates in a two different modes, active, and inactive.
In inactive mode, the sensor type is 6. This type is used when you are reading ambient light conditions and you don't need to provide your own light source.
In active mode, the sensor type is 5. This type is used when you are trying to detecct how light or dark something is and you need to provide a stable light source. When using this mode, you may need to find a way to filter out the ambient light using some math, which we'll get into later.
If you're paying attention and have read this far, you can probably guess that a code sample is coming, and that the light sensor is plugged into port 3. The following code samples the light sensor and dumps the timestamp and sensor value out to the console at the rate of about 500 samples per second.
-- Read light sensor until the orange button is pressed
function LightRead(port,active)
active = active or 0
if 0 == active then nxt.InputSetType(port,6,0x80)
else nxt.InputSetType(port,5,0x80)
end
repeat
print( nxt.TimerRead(), nxt.InputGetStatus(port) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
LightRead(3,0)
We can get a bit more interstig results in the next step, which is basically making the light sensor handle transitions between black and white stripes on a piece of paper, which is the basic building block of a bar code reader.
If we have a single threshold and set all values above that level as black and all values below the level as white, we'll run into trouble when the sensor is in the "grey" area at the threshold. The result will be a mess of transitions between white and black.
To get around this problem, the concept of hysteresis has been used for years. Think of it as increasing the size of the gray area so that we can be sure that we're outside it.
The sample program takes a threshold and a guard value. Values that are greater that the threshold plus the guard are black, and values that are less than the threshold minus the guard are white. The program will print out the new state and the time of each transition.
To determine the theshold and guard parameters, run the LightRead() function, move the sensor over the white and black areas of the barcode, and note the results.
Take the average of the white and black values and use the average as the threshold and try different guard values to see which one gives better results. If the guard is too large, you'll end up with unreliable width measurement, and if it's too small you'll get unreliable edge detection.
-- Read light sensor until the orange button is pressed
function Barcode(port,active,threshold,guard)
nxt.InputSetType(port,0)
active = active or 0
if 0 == active then nxt.InputSetState(port,0,0)
else nxt.InputSetState(port,1,0)
end
nxt.InputSetDir(port,1,1)
local oldState = 1
local newState = 1
local light
repeat
light = nxt.InputGetStatus(port)
if light > (threshold + guard) then newState = 0 end
if light < (threshold + guard) then newState = 1 end
if newState ~= oldState then
print( nxt.TimerRead(), newState )
oldState = newState
end
until( 8 == nxt.ButtonRead() )
end
-- And here's how to call the function. You may need to try this
-- with different values for the threshold and guard
Barcode(3,1,700,20)
This is not a complete implementation of a barcode reader. Other issues that need to be resolved are:
The barcode sample code can also be used as the core of a line follower that uses one side of the line as a guide. Experiment with it and I'm sure you'll be able to come up with all kinds of ideas.
The basic sensors provided in the LEGO NXT kit are really useful for all kinds of applications. The pbLua language makes it very easy to interface with the sensors, and to build on simple concepts to design robust models.
The next tutorial is for the more advanced I2C based sensors that can measure and report multiple signals on one digital interface.