Update

This tutorial has been updated to describe the new RC Train functionality that you can get with the HiTechnic IR Link. Thanks to John Hansen and Jason Railton and their work on interfacing NXC to the IRLink, I was able to add the same to pbLua.

Introduction

This tutorial describes how to use the HiTechnic IR Link with pbLua. The IR Link can be used to control the new PF style motors that are part of the standard Technic line now, as well as other LEGO remote control products such as the RC Trains, the big Technic Bulldozer, or even the RC Dino.

You can buy the IR Link directly from directly from HiTechnic or from the LEGO S@H Website.

By the end of this tutorial, you'll be able to use the HiTechnic IR Link in your own robotic creations using both Power Function and RC Train motors.

HiTechnic IR Link Basics

The HiTechnic IR Link 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.

You can also download a PDF with detailed information on the IR Link which will be really helpful as we continue this discussion.

To get started, plug your IRLink sensor into Port 1 and run the following example code to verify that you can communicate with the IRLink.

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

checkI2C(1,2)

-- Gives these results:

-- Version    -> ýV1.2
-- Product ID -> HiTechnc
-- SensorType -> IRLink

Simple Control Of Motor A

There is really not much to figuring out the protocol. The IR Link spec outlines the meaning of all of the bits, so it's just a matter of figuring out which bits to turn on in the 3 nibbles of the data stream.

The real magic happens in the nxt.EncodeIR() function. It's responsible for taking the three nibbles, calculating the checksum, and formatting the bit stream into a string suitable for sending to the IRLink sensor.

To save time in your programs, you can generate the strings for the IRLink ahead of time, or you generate them as needed if you'll be changing the contents often.

The example below is set up to simply turn MotorA on forward for as long as IRTest() is running, which is until you press the orange button on the NXT.

-- Set up the two strings that we'll use alternately. The only difference
-- is the toggle bit. It will send the combo-direct command on channel 0 to
-- turn OutputA onfwd and OutputB float. The mode byte is 2 for PF motors.
-- If the IR signal is lost, the motors will turn off.
--
-- Note: Output A is the RED connector, and make sure the IR receiver is
--       set to channel 0!

s0 = nxt.EncodeIR( 0x011, 2 )
s1 = nxt.EncodeIR( 0x811, 2 )

-- And this is the test function

function IRTest(port)
  -- Set up the port and report on the sensor type
  checkI2C(port,2)

  repeat
    nxt.I2CSendData( port, s0, 0 )
    waitI2C( port )
    nxt.I2CSendData( port, s1, 0 )
    waitI2C( port )
  until( 8 == nxt.ButtonRead() )
end

-- And using the function - press the orange button on the NXT to stop it
IRTest(1)

Try covering the IRLink to verify that the motor stops after about 2 seconds with no signal, then expose the IRLink to turn the motor on again.

Turn MotorA Fwd/Off/Rev

The next example just expands of the previous one. It will generate IRLink strings on the fly depending on which button is pressed. If you stop pressing the button, the motor stops immediately.

-- Set up strings for brake, fwd, rev for motor A

b0 = nxt.EncodeIR( 0x013, 2 )
b1 = nxt.EncodeIR( 0x813, 2 )

f0 = nxt.EncodeIR( 0x011, 2 )
f1 = nxt.EncodeIR( 0x811, 2 )

r0 = nxt.EncodeIR( 0x012, 2 )
r1 = nxt.EncodeIR( 0x812, 2 )

-- And this is the test function

function IRTest(port)
  -- Set up the port and report on the sensor type
  checkI2C(port,2)

  repeat
    if nxt.ButtonRead() == 2 then       -- Set up for FWD
      s0, s1 = f0, f1                   -- multi-assignment, cool!
    elseif nxt.ButtonRead() == 4 then   -- Set up for REV
      s0, s1 = r0, r1
    else                                -- Set up for BRAKE
      s0, s1 = b0, b1
    end

    nxt.I2CSendData( port, s0, 0 )
    waitI2C( port )
    nxt.I2CSendData( port, s1, 0 )
    waitI2C( port )
  until( 8 == nxt.ButtonRead() )
end

-- And using the function - press the orange button on the NXT to stop it
IRTest(1)

That's not too hard, is it?

Controlling the RC Train Motor

As easy as it was to get the Power Factor motors running with pbLua, the RC Train is even easier. You still need the nxt.EncodeIR() function, except this time we're using Mode 1 instead of Mode 2.

The data parameter is still decoded in nibbles as follows:

  1. RC Channel - ranges from 1 to 3
  2. Command - StepFWD (0) StepREV(1) Stop(2)
  3. Toggle - Alternate between 0 and any non-0 value

So, if you want to increase the forward speed of the RC Train on Channel 1, you send 0x100. To increase it again, the step must be interpreted as a new command, so set the toggle bit and send out 0x101.

This means you can gradually ramp your train speed up and down very easily. Or if you want to stop the train right now just send 0x120

Here's an example program that ramps the speed up every time you press the right arrow, down every time you press the left arrow, and stops the train when you press the grey button. To stop the program, press the orange button.

-- Set up strings with toggles for StepFWD, StepRev, Stop for channel 1 

StepFwd = { nxt.EncodeIR( 0x100, 1 ), nxt.EncodeIR( 0x101, 1 ) }
StepRev = { nxt.EncodeIR( 0x110, 1 ), nxt.EncodeIR( 0x111, 1 ) }
Stop    = { nxt.EncodeIR( 0x120, 1 ), nxt.EncodeIR( 0x121, 1 ) }

-- And this is the test function

function TrainTest(port)
  -- Set up the port and report on the sensor type
  checkI2C(port,2)

  local toggle = true
  local s = Stop[1]

  repeat
    if nxt.ButtonRead() == 2 then       -- Set up for FWD
      if toggle then s = StepFwd[1] else s = StepFwd[2] end
      toggle = not toggle
    elseif nxt.ButtonRead() == 4 then   -- Set up for REV
      if toggle then s = StepRev[1] else s = StepRev[2] end
      toggle = not toggle
    elseif nxt.ButtonRead() == 1 then   -- Set up for Stop
      if toggle then s = Stop[1] else s = Stop[2] end
      toggle = not toggle
    end

    repeat
    -- spin here until the button is released!
    until( 0 == nxt.ButtonRead() )

    nxt.I2CSendData( port, s, 0 )
    waitI2C( port )

  until( 8 == nxt.ButtonRead() )
end

-- And using the function - press the orange button on the NXT to stop it
TrainTest(1)

How easy is that!?

Going Further

Now that you have some idea how it works with one IRLink try the following exercises to see if you can make the IRLink part of your everyday robotics toolkit: