Introduction
This tutorial describes how to use the I2C capabilities of the 4 I/O ports on the NXT. I2C is an industry standard digital communications protocol that can be used over short distances. There are a number of chips with I2C capabilities that can be used in custom sensors, but that’s another tutorial.
This tutorial will discuss the pbLua I2C API and how to use it in combination with some standard string manipulation functions that are part of the Lua language.
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 ultrasound sensor and write simple functions that use the resulting data.
I2C Basics
Before using a sensor with the I2C API it’s a good idea to familiarize ourselves with how the I2C protocol works. I2C is known as a “two wire” protocol because there is one clock line and one data line. The data line is bidirectional, meaning it can be used to send data to and receive data from the device connected to the bus.
A bus is a group of electrical signals that can be shared between multiple devices, and the I2C protocol allows for this. By sending specific bits of data, individual devices can be selected for communication. The NXT I2C port has very strict power limitations, so only one sensor can be plugged into a port at a time.
An I2C transaction has an send phase, where the host (in this case the NXT) sends data to the device, and an optional reply phase where the device sends data back to the host. The host always has control of the clock signal.
There are only four functions in the NXT I2C API:
- nxt.I2CInitPins()
- nxt.I2CGetStatus()
- nxt.I2CSendData()
- nxt.I2CRecvData()
You’ll see that using these four functions in combination with some basic string functions to build up arbitrary byte sequences is not that hard.
Setting up the I2C Port
The first thing we’ll need to do is determine the port number that the sensor is plugged into. The port number runs from 1 to 4 and is used in each and every API call. To get the I2C port set up for communications, it’s a simple matter of calling the function to initialize the pins, like this:
-- Initialize port 4 for I2C communication nxt.I2CInitPins(4) -- Use a variable to hold the port number port = 4 nxt.I2CInitPins(port)
Sending and Receiving Data
In the discussion on I2C basics, you learned that an I2C message has two phases. The host sends data to the device in the first part and the device may send data back to the host in the second.
The nxt.I2CSendData() function call handles this by automatically figuring out the length of the string you are sending. The following sample code shows a function to set up a port to use the I2C protocol, and then sets up the three basic strings required to get basic information out of the I2C port.
-- setupI2C() - sets up the specified port to handle an I2C sensor function setupI2C(port) nxt.InputSetType(port,2) nxt.InputSetDir(port,1,1) nxt.InputSetState(port,1,1) nxt.I2CInitPins(port) end -- Now put the standard I2C messages into the nxt table nxt.I2Cversion = string.char( 0x02, 0x00 ) nxt.I2Cproduct = string.char( 0x02, 0x08 ) nxt.I2Ctype = string.char( 0x02, 0x10 ) nxt.I2Ccontinuous = string.char( 0x02, 0x41, 0x02 ) nxt.I2Cdata = string.char( 0x02, 0x42 )
The string.char() is part of the standard Lua string library and will assemble strings from individual bytes. Rather than generate the strings each and every time we need them, we’ll just do it once and save the results in the nxt array for later.
Now we need to use what we’ve learned to send the strings to the I2C sensor, like this:
-- Use the ultrasound sensor in port 4, make sure the -- previous sample has been loaded setupI2C(4) -- Now send the product string, up to 8 bytes can be sent by the sensor nxt.I2CSendData( 4, nxt.I2Cproduct, 8 )
That’s pretty easy to do, and reading is simple as well, as long as the entire transaction has completed. Since we’re doing everything from the command line, there’s plenty of time for the transaction to complete and we can read (and print) the resulting string like this:
-- Reading the data is simple. Make sure the previous example has been -- loaded. The = notation is a short form for "print" =nxt.I2CRecvData(4)
Checking the I2C Status
Sending and receiving I2C data from the console is straightforward, but if we’re doing things in a program, then it gets a bit more complicated. That’s because the I2C transaction may not have completed by the time we’re ready to read the results.
The I2CGetStatus() function helps us figure out what the state of the I2C port is. It returns three values. The first is the overall state of the I2C port state machine, the second is the transmit phase state, and the third is the receive phase state.
When the I2C transaction has either completed or timed out, the state will be first value returned by I2CGetStatus() will be 0, so the busy-wait function for the I2C port looks like this:
-- waitI2C() - sits in a tight loop until the I2C system goes idle function waitI2C( port ) while( 0 ~= nxt.I2CGetStatus( port ) ) do end end
In the previous example, you might have noticed a new Lua operator that looks like “..” – it’s just the string concatenation operator which is handy because you don’t need to read the data into a separate string.
-- Put it all together in a function that prints out a report of which -- sensor is connected to a port function checkI2C(port) setupI2C(port) nxt.I2CSendData( port, nxt.I2Cversion, 8 ) waitI2C( port ) print( "Version -> " .. nxt.I2CRecvData( port, 8 ) ) nxt.I2CSendData( port, nxt.I2Cproduct, 8 ) waitI2C( port ) print( "Product ID -> " .. nxt.I2CRecvData( port, 8 ) ) nxt.I2CSendData( port, nxt.I2Ctype, 8 ) waitI2C( port ) print( "SensorType -> " .. nxt.I2CRecvData( port, 8 ) ) end -- And now run the function to see if it recognizes the sensor checkI2C(4) -- Results are below, do not paste the following text to the console! Version -> V1.0 Product ID -> LEGO SensorType -> Sonar
Reading Data
If this all looks straightforward to you, then congratulations! It can be a bit intimidating to deal with all of this at once, but if you take it step by step, then it’s much easier.
If you’ve done the previous tutorial on the basic sensors, then you’ll be familiar with the example of continuously reading data until the orange button on the NXT is pressed, so here it is:
function I2CRead(port)
-- Set up the port and report on the sensor type
checkI2C(port)
-- Put it into continuous sample mode
nxt.I2CSendData( port, nxt.I2Ccontinuous, 0 )
waitI2C( port )
-- Read and print the results until the orange button is pressed
repeat
-- Read 8 bytes of data from the sensor
nxt.I2CSendData( port, nxt.I2Cdata, 8 )
waitI2C( port )
s = nxt.I2CRecvData( port, 8 )
-- Break the resulting string into 8 bytes and print the results
c1,c2,c3,c4,c5,c6,c7,c8 = string.byte(s,1,8)
print( string.format( "Result: %3i %3i %3i %3i %3i %3i %3i %3i",
c1, c2, c3, c4, c5, c6, c7, c8 ) )
until( 8 == nxt.ButtonRead() )
end
-- And using the function - press the orange button on the NXT to stop it
I2CRead(4)
The Lua string.byte() function breaks the string into individual bytes and returns them. Note how useful Lua’s the multi-value return is in this case.
The Lua string.format() function works exactly like the standard C runtime sprintf() function. In fact, it’s simply a wrapper for that function!
When you run the I2CRead() function with the Sonar sensor you’ll no doubt notice that the readings whiz by very quickly, and that every other reading is zero. That’s because we’re reading the sensor much more quickly than possible or necessary.
We can fix this by waiting a certain number of milliseconds between readings, but this makes the NXT unable to do anything else while we’re waiting for data. There is a better way to handle this using the cooperative multitasking functions that are part of the Lua core.
But before we do that, we’ll need one more basic tutorial on using the motors.