Direct manipulation of I2C pins

06 Jun 2010

I have been attempting to establish I2C communications with a Lego Mindstorms Ultrasonic sensor.

It seems this device has a few quirks, thanks to its I2C protocol being bit-banged by a microcontroller.

After trying everything I could think of to get this device to work, I found some reference on a nxtasy forum that suggested the device doesn't strictly comply with the I2C protocol, and requires a few "extra states" to be implemented for read operations.

I tracked down the source code for the LeJOS firmware, that apparantly has special handling of this type, and found the following statement:

// Note: It appears that the Lego ultrasonic sensor needs a
// stop and an extra clock before the restart.

The state machine (also bit banged) that implements this logic only adds the extra clocking etc during a read command, just before the restart operation. I think this explains why I have been able to write to the sensor, but not read back from it.

SO...I'm thinking perhaps I may be able to directly toggle the SDA and SCL pins by creating a DigitalInOut object using the same pins, then writing to them between I2C write & read operations to simulate the strange sequence that the NXT Ultrasonic sensor apparantly requres.

Any reason why this wouldn't work? Can a DigitalInOut object manipulate pins that are already in use by an I2C object without causing issues?

06 Jun 2010

Hi Andrew,

The approach would almost work; the only thing is there is currently no way to switch back and forth between two active interfaces (apart from dynamically creating them each time, hence invoking the constructor each time; that might work).

For now, the easiest work around is probably actually to use the I2C interface as normal, but manually cobble the pin mux block of the LPC1768 to turn it in to a digital output, then back to I2C, when you want to force this additional pulse.

However, i'd like to be able to make this work without a hack like this. We have a mindstorms ultrasonic sensor here too, so it would be a good project to get working to help ensure this sort of thing should be possible. I am thinking the best approach may be to allow you to get an interface to reconfigure(refresh) itself, and hence be able to switch between multiple interfaces on the same pin. The approach is somewhat similar to the internal mechanisms I put in to SPI and I2C to allow them to be virtualised, so it could be a reasonable thing to do.

Perhaps see if you can get it working with the hack to prove we can actually talk to it, and then we can look at putting in the stuff needed to do it more elegantly.

Simon

07 Jun 2010

OK - I had a quick go at this, and alas, cobbling the pin mux block to GPIO makes the I2C block very unhappy, and it requires reseting. However, an alternative approach is to use a third pin as a DigitalInOut, which is connected to the clock line. This pin is set as an input, until we want this extra wiggle on the clock line, when we set it as an output, wait a fraction, then set it back to input. Here is an example, where I've connected p20 up to p10 externally:

#include "mbed.h"

int main() {
    I2C uss(p9, p10);                   // Mindstorm ultrasound sensor
    DigitalInOut c(p20);                // Clock override pin
    char buf[10] = { 0 };               // Data read buffer
    char cmd[] = { 0x08 };              // Command "Read Product ID" send buffer
    
    uss.frequency(9600);                // Mindstorm uses 9600 baud I2C
    c = 0;                              // Setup override pin to pull clock low
    c.input();                          // Make it input to start with...
    c.mode(PullUp);                     // ...with pull up
    
    uss.write(0x02, cmd, 1, false);     // Write command
    
    wait(0.00005);                      // Pause after stop
    c.output();                         // Override clock pin low
    wait(0.00005);                      // Pause
    c.input();                          // Remove override...
    c.mode(PullUp);                     // ...with pull up
    wait(0.00005);                      // Pause again
    uss.read(0x02, buf, 5, false);      // Read response
    printf("Read: %s\n", buf);
}
This prints the response "LEGO" on the serial console. Here's the output from a logic analyser:

Mindstorm Sensor I2C trace

07 Jun 2010

Awesome - many thanks Jon & Simon!

I had made a start on changing the pin mux registers and driving the gpios directly (all via direct register access, not using the DigitalOut class), but had not got around to trying it out yet. I won't bother pursuing that now, as Jon's solution looks much simpler, and I have plenty of spare pins available.

Andrew

13 Jul 2010

For the record, I finally got some time to try out Jon's code for myself, and it didn't work :-(

The only difference I can think of is that I'm compiling against the latest version of mbed libraries (revision 24), whereas Jon would have used an earlier version.

However, I managed to get the code to work by moving the "uss.frequency(9600)" command down so it is executed just prior to the write() command in the code shown above. Don't know why that would make a difference, but it does...at least with rev 24 of the library.

In fact, with the frequency() command in the original location, the write() command fails with a return value of 32 and the read() command fails with a return value of 72 - no idea what these values mean (Jon?), but they ain't good.

13 Jul 2010

I'll file this as a low priority bug - you've got it working, but I want to investigate why this doesn't work in v24 as it did in the earlier version.

03 Aug 2012

I know this topic is quite old, but I had the same problem, and I think I have some sort of a better solution. It might be interesting for other people around, because accessing the Lego NXT Ultrasonic sensor is generally not well understood, because of its weird firmware bug. Unfortunately I bought and populated my custom mbed-nxt base board before testing the interface to the Lego ultrasonic sensor. On the board there are no GPIOs left for the hack Jon descriped, therefore I had to find a better solution.

I mainly works by construction a I2C interface, sending the first command. Then destructing it. Then construct a digital Out, send out the infamous extra clock, then destructing it again. Then again constructing a I2C interface, read out the data. The additional, somewhat weird looking, brackets are important. The code is not optimized at all, but it prints out the measured distance on the serial console.

#include "mbed.h"
Serial pc(USBTX, USBRX);
char buf[10] = { 0 };               // Data read buffer
char cmd[] = { 0x42 };              // Command "read distance" send buffer
int main() {
    
    power.write(1);
    while(1) {
       wait(0.1); //100 ms delay between calls
       {
            I2C i2c(p9, p10);
            i2c.frequency(9600);
            i2c.write(0x02, cmd, 1, false);
       }
       {
            DigitalInOut scl(p10);
            scl = 0;                              // Setup override pin to pull clock low
            scl.input();                          // Make it input to start with...
            scl.mode(PullUp);                     // ...with pull up
            wait(0.00005);                      // Pause after stop
            scl.output();                         // Override clock pin low
            wait(0.00005);                      // Pause
            scl.input();                          // Remove override...
            scl.mode(PullUp);                     // ...with pull up
            wait(0.00005);                      // Pause again
       }  
       {
            I2C i2c(p9, p10);
            i2c.frequency(9600);
            i2c.read(0x02, buf, 5, false); 
            pc.printf("Distance: %d cm", buf[0]);   
       }  
       wait(0.5);
    }
}