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 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.

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.

LOW sda
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
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
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
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
LOW clk
LOW 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.

<p>Thanks for that. I am understanding it, even though I don't have a STAMP.</p><p>I have an Arduino Uno and you said you might add the coding for that. Can you help with Arduino commands?</p>
<p>Thanks for the tutorial, helped a lot !!!!</p>

About This Instructable




More by carrick149:Using NXT Components with a Micro Controller 
Add instructable to: