Important
When I first wrote this tutorial, the RFID sensor I had was a pre-production version supplied by Codatex. A few readers have pointed out that the examples did not work with the sensors they had, and I suspected that the production version was slightly different.
Codatex then sent me a production version and – surprise – the sensor did not work with my tutorial example. Thanks to help from Steven Shapiro, another pbLua user, I was able to get the new sensor working properly.
This new version has been updated as of 16Jun2008 and now works correctly with the production sensors. Please let me know if you’re still having trouble.
Introduction
This tutorial describes how to use the Codatex RFID Sensor with pbLua. The RFID sensor is really useful in all kinds of applications. If your robot moves along a line, you can place the RFID tags to help locate waypoints along the path. If you put the tags into containers, you can identify them easily and take action on them.
By the end of this tutorial, you’ll be able to use the Codatex RFID sensor just as easily as any other I2C based sensor that interfaces with the Mindstorms NXT.
Contents
- Sensor Basics
- Codatex RFID Sensor Basics
- Single Read Then Go To Sleep
- Continuous Readings
- Using RFID Tags To Specify Actions
- Handling Unknown Table Values
- Finishing up The Example
- Conclusion
Codatex RFID Sensor Basics
The Codatex RFID Sensor is a third party sensor that connects to an I2C port on the bottom of the NXT. You may want to familiarize yourself with the pbLua I2C API and read over the pbLua I2C Tutorial for detailed examples of how it’s used.
To get started, plug your RFID sensor into Port 1 and run the following example code to verify that you can communicate with the IRLink. Note that this is slightly different than the first tutorial on I2C since we now need to send the basic commands to sensors at different adresses.
-- setupI2C() - sets up the specified port to handle an I2C sensor function setupI2C(port) nxt.InputSetType(port,11) nxt.I2CInitPins(port) end -- These are functions that return strings that can be used to -- access any register in any I2C device. All you need to pass -- in as parameters are the device address and the register you -- want to start reading from. -- function nxt.I2CReadString( devaddr, regaddr ) return string.char( devaddr, regaddr ) end -- Now we'll add 4 generic helpers to get strings from the standard -- registers for use with the LEGO MINDSTORMS NXT sensors -- function nxt.I2Cversion( devaddr ) return nxt.I2CReadString( devaddr, 0x00 ) end function nxt.I2Cproduct( devaddr ) return nxt.I2CReadString( devaddr, 0x08 ) end function nxt.I2Ctype( devaddr ) return nxt.I2CReadString( devaddr, 0x10 ) end function nxt.I2Cdata( devaddr ) return nxt.I2CReadString( devaddr, 0x42 ) end -- waitI2C() - sits in a tight loop until the I2C system goes idle, there -- are better ways to do this, but this is good enough for -- now function waitI2C( port ) while( 0 ~= nxt.I2CGetStatus( port ) ) do end end -- Put it all together in a function that prints out a report of which -- sensor is connected to a port -- -- Recall that nxt.I2CSendData() is capable of write/read operation. In -- other words, if you want to read 8 bytes of data starting at the -- ProductId field, just send the correct string and specify 8 bytes in -- the receive data parameter. All nxt.I2CRecvData() does is read the -- bytes out of the buffer that the nxt.I2CSendData() filled up! function checkI2C( port, devaddr ) setupI2C(port) nxt.I2CSendData( port, nxt.I2Cversion( devaddr ), 8 ) waitI2C( port ) print( "Version -> " .. nxt.I2CRecvData( port, 8 ) ) nxt.I2CSendData( port, nxt.I2Cproduct( devaddr ), 8 ) waitI2C( port ) print( "Product ID -> " .. nxt.I2CRecvData( port, 8 ) ) nxt.I2CSendData( port, nxt.I2Ctype( devaddr ), 8 ) waitI2C( port ) print( "SensorType -> " .. nxt.I2CRecvData( port, 8 ) ) end -- For an I2C device on port 1, all you need to do is: checkI2C(1,2)
And then verify that it’s working:
-- And use it - make sure you specify the port you have plugged the -- IR Link in to port 1 nxt.RFIDdummy = string.char( 0x04, 0x00, 0x00 ) nxt.RFIDboot = string.char( 0x04, 0x41, 0x81 ) nxt.RFIDstart = string.char( 0x04, 0x41, 0x83 ) nxt.RFIDstatus = string.char( 0x04, 32 ) setupI2C(1) nxt.I2CSendData( 1, nxt.RFIDboot, 0 ) nxt.I2CSendData( 1, nxt.RFIDstart, 0 ) nxt.I2CSendData( 1, nxt.RFIDdummy, 0 ) waitI2C( 1 ) checkI2C(1, 4) -- Gives these results: -- Version -> V1.0 -- Product ID -> CODATEX -- SensorType -> RFID
Single Read Then Go To Sleep
This example sets up the sensor for a single reading. If an RFID tag is read, then the sensor goes back to sleep immediately. If there is no RFID tag ready to be read, the sensor goes to sleep about 500 msec after the command is issued.
-- Turn on the RFID sensor and take a single reading, then print the result
nxt.RFIDsingle = string.char( 0x04, 0x41, 0x01 )
-- And this is the test function
function singleRFID(port,delay)
-- Do a single reading
nxt.I2CSendData( port, nxt.RFIDdummy, 0 )
waitI2C( port )
nxt.I2CSendData( port, nxt.RFIDsingle, 0 )
waitI2C( port )
-- wait delay msec ...
local t = nxt.TimerRead()
while t+delay > nxt.TimerRead() do
-- This space intentionally left blank!
end
nxt.I2CSendData( port, nxt.I2Cdata[4], 5 )
waitI2C( port )
local result = nxt.I2CRecvData( port, 5 )
-- Break the resulting string into 5 bytes and print them
local c1,c2,c3,c4,c5 = string.byte(result,1,5)
print( string.format( "Result: %02x %02x %02x %02x %02x",
c1, c2, c3, c4, c5 ) )
end
-- And using the function
singleRFID(1,200)
singleRFID(1,200)
singleRFID(1,200)
Currently, this function only works every other time, in other words you call the function twice to get a single good reading of an RFID tag. This function works best in cases where you need to save power by not reading continuously, and you know exactly when to read a tag.
Continuous Readings
The RFID sensor also has a mode where it reads tags continuously, and makes the results avaialble as they appear. Ater about 2 seconds of I2C inactivity (you have stopped polling for results) the sensor goes to sleep to conserve power.
The following code example is example sets up the sensor for continuous reads with a delay between polling the device for new data. If the result data strings are empty or the same between polls, then no results are printed.
-- Turn on the RFID sensor and take continuous readings, then print results
-- if consecutive readings are different and non-null...
nxt.RFIDcontinuous = string.char( 0x04, 0x41, 0x02 )
nxt.RFIDstatus = string.char( 0x04, 0x32 )
-- These strings represent bad RFID results
nullRFID = string.char( 0x00, 0x00, 0x00, 0x00, 0x00 )
-- And this is the test function
function contRFID(port,delay)
-- wake up the sensor
nxt.I2CSendData( port, nxt.RFIDdummy, 0 )
waitI2C( port )
local oldResult, result = "", ""
local status = 0
repeat
oldResult = result
-- Do continuous reading
nxt.I2CSendData( port, nxt.RFIDcontinuous, 0 )
waitI2C( port )
-- Check status, and wait if it's 0
nxt.I2CSendData( port, nxt.RFIDstatus, 1 )
waitI2C( port )
status = string.byte( nxt.I2CRecvData( port, 1 ) )
if 0 == status then
-- wait 250 msec
local t = nxt.TimerRead()
while t+250 > nxt.TimerRead() do
-- This space intentionally left blank!
end
end
-- wait delay msec before reading sensor
local t = nxt.TimerRead()
while t+delay > nxt.TimerRead() do
-- This space intentionally left blank!
end
nxt.I2CSendData( port, nxt.I2Cdata[4], 5 )
waitI2C( port )
result = nxt.I2CRecvData( port, 5 )
-- print results if valid and different from previous
if ((result ~= nullRFID) and (result ~= oldResult)) then
-- Break the resulting string into 5 bytes and print them
local c1,c2,c3,c4,c5 = string.byte(result,1,5)
print( string.format( "Result: %02x %02x %02x %02x %02x",
c1, c2, c3, c4, c5 ) )
end
until( 8 == nxt.ButtonRead() )
end
-- And using the function
contRFID(1,200)
And here are some typical results. Again, you may have to fiddle with the delay parameters to get good data for your application.
> contRFID(1,100) Result: 50 00 2a 18 b6 Result: 50 00 29 d8 3b Result: 50 00 2a 18 b6 Result: 04 16 1d 5f de
Using RFID Tags To Specify Actions
What we’re going to do next is make a very simple example of how to associate data with RFID tags. We’ll assume that you have at least two tags, and can identify which is which. Use the previous example to determine the “code” for each of your tags and then identify them somehow. I have used a pencil to mark my tags as 1 and 2 as shown here:
<%linkImage( "<%categoryImagePath%>tutorial/rfidTagLabels.png text=”RFID Tag labels text=” text=” text=0, 0, 0]
Once that’s done, it’s pretty easy to write a program to scan for tags, see if they are associated, and then do an action, and I’ll use a really neat trick that you can do with Lua to make it easy to associate actions with tags.
One of the coolest things about Lua is that tables can be indexed by anything – yes anything. Most languages let you index a table (or array) using only numerical indexes. In this example, we’ll use a string. And the string we’ll use is the result that scanning the tag returns.
Lua has an additional feature, and that is that access to tables through an invalid or non-existent index can do actions as well. To illustrate what I mean, I’ll associate a different tone with each tag, and I’ll associate a low tone with an unknown tag.
Here’s the code to get us started experimenting with sounds on the NXT…
-- Here are statements that you can run one at a time to sound tones:
nxt.SoundTone(440)
nxt.SoundTone(880)
nxt.SoundTone(110)
-- Here is a way to assign the tones to specific badges from the
-- previous examples. First start with a blank table:
badgeAction = {}
-- Next, index the table with a string and store compiled tone function:
badgeAction[ "low" ] =
loadstring( "nxt.SoundTone(440)" )
badgeAction[ "high" ] =
loadstring( "nxt.SoundTone(880)" )
-- And test the array one element at a time as a function:
badgeAction["low"]() -- should sound a low tone
badgeAction["high"]() -- should sound a high tone
Have a close, careful look at this and think about all the really interesting things we’ve just done with Lua:
- We can index a table by an arbitrary string value
- We can compile a string as a function
- We can assign a function to a table value
- We can execute a table value as a function
At this point you’re thinking that Lua may be unlike any language you’ve worked with before, and you’re right. But watch what happens next…
Handling Unknown Table Values
The application we’re about to write will sound different tones depending on which RFID tag we put in front of the sensor. One additional thing we’d like to do is sound a very low tone when we do not recognize the tag.
Going back to our previous example code, here’s what happens when we specify a non-existent table value:
-- What happens with a non-existent index? badgeAction["dummy"]() tty: stdin:1: attempt to call field 'dummy' (a nil value)
That’s pretty much what we expected to happen. How can Lua possibly figure out what to do when it’s asked to retrieve a non-existent value?
It turns out that Lua supports a very powerful concept called a metatable. In particular, we can override the operation of table accesses to non-existent values by specifycing an entry in the metatable for __index.
Maybe another example is in order…
-- Set up a metatable, it can have any name we want, but this is a consistent
-- style if the metatable is for one table only...
badgeAction.mt = {}
-- Now specify what to do when we index a non=existent value...
badgeAction.mt.f = loadstring( "nxt.SoundTone(110)" )
badgeAction.mt.__index = function() return badgeAction.mt.f end
And let’s test it:
-- And test it out... badgeAction["dummy"]() tty: stdin:1: attempt to call field 'dummy' (a nil value)
What happened – there’s no difference from the previous operation! The answer is that we have not yet assigned the metatable to the original table, so let’s do that and test it right away…
-- Assign the new metatable to the original table setmetatable(badgeAction, badgeAction.mt) -- And test it out... badgeAction["dummy"]()
That’s better! No matter what index we specify now for the badgeAction[] table, if it does not exist, we get a very low tone.
Finishing up The Example
OK, there’s not much left to now except to modify the continuous read example a bit. All we need to do is initialize the badgeAction[] table with the real badge strings, and then call the badgeAction[]() function every time we detect a new non-null badge.
Here’s what the code looks like:
-- Example code to sound tones when badges are recognized
--
-- First, enter table values for the badges we want associated with tones
badgeAction[ string.char( 0x50, 0x00, 0x2a, 0x18, 0xb6 ) ] =
loadstring( "nxt.SoundTone(440)" )
badgeAction[ string.char( 0x50, 0x00, 0x29, 0xd8, 0x3b ) ] =
loadstring( "nxt.SoundTone(880)" )
-- Make sure that you've set up the metatable from the previous example...
--
-- Then load up this new function.
function badgeTone(port,delay)
-- wake up the sensor
nxt.I2CSendData( port, nxt.RFIDdummy, 0 )
waitI2C( port )
local oldResult, result = "", ""
local status = 0
repeat
oldResult = result
-- Do continuous reading
nxt.I2CSendData( port, nxt.RFIDcontinuous, 0 )
waitI2C( port )
-- Check status, and wait if it's 0
nxt.I2CSendData( port, nxt.RFIDstatus, 1 )
waitI2C( port )
status = string.byte( nxt.I2CRecvData( port, 1 ) )
if 0 == status then
-- wait 250 msec
local t = nxt.TimerRead()
while t+250 > nxt.TimerRead() do
-- This space intentionally left blank!
end
end
-- wait delay msec before reading sensor
local t = nxt.TimerRead()
while t+delay > nxt.TimerRead() do
-- This space intentionally left blank!
end
nxt.I2CSendData( port, nxt.I2Cdata[4], 5 )
waitI2C( port )
result = nxt.I2CRecvData( port, 5 )
-- print results if valid and different from previous
if ((result ~= nullRFID) and (result ~= oldResult)) then
-- Break the resulting string into 5 bytes and print them
local c1,c2,c3,c4,c5 = string.byte(result,1,5)
print( string.format( "Result: %02x %02x %02x %02x %02x",
c1, c2, c3, c4, c5 ) )
-- And don't forget to sound the tone!
badgeAction[ result ]()
end
until( 8 == nxt.ButtonRead() )
end
-- And using the function
badgeTone(1,200)
Conclusion
The RFID sensor is dead-easy to use, and it’s not hard to think of really good examples of use. I’ll be posting a note about a NXT controlled train that you can send to get specific items from hoppers based on which RFID tag you show to it.
Keep on programming!