Important

When I first wrote this tutorial, the RFIF 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

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,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 = { [2] = string.char( 0x02, 0x00 ),
                   [4] = string.char( 0x04, 0x00 ) }

nxt.I2Cproduct = { [2] = string.char( 0x02, 0x08 ),
                   [4] = string.char( 0x04, 0x08 ) }

nxt.I2Ctype    = { [2] = string.char( 0x02, 0x10 ),
                   [4] = string.char( 0x04, 0x10 ) }

nxt.I2Cdata    = { [2] = string.char( 0x02, 0x42 ),
                   [4] = string.char( 0x04, 0x42 ) }

-- waitI2C() - sits in a tight loop until the I2C system goes idle

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

function checkI2C(port, address)
  setupI2C(port)

  nxt.I2CSendData( port, nxt.I2Cversion[address], 8 )
  waitI2C( port )
  print( "Version    -> " .. nxt.I2CRecvData( port, 8 ) )

  nxt.I2CSendData( port, nxt.I2Cproduct[address], 8 )
  waitI2C( port )
  print( "Product ID -> " .. nxt.I2CRecvData( port, 8 ) )

  nxt.I2CSendData( port, nxt.I2Ctype[address], 8 )
  waitI2C( port )
  print( "SensorType -> " .. nxt.I2CRecvData( port, 8 ) )
end

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:

RFID Tag labels

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 eaxmple, 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:

  1. We can index a table by an arbitrary string value
  2. We can compile a string as a function
  3. We can assign a function to a table value
  4. 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!