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 zip archive or the pbForth Scripts as a tar.gz archive. The scripts you'll need are in the rcxScripts/rcxRemote directory.
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 !
\ -----------------------------------------------------------------------------
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 Community discussion forums. All of the messages from the remote look exactly the same, they just indicate which switches are pressed according to this table:
| Code | Key |
| 0x0001 | REMOTE_MSG_1 |
| 0x0002 | REMOTE_MSG_1 |
| 0x0004 | REMOTE_MSG_2 |
| 0x0001 | REMOTE_MSG_3 |
| 0x0008 | REMOTE_A_FWD |
| 0x0010 | REMOTE_B_FWD |
| 0x0020 | REMOTE_C_FWD |
| 0x0040 | REMOTE_A_REV |
| 0x0080 | REMOTE_B_REV |
| 0x0100 | REMOTE_C_REV |
| 0x0200 | REMOTE_PRG_1 |
| 0x0400 | REMOTE_PRG_2 |
| 0x0800 | REMOTE_PRG_3 |
| 0x1000 | REMOTE_PRG_4 |
| 0x2000 | REMOTE_PRG_5 |
| 0x4000 | REMOTE_STOP |
| 0x8000 | REMOTE_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 !
\ -----------------------------------------------------------------------------
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: \ \ <http://www.hempeldesigngroup.com/lego/pbForth/scripts/howtoIRData.html> : 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.