Controlling many Scouts with one RCX.

by Steve Baker
The intention here is to allow a number of Lego 'Scout' computers to act a slaves to a single Lego 'RCX' - the scouts will all run the same program - with one variation - a single constant that gives each one a unique identifier. There is no particular reason for the slave computers to have to be Scouts - they could be other RCX's - but Scouts are much cheaper and not much use for anything else.

The program that runs in the Scouts allows motors, light and sounds to be controlled remotely - and produces sensor returns from the two sensor inputs and the built-in light sensor. All of the 'intelligence' in the robot will run in the RCX.

The code at the RCX end is very simple - and complete programs to drive the scouts is provided below.

Protocols.

Since IR commands from the RCX will be 'seen' by all of the Scouts - and there is a good chance that one Scout may see the output from another Scout, some care has to be taken to "Address" each message to a particular computer.

A message consists of only a single byte (8 bits):


   7   6   5   4   3   2   1   0 
  [IDENT]  [        DATA       ]

The top two bits is the destination identifier, the bottom 6 bits remains for data.

This permits up to four computers to be addressed. One RCX and up to three Scouts. The RCX is computer ZERO.

The data part of the message has different meanings for Scout and RCX.

SCOUT-to-RCX.


  7   6   5   4   3   2   1   0
  0   0   X   X  S3H S3L S2  S1

Since the scout can only read touch-sensors (switches), we only need one bit for each of SENSOR_1 and SENSOR_2 S3H and S3L are the SENSOR_3 return - which can be:

 S3H S3L
  0   1   -  Low
  1   0   -  Medium
  1   1   -  High

The value will never be zero because that could result in message 0x00 being sent to the RCX - which is "discouraged".

Bits 4 and 5 are unused - don't rely on them being Zero.

To avoid two scouts transmitting at the same instant and corrupting each others' data - thus confusing the RCX, they obey the "Only speak when you are spoken to" rule. Hence the RCX must talk to each Scout in turn in order to get data back from them.

RCX-to-SCOUT.

There are many things within the Scout that can be controlled from the RCX - motors, sound and light.

Bits 4/5 of the data therefore contains a command code that is:


   0   0  -  Command the Scout's motors.
   0   1  -  Play a sound.
   1   0  -  Set Motor Power.
   1   1  -  Set Light threshold, etc.

Motor Command:

Each motor has a 2 bit command code

     0   0    -  Off.
     0   1    -  OnFwd.
     1   0    -  OnRev.
     1   1    -  Float.

OUT_A is bits 0 and 1, OUT_B is bits 2 and 3.

Sound Command.

To Be Decided.

Set Motor Power.

The power level for OUT_A is packed into bits 0,1,2 the level for OUT_B is packed into bits 3,4,5.

Set Lights and Light Threshold.

To Be Decided.

The Software

In the RCX, you start with this software....

Master Computer

The NQC program run by RCX master computer is:

/*
  Compile using:

   nqc -S/dev/{port} -TRCX2 rcx.nqc

   Where:  {port} - Whichever serial port you use.
*/

#define TIMEOUT   50   /* 500ms */

int sensor      [ 9 ] ;
int motor       [ 6 ] ;
int motor_power [ 6 ] ;

task scout_comms ()
{
  int i ;
  int msg ;

  while ( true )
  {
    for ( i = 0 ; i < 3 ; i++ )
    {
      while ( true )
      {
        ClearMessage () ;
        SetTimer ( 0, 0 ) ;
        SendMessage ( (i+1) * 64 +
                      0 * 16 +
                      motor[i*2] +
                      motor[i*2+1] * 4 ) ;
        do
        {
          msg = Message () ;
        } while ( FastTimer ( 0 ) < TIMEOUT && msg == 0 ) ;

        if ( msg != 0 )
          break ;

        PlaySound ( SOUND_DOUBLE_BEEP ) ;
      }

      sensor[i*3  ] =    msg   & 0x01 ;
      sensor[i*3+1] =  (msg/2) & 0x01 ;
      sensor[i*3+2] = ((msg/4) & 0x03) - 1 ;
    }
  }
}


task main ()
{
  int i ;

  for ( i = 0 ; i < 3 ; i++ )
  {
    sensor [ i*3 + 0 ] = 0 ;
    sensor [ i*3 + 1 ] = 0 ;
    sensor [ i*3 + 2 ] = 0 ;
    motor  [ i*2 + 0 ] = 0 ;
    motor  [ i*2 + 1 ] = 0 ;
  }

  start scout_comms ;

  Wait ( 100 ) ;

  /*
    Everything from here on down is your application code.
  */

  .....whatever.....
}

Then, in your application code, you can set the motors in the scouts running just by assigning to the 'motor' array which has six entries - corresponding to the six Scout motor outputs. Set the array entry to zero to stop the motor, 1 to run it forwards, 2 to run it backwards or 3 to 'float' it.

    motor [ 4 ] = 1 ;  /* Turn on motor number 4 */

Similarly, you can find the most recently known value of each of the Scout's sensors by reading the 'sensor' array which has nine entries.

Slave Computers

The NQC program run by each of the Scout slaves is:

/*
  Compile using:

   nqc -S/dev/{port} -TScout -DMY_IDENT={id} scout.nqc

   Where:  {port} - Whichever serial port you use.
           {id} - 1, 2 or 3 depending on which Scout you are setting up.
*/

task main ()
{
  ClearMessage () ;

  while ( true )
  {
    int msg, bits ;

    msg = Message () ;

    if ( msg != 0 )
      ClearMessage () ;

    /*
      No need to check for message zero because bits would
      be zero - which won't match MY_IDENT
    */

    bits = (msg / 64) & 0x03 ;
    msg = msg & 0x3F ;

    if ( bits == MY_IDENT )
    {
      SendMessage ( SENSOR_1 +
                    SENSOR_2 * 2 +
                   (SENSOR_3+1) * 4 ) ;

      bits = msg / 16 ;

      /*
        0 0  -  Command the Scout's motors.
        0 1  -  Play a sound.
        1 0  -  Set Motor Power.
        1 1  -  Set Light threshold, etc.
      */

      switch ( bits )
      {
        case 0 :  /* Motor */
           /*
              0 0    -  Off.
              0 1    -  OnFwd.
              1 0    -  OnRev.
              1 1    -  Float.
           */

           bits = msg & 0x03 ;

           switch ( bits )
           {
             case 0 : Off   ( OUT_A ) ; break ;
             case 1 : OnFwd ( OUT_A ) ; break ;
             case 2 : OnRev ( OUT_A ) ; break ;
             case 3 : Float ( OUT_A ) ; break ;
           }

           bits = ( msg / 4 ) & 0x03 ;

	   switch ( bits )
	   {
	     case 0 : Off   ( OUT_B ) ; break ;
	     case 1 : OnFwd ( OUT_B ) ; break ;
	     case 2 : OnRev ( OUT_B ) ; break ;
	     case 3 : Float ( OUT_B ) ; break ;
	   }
          break ;

        case 1 :  /* Sound */
          break ;

        case 2 :  /* Motor Power */
          SetPower ( OUT_A, (msg & 0x03) * 2 ) ;
          SetPower ( OUT_B, (msg & 0x0C) / 2 ) ;
          break ;

        case 3 :  /* Light Threshold */
          break ;
      }
    }
  }
}

Notice that you have to compile and download for each slave computer in turn, changing 'MY_IDENT' to 1, 2 or 3 each time:

   nqc -S/dev/{port} -TScout -DMY_IDENT={id} scout.nqc


RETURN TO TOP
Steve Baker <steve@sjbaker.org>