Shop OBEX P1 Docs P2 Docs Learn Events
Erratic I2C Values — Parallax Forums

Erratic I2C Values

John BoardJohn Board Posts: 371
edited 2013-08-22 03:46 in Propeller 1
G'day,

This is my first time coding I2C stuff, so please excuse my ignorance.

I've been writing some code for the HMC5883L Magnomometer using this arduino example code as my template:
/*
An Arduino code example for interfacing with the HMC5883


by: Jordan McConnell
SparkFun Electronics
created on: 6/30/11
license: OSHW 1.0, [URL]http://freedomdefined.org/OSHW[/URL]


Analog input 4 I2C SDA
Analog input 5 I2C SCL
*/


#include <Wire.h> //I2C Arduino Library


#define address 0x1E //0011110b, I2C 7bit address of HMC5883


void setup(){
//Initialize Serial and I2C communications
Serial.begin(9600);
Wire.begin();

//Put the HMC5883 IC into the correct operating mode
Wire.beginTransmission(address); //open communication with HMC5883
Wire.send(0x02); //select mode register
Wire.send(0x00); //continuous measurement mode
Wire.endTransmission();
}


void loop(){

int x,y,z; //triple axis data


//Tell the HMC5883 where to begin reading data
Wire.beginTransmission(address);
Wire.send(0x03); //select register 3, X MSB register
Wire.endTransmission();


//Read data from each axis, 2 registers per axis
Wire.requestFrom(address, 6);
if(6<=Wire.available()){
x = Wire.receive()<<8; //X msb
x |= Wire.receive(); //X lsb
z = Wire.receive()<<8; //Z msb
z |= Wire.receive(); //Z lsb
y = Wire.receive()<<8; //Y msb
y |= Wire.receive(); //Y lsb
}

//Print out values of each axis
Serial.print("x: ");
Serial.print(x);
Serial.print(" y: ");
Serial.print(y);
Serial.print(" z: ");
Serial.println(z);

delay(250);
}

And using the following guide to learn I2C:

http://www.robot-electronics.co.uk/acatalog/I2C_Tutorial.html

With my minimal I2C skills I've tried to clone it, and this is what I've come up with:
CON


  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000


OBJ


  I2C: "I2C"
  Serial: "FullDuplexSerial"
  Settings: "Settings"


PUB Main


  Serial.start(31, 30, 0, 9600)
  Serial.str(string("I2C Test", 13))
  i2c_init
  i2c_loop  




PUB i2c_init


  Serial.str(string("Initalizing Compass...", 13))
  I2C.Initialize(Settings#IMU_SCL)  
  I2C.Start(Settings#IMU_SCL)                           'Send start                                                                  
  I2C.Write(Settings#IMU_SCL, $3C)                      'Write address
  I2C.Write(Settings#IMU_SCL, $02)                      'Select mode register
  I2C.Write(Settings#IMU_SCL, $00)                      'Set continuous measurement mode 
  I2C.Stop(Settings#IMU_SCL)                            'Send stop
  Serial.str(string("Initalized!", 13))


PUB i2c_loop | x, y, z


  Serial.str(string("Entering loop", 13))
  repeat
    I2C.Start(Settings#IMU_SCL)                         'Send start
    I2C.Write(Settings#IMU_SCL, $3C)                    'Write address
    I2C.Write(Settings#IMU_SCL, $03)                    'Select register 3, X MSB register
    I2C.Start(Settings#IMU_SCL)                         'Send reset
    I2C.Write(Settings#IMU_SCL, $3D)                    'Write address


    x := I2C.Read(Settings#IMU_SCL, 0) << 8
    x |= I2C.Read(Settings#IMU_SCL, 0)
    y := I2C.Read(Settings#IMU_SCL, 0) << 8
    y |= I2C.Read(Settings#IMU_SCL, 0)
    z := I2C.Read(Settings#IMU_SCL, 0) << 8
    z |= I2C.Read(Settings#IMU_SCL, 0)
    
    I2C.Stop(Settings#IMU_SCL)


    Serial.tx(16)
    Serial.dec(x)
    Serial.tx(13)
    Serial.dec(y)
    Serial.tx(13)
    Serial.dec(z)
    Serial.tx(13)
    
    waitcnt(clkfreq/2+cnt)

I am getting readings, however the readings are erratic, the base line values I'm getting are:
61438
44800
2817

However, the values are constantly jumping to:
11
495
65199

Every time the loop updates it switches between the two.

What am I doing wrong? Do I need to set the register pointer back to the start? It might be worth pointing out that this device automatically increments the I2C register every time I read.

The documentation for the device can be found here:

http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Magneto/HMC5883L-FDS.pdf

A
ny help is appreciated!

Thanks,

-John

Comments

  • JonnyMacJonnyMac Posts: 9,107
    edited 2013-08-21 10:48
    Did you see this in the spec?
    The value stored in these two registers is a 16-bit value in 2’s complement form, whose range is 0xF800 to 0x07FF.


    What this means is that you need to extend the 16-bit value to a long by using the ~~ operator.
    x := ~~x
    
  • StephenMooreStephenMoore Posts: 188
    edited 2013-08-21 10:55
    Hi John,

    I am using the HMC5883L quite a bit and am interested in getting stable readings too.

    Yes, you do have to reset the register pointer to the X register at he beginning of each read (by the way the read order is X, Z, Y for some reason).

    I did not see your settings object but assume you have selected two pins other than 28 and 29 for your I2C bus: the Parallax board already has pull-ups on it. I am not sure this makes much of a difference since the I2C spec allows a range of resistor values.

    I tend to see sporadic reading from these modules from time to time and am trying to figure out why. You have to be careful to keep all stray fields to a minimum (this can also mean watching out for any ferromagnetic materials around the detector).

    I have some working code I can post if you are still unable to get reasonable numbers.

    Regards
  • John BoardJohn Board Posts: 371
    edited 2013-08-21 15:42
    G'day,

    Thanks for the help, I'll imlement all of this in a sec!

    I'd be interested to see your code. I may or may not use it, I'll try to get mine working first, but I'm still interested to see what you've come up with.

    -John
  • John BoardJohn Board Posts: 371
    edited 2013-08-21 17:39
    G'day,

    I've modified my code to look like this:
    CON
    
    
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    
    
    OBJ
    
    
      I2C: "I2C"
      Serial: "FullDuplexSerial"
      Settings: "Settings"
    
    
    VAR
    
    
      long x, y, z
    
    
    PUB Main
    
    
      Serial.start(31, 30, 0, 9600)
      Serial.str(string("I2C Test", 13))
      i2c_init
      i2c_loop  
    
    
    
    
    PUB i2c_init
    
    
      Serial.str(string("Initalizing Compass...", 13))
      I2C.Initialize(Settings#IMU_SCL)  
    
    
      'I2C.Start(Settings#IMU_SCL)                           'Send start                                                                  
      'I2C.Write(Settings#IMU_SCL, $3C)                      'Write address
      'I2C.Write(Settings#IMU_SCL, $00)                      'Select CRA
      'I2C.Write(Settings#IMU_SCL, $70)                      'Set 8-average, 15Hz default, normal measurement 
      'I2C.Stop(Settings#IMU_SCL)                            'Send stop
    
    
      'I2C.Start(Settings#IMU_SCL)                           'Send start                                                                  
      'I2C.Write(Settings#IMU_SCL, $3C)                      'Write address
      'I2C.Write(Settings#IMU_SCL, $01)                      'Select CRB
      'I2C.Write(Settings#IMU_SCL, $A0)                      'Set Gain=5 
      'I2C.Stop(Settings#IMU_SCL)                            'Send stop
    
    
      I2C.Start(Settings#IMU_SCL)                           'Send start                                                                  
      I2C.Write(Settings#IMU_SCL, $3C)                      'Write address
      I2C.Write(Settings#IMU_SCL, $02)                      'Select mode register
      I2C.Write(Settings#IMU_SCL, $00)                      'Set continuous measurement mode 
      I2C.Stop(Settings#IMU_SCL)                            'Send stop
      
      Serial.str(string("Initalized!", 13))
    
    
    PUB i2c_loop
    
    
      Serial.str(string("Entering loop", 13))
      repeat
        I2C.Start(Settings#IMU_SCL)                         'Send start
        I2C.Write(Settings#IMU_SCL, $3C)                    'Write address
        I2C.Write(Settings#IMU_SCL, $03)                    'Select register 3, X MSB register
        I2C.Start(Settings#IMU_SCL)                         'Send reset
        I2C.Write(Settings#IMU_SCL, $3D)                    'Write address
    
    
        x := ~~x
        x := I2C.Read(Settings#IMU_SCL, 0) << 8
        x |= I2C.Read(Settings#IMU_SCL, 0)
        y := ~~y
        y := I2C.Read(Settings#IMU_SCL, 0) << 8
        y |= I2C.Read(Settings#IMU_SCL, 0)
        z := ~~z
        z := I2C.Read(Settings#IMU_SCL, 0) << 8
        z |= I2C.Read(Settings#IMU_SCL, 0)
        
        I2C.Stop(Settings#IMU_SCL)
    
    
        Serial.tx(16)
        Serial.dec(x)
        Serial.tx(13)
        Serial.dec(y)
        Serial.tx(13)
        Serial.dec(z)
        Serial.tx(13)
        
        waitcnt(clkfreq/10+cnt)
    

    I'm using ports 22-23 (SCL/SDA) of the prop. I think the values I'm getting aren't electronic related, I think it's programming related. When I tilt the robot one way, it's fine, when I tilt the other way, the readings get all funny.

    Normal readings when tilting one way:
    65530
    178
    65384
    

    Funny readings are all over the place, but here's an example:
    40447
    21760
    5888
    

    But its jumping all over the place. My guess is it's something to do with how the prop handles the numbers it's getting. Any ideas though? I've no clue what to do!

    -John
  • JonnyMacJonnyMac Posts: 9,107
    edited 2013-08-21 18:05
    You put the sign extension before reading the value -- it must come after you read from the device.
    x := I2C.Read(Settings#IMU_SCL, 0) << 8
        x |= I2C.Read(Settings#IMU_SCL, 0)
        x := ~~x
    
  • John BoardJohn Board Posts: 371
    edited 2013-08-21 19:23
    JonnyMac wrote: »
    You put the sign extension before reading the value -- it must come after you read from the device.
    x := I2C.Read(Settings#IMU_SCL, 0) << 8
        x |= I2C.Read(Settings#IMU_SCL, 0)
        x := ~~x
    

    It's working now! Thank you very much for your help in getting my first dabblings in I2C working!

    -John
  • JonnyMacJonnyMac Posts: 9,107
    edited 2013-08-21 19:27
    Excellent!

    I don't have one of those devices, but I knocked up an object for it earlier today -- if you would, give this a whack and let me know if works (please). Note that this is just the object and I2C code; you'll need to instantiate it and call the methods from your program.

    The rd_xyz() method will read all three values with one call. Spin methods can only return one long so you need to pass pointers (the address of) the variables you want to update. For example:
    compass.rd_xyz(@x, @y, @z)
    


    Of course, this assumes you've created and started an object called compass.
  • John BoardJohn Board Posts: 371
    edited 2013-08-21 20:00
    JonnyMac wrote: »
    Excellent!

    I don't have one of those devices, but I knocked up an object for it earlier today -- if you would, give this a whack and let me know if works (please). Note that this is just the object and I2C code; you'll need to instantiate it and call the methods from your program.

    The rd_xyz() method will read all three values with one call. Spin methods can only return one long so you need to pass pointers (the address of) the variables you want to update. For example:
    compass.rd_xyz(@x, @y, @z)
    


    Of course, this assumes you've created and started an object called compass.

    G'day,

    Working well! Thanks! Just realized that I probably won't be using the compass though! I'm actually using the SparkFun 9DOF stick, so it has the Gyro and Accelerometer on it too, when I was working out the I2C code I figured the compass would be the easiest. Doesn't matter though, this did teach me how to use I2C, and I should be able to write the Gyro and Accel code without too much hastle.

    As I said, the code you wrote is working well, and here's the code I wrote:
    CON
    
    
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    
    
    OBJ
    
    
      Compass:      "jm_HMC5883L"
      Serial:          "FullDuplexSerial"
    
    
    VAR
    
    
      long x, y, z
    
    
    PUB Main
    
    
      Serial.start(31, 30, 0, 115200)
    
    
      Compass.startx(22, 23)
    
    
      repeat
        Compass.rd_xyz(@x, @y, @z)
    
    
        Serial.tx(16)
        Serial.dec(x)
        Serial.tx(13)
        Serial.dec(y)
        Serial.tx(13)
        Serial.dec(z)
    

    Thanks again for all your help!

    -John

    [EDIT]

    Put a profiler on the rd_xyz, and it comes back at about 3ms.
  • shimniokshimniok Posts: 177
    edited 2013-08-21 23:30
    If you want to try my in-development I2C driver, let me know. It properly implements I2C master, respecting the open collector design (it never drives the bus) and respects slave clock stretching. Not sure if either are an issue but anyway, yours for the asking. :)

    I2C is awesome once you get it working. I always seem to struggle a little at first with every new device, but eventually it rocks. It doesn't help that I use multiple masters (Propeller, Arduino, mbed, etc.) with different libraries. :D Nothing is better than a logic analyzer when working on this stuff. Saves me *so* much time.

    anyway best of luck!

    Michael
  • JonnyMacJonnyMac Posts: 9,107
    edited 2013-08-22 03:46
    It properly implements I2C master, respecting the open collector design (it never drives the bus) and respects slave clock stretching.

    I'm with you: my I2C object (provided with the HMC5883L object above) also obeys those rules.
Sign In or Register to comment.