At the forefront of Artificial Intelligence
  Home Articles Reviews Interviews JDK Glossary Features Discussion Search
Home » Articles » Robotics » LEGO Mindstorms

LEGO IR Protocol in C++

In an attempt to relieve a hardcore boredom attack, I thought I'd look into the IR protocol for LEGO Mindstorms and what I could achieve with it. I firstly set about looking for an easy way to access the serial port and came across PJ Naughter's CSerialPort class which encapsulated the SDK functions in a nifty C++ class. Armed with this, I set about trying to find information on the protocol and quickly found out that the IR transmitted using the following serial port settings:

2400 baud, NRZ, 1 start, 8 data, odd parity, 1 stop bit
While I'm not too sure what all of that meant, it was easy enough to set up CSerialPort to support it (I think). Next was to find how the IR transmitter actually sent and retrieved the data (protocol). I found that the IR protocol is fairly simple (from the RCX Internals site):
0x55 0xff 0x00 D1 ~D1 D2 ~D2 ... Dn ~Dn C ~C
The initial section (bolded) is the header for any packet transmission. Why these bytes? Well, 0x55 is 1010101 in binary, 0xff is 11111111 and 0x00 is obviously 00000000 meaning that there is the same number of ones and zeros. The middle portion of the data (red) are the opcodes you want to send to the RCX, followed by the one's complement of the opcode. The final two bytes are an 8-bit checksum and it's complement. If you were to code this, your code would probably look something like this:
void FormatOpcodes(const char *opcodes, char *opc, int len)
{
    // opcodes is an array of opcode bytes, opc is the array that will be sent 
    // to the RCX and len is the length of the opcodes. This is required, since 
    // 0x00 is part of the header.

    int sum = 0, i = 3;
	
    opc[0] = char(0x55);
    opc[1] = char(0xff);
    opc[2] = char(0x00);

    for (int j=0; j<len; j++) {
        opc[i++]   = opcodes[j];
        opc[i++]   = (~(opcodes[j])) & 0xff;
		
        sum += opcodes[j];
    }
	
    opc[i++]  = sum & 0xff;
    opc[i]    = (~sum) & 0xff;
}

The IR transmitter actually echos everything that it sends (thanks to Stef Mientki for that): this is so the LEGO software can check whether it is the LEGO IR transmitter that is connected to the serial port. Incidentally, the LEGO software also checks the IR transmitter by taking the RTS signal high then low and if the CTS signal follows it is assumed that it is the IR transmitter attached. This is because the RTS and CTS lines are wired together in the IR transmitter.

Retrieving the Battery Level

As an example, let's look at how to retrieve the battery level:
    // These opcodes makes the RCX return the battery level in millivolts.
    char buffer[7]  = { 0x55, 0xff, 0x00, 0x30, 0xcf, 0x30, 0xcf };
    
    m_cIRPort.Purge(PURGE_TXABORT | PURGE_RXABORT);

    m_cIRPort.Write(buffer, sizeof(buffer));
    m_cIRPort.Read(buffer, sizeof(buffer));

    m_cIRPort.Read(reply, 11);

    CString str = "", temp;
    for (int i=0; i<11; i++) {
        temp.Format("%.2Xh ",reply[i] & 0xff);

        str += temp;
    }

    int mv;
    mv =  (reply[7] & 0xff) * 256;
    mv += (reply[5] & 0xff);
	
    temp.Format("%d",mv);

    AfxMessageBox(str + "(" + temp + ")");
This code relies on prior knowledge of the return reply size, using it with other opcodes might lock your computer (or at least your program). Ok, so to step through the code. We first purge the serial port of all input and output bytes (this is just for safety). We then write the opcodes to the serial port, and read then back in again since the IR transmitter will have echoed it back.

We know that the RCX will return a reply that is 11 bytes long: 3 for the header, 2 for the query and opcode (complemented), another 4 for the battery level (two bytes, and complements) and another 2 for the checksum and it's complement. So, a typical reply would look like:

0x55 0xff 0x00 0xCF 0x30 (B1 ~B1  B2 ~B2) C ~C
Now a look at how to read the battery data. The bytes are backward, so we need to offset the B2 by two bytes. This is achieved by multipling by 256 (0xFF). We then add B1 and we have our battery voltage in millivolts!

Sending the Same Message

You cannot send the same message to the RCX, since the RCX never executes the same opcode twice in a row. Therefore, every opcode comes with an alternate opcode with the 3rd bit set (1000 in binary, 0x08 in decimal/hexadecimal). Therefore, if I wanted to check for the battery level again, I'd have to send another opcode set, with the 0x08 bit set and all the complements and checksums updated. Therefore, my second battery request would look like:
	char buffer2[7] = { 0x55, 0xff, 0x00, 0x38, 0xc7, 0x38, 0xc7 };
If you are sending messages again, you must reset the 0x08 bit again and start over. Note that for multiple opcode statements, it is only the first opcode (D1) that must be changed (thanks to Douglas Edric Stanley).

Why does the RCX not execute the same instruction twice? It is a safety feature that enables the software to easily recover if a transmission error occurs. For example, if your software transmits an opcode and receives a faulty reply, it should retransmit it. Therefore, the RCX will echo it's return for each similar instruction sequence it receives.

Conclusion

The hardest part of programming with the RCX is getting the serial port to work for you. After a bit more experimentation, I wrote CRipComm, a C++ class designed for Win32 (due to serial port functions) that handles all the serial communication and opcode formatting.

RCX Internals - Best site for RCX info. Has all the opcodes explained and IR protocol.
CSerialPort - Serial Port Wrapper - The class I used for the serial port before writing CRipComm.

Last Updated: 20/04/2001

Article content copyright © James Matthews, 2001.
 Article Toolbar
Print
BibTeX entry

Search

Latest News
- Generation5 10-year Anniversary (03/09/2008)
- New Generation5 Design! (09/04/2007)
- Happy New Year 2007 (02/01/2007)
- Where has Generation5 Gone?! (04/11/2005)
- NeuroEvolving Robotic Operatives (NERO) (25/06/2005)

What's New?
- Back-propagation using the Generation5 JDK (07/04/2008)
- Hough Transforms (02/01/2008)
- Kohonen-based Image Analysis using the Generation5 JDK (11/12/2007)
- Modelling Bacterium using the JDK (19/03/2007)
- Modelling Bacterium using the JDK (19/03/2007)


All content copyright © 1998-2007, Generation5 unless otherwise noted.
- Privacy Policy - Legal - Terms of Use -