Introduction: Using NXT Components With a Micro Controller

This is a guide to using the motors and sensors from the LEGO NXT set with a Parallax Basic Stamp II or BS2.  However, rather than just providing schematics and sample code for the BS2, I'll be providing some theory on how the sensors work as well.  This way, someone using an arduino or other microcontroller should find this guide at least somewhat helpful.

My two main sources of information are
The LEGO Hardware Developer Kit  http://mindstorms.lego.com/en-us/support/files/default.aspx
TheCompBlog: Hacking the NXT http://www.thecompblog.com/2012/07/hacking-lego-nxt-part-1.html
-The latter is a decent guide if you're using an arduino.  But take note that his schematics often show pins 2 and 3 connected to +5V instead of ground.  Pins 2 and 3 are ground for all of the sensors except the button which is just a switch.

Have any suggestions?  Is something difficult to understand, incorrect, or missing?  Leave a comment and I'll see what I can do.  I would like this instructables to be a comprehensive guide on using the BS2 with the NXT set.  If you're using a different microcontroller and you're stuck, I may or may not be able to help but it doesn't hurt to ask.

I hope to get an arduino soon, and if this guide does well I will post a complimentary guide for using the arduino with sample codes for that.  Let me know if you'd like that.

Step 1: NXT Cables

First and foremost, we need to connect our NXT hardware to our microcontroller.  There are a few ways of going about this.
I opted for stripping a set of NXT cables I ordered from http://mindsensors.com.  I split up the wires, stripped them, soldered short lengths of solid-core wiring onto each, heatshrunk each solder joint, and heatshrunk the whole bundle of wires, creating a connector.
-In hindsight, you can buy one long wire and make two spliced wires out of it by cutting it in the middle.
-Also, make sure you keep your wires in order.  The NXT wires are color-coded but the mindsensors wires are not.
-Some people have made modified RJ-12 cables and others have just worked with the originals.
Another option is to buy a female jack from mindsensors which requires little to no messing around with a soldering iron and looks a lot cleaner.

Wires are numbered from the latch side right-to-left, so the wire nearest the latch is 1 and the wire farthest from the latch is 6.  
LEGO color codes them as follows:
1-White
2-Black
3-Red
4-Green
5-Yellow
6-Blue
(See image for reference)

Step 2: The Touch Sensor

Let's start with the touch sensor since it's the simplest part.  You can also use this to ensure you've got your wires in the right order.  Using the touch sensor is very simple: it's a normally open SPST switch, which basically means that it's "on" when you push the button and "off" when you release it.  Pin 1 is one terminal and Pins 2 and 3 are the other (use either or both).  If it's not working, you may have your wires backwards.  Try using Pin 6 for one terminal and Pins 4 and 5 for the other.

Step 3: The Motor

The NXT motor is a very nice piece of hardware and is also fairly easy to use. It features a 9V DC motor with a gearbox and a double encoder with a resolution of approximately 1 degree.

Pins 1 and 2 are the leads for the DC motor.  Apply a potential, and the motor turns.  Switch pins and the motor turns the other way.  It's that simple.  I use an H Bridge for this.

The encoder is almost as simple.  Pin 3 is Ground and Pin 4 is Vcc (5V).  These supply power to the encoder.  Pin 5 and Pin 6 are the two encoder outputs.  In case anyone is unfamiliar with double encoders, here's how they work:

Attached to the motor gearbox somewhere is an encoder wheel which has a bunch of radial slots in it.  It turns with the rest of the gearbox.  Two infrared sensors are placed such that the radial slots and spokes alternatively break and restore the IR beam, causing alternating 1's and 0's.  So, when the motor is turning, either line will read 101010101010 at a rate determined by the rate of the motor.  This is useful for speed and translation control.  The more 1's and 0's, the farther you've gone.  The faster it alternates, the faster you're going.
So why do we need two encoders?  For determining direction.  Because the two IR sensors are offset, they output a pattern that is specific to direction of rotation.  If the motor turns one way, they will output (Pin 5 Pin 6) 11, 10, 00, 01, repeat.  If the motor turns the other way, the pattern is reversed: 11, 01, 00, 10, repeat.  By watching the pattern, you can determine direction of rotation.  I'd get specific about clockwise and counterclockwise, but it depends on how you hold the motor and is really easy to figure out on your own through experimentation.

Coding the basic stamp to use the encoder:
To determine the speed of the motor, I use the count function.  Simply count the number of impulses from either of the encoder pins for a set period of time and you will get a number proportional to the speed of the motor.

COUNT Pin5, 100, speed

To wait for a certain number of cycles, I use a PULSIN command inside a for loop.  The PULSIN makes the BS2 wait for the next pulse from the encoder before proceeding to the next step of the loop.  This code will wait for n pulses.  The variable "variable" is not important, but could also be used to determine speed in theory.

FOR i = 1 to n
PULSIN Pin5, 1, variable
NEXT 

Determining direction is a little trickier.  I have the stamp wait until the encoder reaches a specific step in the cycle such as 11 by using an if statement in a loop.  Once the step has been reached, I check to see which step comes next (in this case, either 10 or 01) using a pair of if statements in another loop.  Depending on which step comes next, you can determine which way the motor is turning.

It is also possible to use this method to passively measure the absolute displacement of the motor.  Turning one direction will add to the displacement, while turning the other direction will subtract.  This way, if you start at 0, turn the motor a certain angle, and turn it back, you should go back to 0 rather than the distance covered.  This code checks direction once per cycle and then adds or subtracts 1 from displacement accordingly.  It works at moderate rotational speeds, but not at high speeds due to the stamp's clock speed.

pin5 PIN 1
pin6 PIN 0
disp VAR Word

loop1:
IF pin5=1 AND pin6=1 THEN direction   'wait for 11 encoder step
GOTO loop1

direction:
IF pin5=0 THEN cw                                     'See which pin goes low first to determine direction
IF pin6=0 THEN ccw
GOTO direction

cw:
disp=disp+1              'In this example, I made clockwise positive and counter-clockwise negative.
DEBUG ?disp
GOTO loop1


ccw:
disp=disp-1
DEBUG ?disp
GOTO loop1

I tried writing code to do this on a step-by-step basis so that instead of waiting for 11 to come around, it would check direction at each step for 4 times the accuracy.  Unfortunately, the stamp doesn't run fast enough for that.  A faster microcontroller might be able to handle it.  Here's the code, but again it only works at very low speeds.

First, the code establishes which step the encoder is starting on and directs to the subroutine corresponding to that step.  
Displacement starts at 1 due to an inherent fencepost error.
Each subroutine looks at what the previous step was.  If the previous step was CCW of this step then the motor turned CW and 1 is added.  Otherwise, CCW is default and 1 is subtracted.  (Fencepost error occurs here but isn't too important)
Next, the subroutine waits for the next step and then goes to the subroutine corresponding to that step.
It works very nice in theory but there are too many commands for each step so the BS2 can't handle it fast enough.

a PIN 0
b PIN 1
lasta VAR Bit
lastb VAR Bit
disp VAR Word
disp=1

INPUT a
INPUT b

lasta=a
lastb=b

IF a=1 THEN a1
IF b=1 THEN S01
GOTO S00

a1:
IF b=1 THEN S11
GOTO S10

S01:
disp=disp-1
IF lasta=1 AND lastb=1 THEN disp=disp+2
DEBUG ?disp
lasta=0
lastb=1
S01Loop:
IF a=1 AND b=1 THEN S11
IF a=0 AND b=0 THEN S00
GOTO S01Loop

S11:
disp=disp-1
IF lasta=1 AND lastb=0 THEN disp=disp+2
DEBUG ?disp
lasta=1
lastb=1
S11Loop:
IF a=1 AND b=0 THEN S10
IF a=0 AND b=1 THEN S01
GOTO S11Loop

S10:
disp=disp-1
IF lasta=0 AND lastb=0 THEN disp=disp+2
DEBUG ?disp
lasta=1
lastb=0
S10Loop:
IF a=1 AND b=1 THEN S11
IF a=0 AND b=0 THEN S00
GOTO S10Loop

S00:
disp=disp-1
IF lasta=0 AND lastb=1 THEN disp=disp+2
DEBUG ?disp
lasta=0
lastb=0
S00Loop:
IF a=1 AND b=0 THEN S10
IF a=0 AND b=1 THEN S01
GOTO S00Loop

Step 4: The Sound Sensor

The sound sensor is a little more interesting than the touch sensor.  It should be noted that this is a sound sensor and not a microphone: it is good at detecting loud noises, but not at outputting an analog audio signal.  
Wiring is as follows:
Pin 1 - Analog Current Output
--Connect  Pin 1 to ground using a 10k resistor
Pin 2 - Ground
Pin 3 - Ground
Pin 4 - Vcc (+5V)
Pin 5 - Not Used
Pin 6 - Not Used

You can read an analog output of sound intensity from Pin 1.  Increased volume intensity results in increased voltage.  Unfortunately, the BS2 isn't very good at analog stuff.  If you want an actual numerical output of how loud things are, you'll need an analog-to-digital converter or ADC.  If you just need to detect loud noises, it's much simpler.  You'll need to add a potentiometer with a high resistance (I use 1MegaOhm) to your setup.  Connect POT terminal A on Pin 1 and terminal B on +5V (see schematic).  Connect your stamp's input to the wiper or terminal W.  Turning the potentiometer dial will scale the voltage output between 0 and 5V.  If the output is less than 1.5V, the stamp will register LOW and if the output is above 1.5V, the stamp will register HIGH.  Adjust the potentiometer to achieve the volume threshold you would like.

You can also connect the 10k resistor to 5V and the potentiometer to ground so that you get lower voltages for higher volumes instead of higher voltages for lower volumes.  This is a bit counter-intuitive, but whatever floats your boat.

Step 5: The Light Sensor

The light sensor actually works the same as the sound sensor and can be used in the same manner except for one small difference: Pin 5 controls the LED on the sensor.  Apply 5V to turn the LED on, or leave the pin low to turn it off.  No resistor required.

Step 6: The Ultrasonic Sensor

This is the big one.  The complexity of everything prior to this pales in comparison.  However, it's still not that bad.  The ultrasonic sensor uses the I Squared C or I2C protocol.  This protocol allows a master (the LEGO RCX) to control multiple slave devices (sensors) using only two wires (plus power wires).  This comes in handy when you start looking at multiplexers and using more than one sensor on one port.  It also allows additional functionality for the ultrasonic sensor.  I2C is a digital protocol, but it is not explicitly supported by the BS2.  Luckily, we can do something known as "bit-banging" the protocol by writing our own code for it.  This is advantageous because the ultrasonic sensor does not exactly follow I2C protocol anyways.  So, without further ado, here is a crash course in I2C with an emphasis on the ultrasonic sensor.

General procedure:
Master sends start signal
Master sends address byte (determines which device is being accessed and whether to read or write)
Slave acknowledges
Data transmission (From master to slave or from slave to master)
Receiver acknowledges after each byte
Master Sends Stop Command

You'll want to download the LEGO Hardware Developer Kit here: http://mindstorms.lego.com/en-us/support/files/default.aspx

If you look at appendix 7, you will see all of the possible commands for the ultrasonic sensor as well as the expected output.
Bytes are written as Hex or Base 16 (as denoted by "0x").

To give an example of a command, let's try reading the product ID which should return "LEGO"
To do this, We would send a start command, then the sensor's write address (2) which is acknowledged, then the Product ID command (8) which is acknowledged followed by a Restart Command ("R" in the appendix), and the sensor's read address (3) which must be acknowledged. After the read address is sent, the sensor will return 5 bytes (L, E, G, O, and a null 0x00).  Each byte must be acknowledged by the stamp.

The Restart command consists of a Stop, a clock cycle, and a Start.  That extra clock cycle, often referred to as a "wiggle" is not part of the standard I2C protocol.  If you are using a microcontroller that supports I2C, you'll need to make sure you send that extra clock cycle.

Now, we need to get into the real nitty-gritty of the I2C protocol.  We'll start with the sensor's pinout.
Pin 1 - 9V (Vcc)
Pin 2 - Ground
Pin 3 - Ground
Pin 4 - 5V (Vcc)
Pin 5 - SCL
Pin 6 - SDA

SCL is your clock line which is used to synchronize data transmission and SDA is the actual data line.  I tend to call them "clock" or "clk" and "data" so bear with me.

There are resistors connecting both SCL and SDA to the 5V source so that unless something is done, they default to 5 volts.  Communication is done by pulling these lines to 0 and then releasing them.  Note that you don't have to pull the lines high, as they default high.  Because of that, when you're done with a command, it's best to set both to input.  If you fail to release the lines, you could cause a short with a high on the BS2 end and a low on the sensor end.

The I2C commands themselves are actually pretty straightforward.

Start:
Start with both lines high, pull SDA low, then pull clock low.  The effect is that SDA goes from high to low while clock is high.

Stop:
Start with both lines low, let clock go high, then let SDA go high.  The effect is SDA goes from low to high while clock is high.

See how Stop is the opposite of Start?

To cycle the clock, simply pull it low for a moment and then release it.

Sending Data:
Data is sent in bytes and each byte is followed by an acknowledge by the recipient. 
First, the clock is pulled low, then SDA is set to the next bit to be sent by the transmitter, then clock is released high and SDA is read by the receiver, then clock is pulled low again.  This repeats for all 8 bits.  Data is only valid when clock is high and data may only be changed when clock is low.  Changing SDA while the clock is high results in either a Start or Stop command. After the 8 bits have been sent, the receiver holds SDA low as an acknowledgement while the master releases and lowers clock one more time.  Bytes are sent MSB First or Most Significant Bit First.  For example, the byte for "2" 00000010 would be sent starting with the bit on the left and ending with the bit on the right.

Address Bytes:
Every sensor has a 7 bit address which is sent in conjunction with a directional bit.
The 7 bit address of the ultrasonic sensor is "1" or "0000001"
The directional bit is either "0" for write (send to slave) or "1" for read (receive from slave).
The result is a send address of 00000010 or 2 and a read address of 00000011 or 3.

So how do we actually do all of this?  I tried explicitly coding everything on the stamp, but it ran too slow.  The sensor runs at an optimal speed of 9600 Hz, and the stamp doesn't come close to that when executing line by line.  Instead, I resorted to using SHIFTIN and SHIFTOUT commands which are actually much easier.  Start, Stop, and Restart are still accomplished manually using High and Low commands.  The result looks like this:

byt0, byt1, and byt2 are found in the Hardware Developer Kit Appendix 7.  
bytes_receive is the number of bytes to be received from the sensor
alpha is 1 if an ASCII string is expected (think "alphabet") and 0 if a numerical response is expected
Be sure to write up an error function for when the sensor fails to ack.  Mine just restarted the whole process.

'start
INPUT sda
INPUT clk
LOW sda
'send0
SHIFTOUT sda, clk, MSBFIRST, [byte0]     'address byte
SHIFTIN sda, clk, MSBPRE,[ack\1]             'receive acknowledgement
IF ack=1 THEN error                                      'if ack is not received, run error routine
'send1
SHIFTOUT sda, clk, MSBFIRST, [byt1]        'send command byte
SHIFTIN sda, clk, MSBPRE,[ack\1]              'receive acknowledgement
IF ack=1 THEN error                                       'if ack is not received, run error routine
'restart                                                                'Restart command (Stop, clock, Start)
LOW clk
LOW sda
HIGH clk
HIGH sda
LOW clk
HIGH clk
LOW sda
'send2
SHIFTOUT sda, clk, MSBFIRST, [byt2]        'Send address byte for return
SHIFTIN sda, clk, MSBPRE,[ack\1]              'Receive ack
IF ack=1 THEN error                                       'if ack is not received, run error routine
'receive
FOR i=0 TO bytes_receive-1
SHIFTIN sda, clk, MSBPRE,[bytr(i)\8]          'Receive Byte
SHIFTOUT sda, clk, MSBFIRST, [0\1]          'Send acknowledgement
IF alpha=0 THEN DEBUG DEC bytr(i)        'If number, debug decimal value of each byte
NEXT                                                                 'Repeat, depending on number of bytes expected
IF alpha=1 THEN DEBUG STR bytr             'If string, debug all bytes as alphanumeric string
DEBUG CR
'Stop
LOW clk
LOW sda
INPUT clk
INPUT sda

If a Command's third byte (byte 2) is something other than "R+0x03", send the third byte without the restart sequence and don't expect a reply.