headerImage

Contents

Introduction

This article is all about reading and writing arbitrary data using the IR port on the RCX. Some of the applications of this idea include reading the LEGO remote control and sending messages between RCX bricks. We will start with a simple application that decodes binary data from the IR tower, and then move on to decoding data from the remote control.

I'm assuming that you have at least a basic grasp of programming in pbForth. You should also know how to upload scripts to the RCX. You might also review pbForth RCX Extension Scripts if you're unfamiliar with pbForth on the RCX.

To get the most up to date version of pbForth firmware and the example scripts, you can get the pbForth Scripts as a ziparchive or the pbForth Scripts as a tar.gzarchive. The scripts you'll need are in the pbScripts/rcxRemote directory.

Reading The IR Port

First, get the latest pbForth firmware loaded into the RCX. Once you have the firmware loaded we can move to the next step which is figuring out how the sensors work. Once again, the power of pbForth is apparent in the ease with which we can set up very simple test harnesses to evaluate programming concepts.

The basic RCX IR protocol runs at 2400 buad, 8 data bits, 1 stop bit and odd parity. Previous versions of pbForth used even parity, but it turns out that USB tower is stuck at odd parity, so now pbForth uses it too. I've excerpted the following from a note by Dave Baum...

At the packet level, all packets look like this:
    
0x55 0xff 0x00 D1 ~D1 D2 ~D2 ... Dn ~Dn C ~C

where D1...Dn are the message body, and C = D1 + D2 + ... Dn.

The data for sending an IR message is F7 followed by the 8 bit "message".

For example:

55 ff 00 f7 08 12 ed 09 f6

is a packet sending the message "0x12" to the RCX

This is pretty easy to understand, but how do we work this into the RCX using pbForth. Well, let's start with the basics of reliable communication protocols - state machines.

State machines are a simple way to specify the operation of a protocol. From the example message, the first thing we need to be able to identify is the start of a message, so let's work on just that for now. The following program waits for the 55 FF 00 portion of the message and beeps when it finds it. It will wait for 5 seconds and then timeout if the packet as not been found.

There are a few things to watch out for in the sample program. First, note that the pbForth word KEY returns the value from the IR port with the high bit stripped off. This makes it useless for binary data. Also, KEY is blocking which means it waits for a key until it returns. This makes it impossible to do anything else while we're waiting for IR data. Fortunately, there are two words that help us get around the limitiations of KEY.
EKEY Returns the raw binary data from the IR port. Only returns when a character has been received with no errors
EKEY? Returns 0 if there are no valid characters waiting, and non-0 if there is a valid character waiting. Use EKEY to actually get the character

Another thing to watch out for is the control structures. The list of IFs in a row is just another way of arranging a case statement. Note that if the character we are expecting next is not received, then we can simply knock the state of the packet receiver back to 0.

To test this for yourself, load the script into the RCX and enter TEST into the console. If your remote does not send the right data within 5 seconds, you'll hear a low buzz tone. If your remote is working, you'll get a high pitched tone.

\ -----------------------------------------------------------------------------
\ remote1.txt - looks for packet headers in the IR data stream
\ -----------------------------------------------------------------------------
\ Revision History
\
\ R. Hempel   2002-07-06 - Original
\ -----------------------------------------------------------------------------
\ Advances the headrer state one level for every character match
\
\ headerState Event            Action
\      0      waiting for 55   set headerState to 1 
\      1      waiting for FF   set headerState to 2
\      2      waiting for 00   set headerState to 3
\
\ Returns 0 if no packet has been found yet, non-0 if a packet is completed
\ -----------------------------------------------------------------------------

BASE @
HEX

0 VALUE headerState

: getHeader ( -- f )
  EKEY?                                       \ Check for IR character
  IF   EKEY                                   \ If there is one, get it 

    headerState 0 =
    IF   55 = IF   1 TO headerState
              ELSE 0 TO headerState
              THEN
    ELSE

    headerState 1 =
    IF   FF = IF   2 TO headerState
              ELSE 0 TO headerState
              THEN
    ELSE

    headerState 2 =
    IF    0 = IF   3 TO headerState
              ELSE 0 TO headerState
              THEN
              
    THEN THEN THEN
    
  THEN
   
  3 headerState = ; 
  
DECIMAL
  
: TEST
  500 0 timer_SET  
  0 TO headerState
  
  BEGIN getHeader 0 timer_GET  0= OR
  UNTIL
  
  3 headerState = IF 400 ELSE 100 THEN 10 SWAP SOUND_TONE ;

BASE !

\ -----------------------------------------------------------------------------

Decoding RCX Message Packets

The next step is, of course, to decode the rest of the packets, not just the headers. The general description of the message in the previous section is accurate - but not very specific. For a complete description of all of the messages is in Kekoa's Opcode reference.

I won't get into the specifics of decoding all of the packets, but we'll just do a quick and dirty decode of the LEGO Remote Protocol. It is best described in this message on the LUGNET Communitydiscussion forums. All of the messages from the remote look exactly the same, they just indicate which switches are pressed according to this table:
CodeKey
0x0001REMOTE_MSG_1
0x0002REMOTE_MSG_1
0x0004REMOTE_MSG_2
0x0001REMOTE_MSG_3
0x0008REMOTE_A_FWD
0x0010REMOTE_B_FWD
0x0020REMOTE_C_FWD
0x0040REMOTE_A_REV
0x0080REMOTE_B_REV
0x0100REMOTE_C_REV
0x0200REMOTE_PRG_1
0x0400REMOTE_PRG_2
0x0800REMOTE_PRG_3
0x1000REMOTE_PRG_4
0x2000REMOTE_PRG_5
0x4000REMOTE_STOP
0x8000REMOTE_BIP

To check out the operation, just load the following script into the RCX and enter TEST at the console. Aim the remote at the RCX and press a key. If you hear a high tone, everything went fine. To print out the bytes that were received, just enter - note that the "." is part of the word. In Forth the "." implies that something is getting printed.

\ -----------------------------------------------------------------------------
\ remote2.txt - looks for complemented bytes in the IR stream
\ -----------------------------------------------------------------------------
\ Revision History
\
\ R. Hempel   2002-07-06 - Original
\ -----------------------------------------------------------------------------
\ Advances the byte state one level for every character match
\
\ byteState Event               Action
\      0    waiting for byte    set byteState to 1 
\      1    waiting for ~byte   set byteState to 2
\
\ Returns 0 if no byte has been found yet, non-0 if a byte is completed
\ -----------------------------------------------------------------------------

BASE @
HEX

0 VALUE byteState
0 VALUE byte

CREATE irMessage 8 ALLOT

: getByte
  EKEY?                                       \ Check for IR character
  IF   EKEY                                   \ If there is one, get it 

    byteState 0 =
    IF   TO byte   1 TO byteState
    ELSE
    
    byteState 1 =
    IF   byte INVERT FF AND =
         IF   2 TO byteState
         ELSE 0 TO byteState
         THEN
                         
    THEN THEN

  THEN
  
  2 byteState = ; 
  
DECIMAL
  
: TEST
  500 0 timer_SET
    
  0 TO headerState
  BEGIN getHeader 0 timer_GET  0= OR
  UNTIL 

  4 0 DO
    0 TO byteState
    BEGIN getByte 0 timer_GET  0= OR
    UNTIL byte irMessage I + C!
  LOOP

  2 byteState = IF 400 ELSE 100 THEN 10 SWAP SOUND_TONE ;

: irMessage. 4 0 DO irMessage I + C@ U. LOOP ;

BASE !

\ -----------------------------------------------------------------------------

Writing The IR Port

Last but not least, we need to figure out some way of writing to the IR port. This is actually pretty easy to do because the EMIT word does everyting we need - it even blocks on writes until the character is actually in the TX buffer.

The example script is straightforward. It has a helper word to send out the header and the bytes in complemented form. In an actual application, you'll probably want to send messsages out of a buffer, but this gets the point across. You can use this script to nuke an opposing RCX that is running standard firmware by carefully choosing which buttons you are simulating :-)

The script takes advantage of the fact that EMIT can only send the least significant byte of a word.

\ -----------------------------------------------------------------------------
\ remote3.txt - Sends a message out the IR port that simulates the remote
\ -----------------------------------------------------------------------------
\ Revision History
\
\ R. Hempel   2002-07-06 - Original
\ -----------------------------------------------------------------------------

BASE @
HEX

: sendHeader
  55 EMIT FF EMIT 00 EMIT ;

: sendByte ( c -- )
  DUP EMIT INVERT EMIT ;
  
\ The sendRemote word accepts a word that matches the button status
\ of the remote. See the rcxRemote script tutorial at:
\
\ 

: sendRemote ( n -- )
  sendHeader
  D2 sendByte 
  DUP 8 RSHIFT 2DUP sendByte sendByte
  + D2 + sendByte ;

BASE !

\ -----------------------------------------------------------------------------

Hopefully this little tutorial has given you some ideas that you can use on your own robot creations. The IR port is an important part of the RCX comnmunications system, and using pbForth can help you get more functionality than just the standard firmware.