Shop OBEX P1 Docs P2 Docs Learn Events
bma180 accelerometer with I2C — Parallax Forums

bma180 accelerometer with I2C

seiko99seiko99 Posts: 9
edited 2012-09-30 09:24 in Propeller 1
I finally got a quickstart board and need to quickly get started on what I thought would be a simple project of connecting this breakout board I got from sparkfun.
I have dug and dug for any projects with sample code I can look at using this thing and can't find a thing. I think that quad copters since they use propellers is clouding the search engines - lol

This is such a great sensor and has been out so long, surely someone has done something with it. I started an object file with all the registers and values and have been trying to modify the c code that is on the sparkfun site, but I guess the jump to spin is too much for me.

I have been at it almost every evening for almost 2 weeks and am getting nowhere. Help?

Comments

  • Mike GreenMike Green Posts: 23,101
    edited 2012-07-15 10:43
    I haven't seen any examples here on this chip's use. It's a pretty complex looking part with lots of options which may be why there's not any evidence of interest. How are you doing the I2C part of things? There are a number of objects that will handle that part for you. They take an I2C address and I/O pin numbers plus a register address in the BMA180 and take care of transferring bytes to and from the device. "Basic_I2C_Driver" is one such object.

    To read from a BMA180 register if the BMA180 SCL is <SCL pin> and SDA is on the next higher I/O pin do:

    Z := i2c.ReadByte(<SCL pin>, <device>, i2c#OneAddr | <register>)

    To write to a BMA180 register do:

    i2c.WriteByte(<SCL pin>, <device>, i2c#OneAddr | <register>, <data to write>)

    The <device> code is the 8-bit byte that selects the device. It's the device address (a 7-bit number) shifted left one bit to make the byte sent to the device. The least significant bit of this byte is always zero for these routines. The BMA180 allows for two different device addresses depending on the setting of one of the device's pins. See the datasheet for details.

    Similarly, you can read or write multiple bytes from / to successive BMA180 registers. See the comments in Simple_I2C_Driver for details.
  • seiko99seiko99 Posts: 9
    edited 2012-07-16 16:46
    Mike Green wrote: »
    I haven't seen any examples here on this chip's use. It's a pretty complex looking part with lots of options which may be why there's not any evidence of interest. How are you doing the I2C part of things? There are a number of objects that will handle that part for you. They take an I2C address and I/O pin numbers plus a register address in the BMA180 and take care of transferring bytes to and from the device. "Basic_I2C_Driver" is one such object.

    To read from a BMA180 register if the BMA180 SCL is <SCL pin> and SDA is on the next higher I/O pin do:

    Z := i2c.ReadByte(<SCL pin>, <device>, i2c#OneAddr | <register>)

    To write to a BMA180 register do:

    i2c.WriteByte(<SCL pin>, <device>, i2c#OneAddr | <register>, <data to write>)

    The <device> code is the 8-bit byte that selects the device. It's the device address (a 7-bit number) shifted left one bit to make the byte sent to the device. The least significant bit of this byte is always zero for these routines. The BMA180 allows for two different device addresses depending on the setting of one of the device's pins. See the datasheet for details.

    Similarly, you can read or write multiple bytes from / to successive BMA180 registers. See the comments in Simple_I2C_Driver for details.

    I just spent $60 bucks on ink to finally print out the datasheet. I have started with the Basic_i2c_driver and created my own object file containing all the registers for the bma180.
    I was confused at first thinking that I had to use the prop's SCL and SDA. I am believing now that this is bit-bang and I can use whatever pins I want. I guess my biggest confusion comes from the examples that were put together by James Burrows (I think). The i2c object is initialized and then the object/device specific routines are initialized and used. They are so device specific, it is daunting as a first propeller project.

    Anyways, I am pushing on and will post some code tonight hopefully. Your examples should help a lot. I am excited about the things I see with the propeller. Last year I built a machine with an arduino mega that drove me so crazy with the interrupt game that I almost gave up. I think once this little accelerometer starts doing what I need, I may have a mega for sale on ebay.
  • Mike GreenMike Green Posts: 23,101
    edited 2012-07-16 16:55
    Basic_I2C_Driver is bit-bang, The only restriction on pin numbers is that the number passed to the I2C routines is that of SCL and SDA has to be the next higher numbered I/O pin. You can certainly use the same I2C pins used for the Prop's EEPROM, but you don't have to. James Burrows took the original Basic_I2C_Driver and added a bunch of device specific interfaces. If you have one of these devices, his routines will save some work. They also serve as examples of how to interface to devices similar to them.
  • seiko99seiko99 Posts: 9
    edited 2012-07-16 19:32
    OBJ
    i2c : "Basic_I2C_Driver"
    Ser : "Parallax Serial Terminal"

    CON
    _clkmode = xtal1 + pll16x 'Standard clock mode * crystal frequency = 80 MHz
    _xinfreq = 5_000_000
    BMA_CRC = $5B
    BMA_OFFSETZ = $3A
    BMA_OFFSETY = $39
    BMA_OFFSETX = $38
    BMA_OFFSETT = $37
    BMA_OFFSETLSB2 = $36
    BMA_OFFSETLSB1 = $35
    BMA_GAINZ = $34
    BMA_GAINY = $33
    BMA_GAINX = $32
    BMA_GAINT = $31
    BMA_TCOZ = $30
    BMA_TCOY = $2F
    BMA_TCOX = $2E
    BMA_CD2 = $2D
    BMA_CD1 = $2C
    BMA_SLOPETH = $2B
    BMA_HIGHTH = $2A
    BMA_LOWTH = $29
    BMA_TAPSENSTH = $28
    BMA_HIGHDUR = $27
    BMA_LOWDUR = $26
    BMA_HIGHLOWINFO = $25
    BMA_SLOPETAPSENSINFO = $24
    BMA_HY = $23
    BMA_CTRLREG4 = $22
    BMA_CTRLREG3 = $21
    BMA_BWTCS = $20
    BMA_RESET = $10
    BMA_CTRLREG2 = $0F
    BMA_CTRLREG1 = $0E
    BMA_CTRLREG0 = $0D
    BMA_STATUSREG4 = $0C
    BMA_STATUSREG3 = $0B
    BMA_STATUSREG2 = $0A
    BMA_STATUSREG1 = $09
    BMA_TEMPERATURE = $08
    BMA_ACCZMSB = $07
    BMA_ACCZLSB = $06
    BMA_ACCYMSB = $05
    BMA_ACCYLSB = $04
    BMA_ACCXMSB = $03
    BMA_ACCXLSB = $02
    BMA_VERSION = $01
    BMA_CHIPID = $00
    BMA_address = $80
    ''Range setting
    RANGESHIFT = 1
    RANGEMASK =$0E
    BWMASK = $F0
    BWSHIFT = 4
    i2cSCL = 26
    i2cSDA = 27
    bma180address = 00_0000
    ''interrupt_pin = 22 ''for when I figure out how to do that


    VAR
    long id
    byte Z

    PUB main
    ser.Start(115_200)
    waitcnt(clkfreq/10 + cnt) 'wait for things to settle

    PUB init(range, bw) ''init bma180
    ''from advice:
    Z := i2c.ReadByte(i2cscl, bma180address, i2c#bma180address | BMA_CHIPID)

    This is where I am at for the moment. The "i2c#bma180address | BMA_CHIPID" is not making sense and is just me guessing. Maybe I am over analyzing.
    Should it be i2c.ReadByte(i2cscl, bma180address, BMA_CHIPID) ? I see that the interpreter wants a constant if I do it the old way.

    Thanks again for any help
  • seiko99seiko99 Posts: 9
    edited 2012-07-22 17:27
    Ok, I got it working. I used some of the c code from the sparkfun site on the bits, but I feel like I am not getting the full resolution or at least the numbers the way I need them. I want to put a routine that will test for changes over a certain amount. My current code does not indicate positive or negative when rendered in decimal. Thus a small movement will go from say 48 to 1432 when it crosses zero. This also screws with averaging. I want full 14 bit resolution. The MSB and LSB together consist of a 2's complement total of 16 bits with one data ready bit.

    A piece of my code for one axis is as follows:

    VAR
    long id
    word bmversion, temp, temp1, temp2, temp3, temp4, temptemp
    long tempX, tempY, tempZ

    PUB getX
    repeat while temp <> 1
    temp := i2cObject.readLocation(bmaddress, ACCXLSB) & $1
    temp := i2cObject.readLocation(bmaddress, ACCXMSB)
    temp2 := temp << 8
    temp2 |= i2cObject.readLocation(bmaddress, ACCXLSB)
    tempX := temp2 >> 2
    temp := 0

    I guess I should convert to signed decimal? Are the <> and other bitwise methods I am calling the right ones? Oh yeah, how about degrees?
  • seiko99seiko99 Posts: 9
    edited 2012-07-27 15:15
    This 14 bit number is making me nuts. I think that the Serial_Terminal.spin code is messing with me
    VAR
      long id
      byte temp, i
      word bmversion, temp1, temp2, temp3, temp4, temptemp
      word tempX, tempY, tempZ
    
    PUB getX
        repeat while temp <> 1  ''temp is a byte, temp2 and tempX are words
              temp := i2cObject.readLocation(bmaddress, ACCXLSB) & $1      ''check data ready bit
        temp := i2cObject.readLocation(bmaddress, ACCXMSB)
        temp2 := temp << 8  ''move MSB to position
        temp2 |= i2cObject.readLocation(bmaddress, ACCXLSB)
        tempX := temp2
    

    I decided to not right shift, leaving a couple of flag bits in the word. It is really inconsequential if I can get the negative numbers (I would rather work with 14 bits). As it is, I only get positive.
            Ser.str(String("X: "))
            Ser.dec(tempX)
    

    I tried setting tempX to $FF80 which should be negative 128. Still wont show negative. I printed out my words in binary and the sign bits are set.
    I am playing around with numbers.spin, but not getting anywhere. Help?
  • LawsonLawson Posts: 870
    edited 2012-07-27 16:24
    I think what's catching you is that all math in spin is done on 32-bit words. -128 would be $FFFF_FF80, etc.

    Two easy ways to fix this. Use the "~~variable" 16-bit sign extension operator. (i.e. "tempX := ~~temp2" ) Or, to sign extend any word width, use the shift left and shift arithmetic right operators. (i.e. "tempX := (temp2 <<16) ~> 16" )

    Lawson
  • seiko99seiko99 Posts: 9
    edited 2012-08-11 14:07
    Ok, this has been interesting. I now have the readings that I want from the serial terminal. It has been tough and the code is surely rough. My problem now is that I cannot start another cog to simply toggle some leds as a test. I am posting it all in the hopes that it might help.
    OBJ
            i2cObject : "i2cObject"
            Ser  : "Parallax Serial Terminal"
            numb : "Numbers"
            f32     : "F32"
            fstring : "FloatString"
            
    CON
            _clkmode = xtal1 + pll16x       'Standard clock mode * crystal frequency = 80 MHz
            _xinfreq = 5_000_000
            CRC                 = $5B
            OFFSETZ             = $3A
            OFFSETY             = $39
            OFFSETX             = $38
            OFFSETT             = $37
            O7LSB2          = $36
            OLSB1          = $35
            GAINZ               = $34
            GAINY               = $33
            GAINX               = $32
            GAINT               = $31
            TCOZ                = $30
            TCOY                = $2F
            TCOX                = $2E
            CD2                 = $2D
            CD1                 = $2C
            SLOPETH             = $2B
            HIGHTH              = $2A
            LOWTH               = $29
            TAPSENSTH           = $28
            HIGHDUR             = $27
            LOWDUR              = $26
            HIGHLOWINFO         = $25
            SLOPETAPSENSINFO    = $24
            HY                  = $23
            CTRLREG4            = $22
            CTRLREG3            = $21
            BWTCS               = $20
            RESET               = $10
            CTRLREG2            = $0F
            CTRLREG1            = $0E
            CTRLREG0            = $0D
            STATUSREG4          = $0C
            STATUSREG3          = $0B
            STATUSREG2          = $0A
            STATUSREG1          = $09
            TEMPERATURE         = $08
            ACCZMSB             = $07
            ACCZLSB             = $06
            ACCYMSB             = $05
            ACCYLSB             = $04
            ACCXMSB             = $03
            ACCXLSB             = $02
            VERSION             = $01
            CHIPID              = $00
            address             = $80
            ''Range setting
            RANGESHIFT              = 1
            RANGEMASK               =$0E
            BWMASK                  = $F0
            BWSHIFT                 = 4
            i2cSCLLine                  = 24
            i2cSDALine                  = 25
            bmaddress           = 00_0000
            interrupt_pin         = 0                    ''for when I figure out how to do that
            led                     = 23
            
    
    VAR
      long id, temp
      long stacks[20]
      byte tempxlsb, tempxmsb, tempylsb, tempymsb, tempzlsb, tempzmsb, temptemp, temp1
      long avg_x, avg_y, avg_z, lastx, lasty, lastz
      word temp2, index
      long bmversion, pitch[numreadings], roll[numreadings], theta[numreadings]
      long tempY, tempZ, alarmcnt, noalarm
      long tempX, diff, devy, devz
      long readingsx[numreadings], readingsy[numreadings] 
      long readingsz[numreadings], devx, totalx, totaly, totalz, setp, setr, sett
      byte cogidnum
      
    CON
      hyst = 180
      numreadings = 20
      ''stabilize = 100
      lsb = 0.00019
    
    
    PUB start
      dira[led] := 1
      cognew(alarmcheck, @stacks)
      main
      
    PUB main
      'f32.start
      dira[interrupt_pin]~
    
      Numb.init
      ser.Start(115_200)
      waitcnt(clkfreq/10 + cnt)   'wait for things to settle
      i2cObject.init(i2cSDALine,i2cSCLLine)   'pin numbers of the sda and scl lines to i2cobject
      init($0, $0)
      waitcnt(clkfreq/10 + cnt)   'wait for things to settle
      
      startup   ''get set points 
          ''if ina[interrupt_pin]
            ''8192 counts at 1g  4096 at 2g
            ''outa[led] := 1
      ''alarmcheck
      index :=  0
    
        repeat
            if index => numreadings
            ''debug
              index := 0
          ReadTemp
          ''getXYZ
              ''temp := 0
            repeat while temp <> 1  ''temp is a byte, temp2 and tempX are words
              temp := i2cObject.readLocation(bmaddress, ACCXLSB) & $1      ''check data ready bit
            tempxmsb := i2cObject.readLocation(bmaddress, ACCXMSB)
            temp2 := tempxmsb << 8  ''move MSB to position
            temp2 |= i2cObject.readLocation(bmaddress, ACCXLSB)
            temp2 := temp2 >> 2
            if tempxmsb > $7F
              tempX := $FFFFC000 
            tempX |= temp2  ''knock out data ready bits
            temp := 0
      
            ''getY
            repeat while temp <> 1  ''temp is a byte, temp2 and tempX are words
                  temp := i2cObject.readLocation(bmaddress, ACCYLSB) & $1      ''check data ready bit
            tempymsb := i2cObject.readLocation(bmaddress, ACCYMSB)
            temp2 := tempymsb << 8  ''move MSB to position
            temp2 |= i2cObject.readLocation(bmaddress, ACCYLSB)
            temp2 := temp2 >> 2
            if tempymsb > $7F
              tempY := $FFFFC000 
            tempY |= temp2  ''knock out data ready bits
            temp := 0
            '' get z
            repeat while temp <> 1  ''temp is a byte, temp2 and tempX are words
              temp := i2cObject.readLocation(bmaddress, ACCZLSB) & $1      ''check data ready bit
            tempzmsb := i2cObject.readLocation(bmaddress, ACCZMSB)
            temp2 := tempzmsb << 8  ''move MSB to position
            temp2 |= i2cObject.readLocation(bmaddress, ACCZLSB)
            temp2 := temp2 >> 2
            if tempzmsb > $7F
              tempZ := $FFFFC000 
            tempZ |= temp2  ''knock out data ready bits
            'ser.clear
            'ser.dec(tempz)
            temp := 0
        
              ''gettotals
          totalx := totalx - readingsx[index]    ''subtract previous reading
          totaly := totaly - readingsy[index]
          totalz := totalz - readingsz[index]
          readingsx[index] := tempX     ''put new reading in array
          readingsy[index] := tempY
          readingsz[index] := tempZ
          totalx += readingsx[index]    ''add newest readings to totals
          totaly += readingsy[index]
          totalz += readingsz[index]
          'ser.newline
          'ser.dec(tempZ)
          ''getAvg
          avg_x := totalx / numreadings - 1
          avg_y := totaly / numreadings - 1
          avg_z := totalz / numreadings - 1
    
          ''get averages before wiping them and make them absolute
          'lastx := avg_x
          'lasty := avg_y
          'lastz := avg_z
          f32.start
          tempx := f32.ffloat(avg_x)     'make true floats out of our variables using averages
          devx := f32.fmul(lsb,tempx)
          tempy := f32.ffloat(avg_y)
          devy := f32.fmul(lsb,tempy)
          tempz := f32.ffloat(avg_z)
          devz := f32.fmul(lsb,tempz)
          ''calculate pitch roll and yaw  - pitch - arctan(ax/sqrt(ay^2+az^2))
          tempx := f32.fmul(devy,devy)
          tempy := f32.fmul(devz,devz)
          tempx := f32.fadd(tempx, tempy)
          tempx := f32.fsqr(tempx)
          tempx := f32.fdiv(devx, tempx)
          pitch[index] := f32.atan(tempx)
          pitch[index] := f32.degrees(devx)  ''end of pitch
          ''roll    arctan(ay/sqrt(ax^2+az^2)
          tempy := f32.fmul(devx,devx)
          tempx := f32.fmul(devz,devz)
          tempy := f32.fadd(tempx, tempy)
          tempy := f32.fsqr(tempy)
          tempy := f32.fdiv(devy, tempy)
          roll[index] := f32.atan(tempy)
          roll[index]:= f32.degrees(devy)  ''end of roll
          '' theta - arctan(sqrt(ax^2+ay^2)/az)
          tempx := f32.fmul(devx, devx)
          tempy := f32.fmul(devy, devy)
          tempz := f32.fadd(tempx, tempy)
          tempz := f32.fdiv(tempz, devz)
          theta[index] := f32.fsqr(tempz)
          theta[index] := f32.atan(theta[index])
          theta[index] := f32.degrees(theta[index])
          f32.stop
          'outa[led] := 0       
          ''alarmcheck
          showall
          'debug      
          index := index + 1
          tempX := 0
          tempY := 0
          tempZ := 0
    
    PUB debug
      repeat index from 0 to numreadings - 1
        Ser.str(String("X : "))
        Ser.dec(readingsx[index])
        Ser.str(String(" Y : "))
        Ser.dec(readingsy[index])
        Ser.str(String(" Z : "))
        Ser.dec(readingsz[index])
        Ser.str(String(" ID : "))
        Ser.dec(index)
        Ser.newline
    
    PUB alarmcheck  'pitch roll and theta are arrays, you can compare throughout
    'trying to run this in its own cog
     repeat
        outa[led] := 1
        waitcnt(clkfreq + cnt)  ''set bw
        outa[led] := 0
        waitcnt(clkfreq + cnt)  ''set bw
      
         
    PUB init(range, bw)  ''init bma180
      ''if(i2cObject.readLocation(bmaddress,CHIPID) <> 3)
      ''  return -1
      '' set ee_w bit
      temp := i2cObject.readLocation(bmaddress, CTRLREG0)
      temp |= $10
      i2cObject.writeLocation(bmaddress, CTRLREG0, temp)
      waitcnt(clkfreq/10 + cnt)  ''set bw
      temp := i2cObject.readLocation(bmaddress, BWTCS)
      temp1 := bw
      temp1 := (temp1<<4)
      temp &= (!BWMASK)
      temp |= temp1
      i2cObject.writeLocation(bmaddress, BWTCS, temp) ''write new data range, leave other bits the same
      waitcnt(clkfreq/10 + cnt)
      ''set range
      temp := i2cObject.readLocation(bmaddress, OLSB1)
      temp1 := range
      temp1 := (temp1<<RANGESHIFT)
      temp &= (!RANGEMASK)
      temp |= temp1
      i2cObject.writeLocation(bmaddress, OLSB1, temp) ''write new data range, leave other bits the same
      waitcnt(clkfreq/10 + cnt)
      '' turn on slope int 
      temp := i2cObject.readLocation(bmaddress, CTRLREG3)
      temp |= $44
      i2cObject.writeLocation(bmaddress, CTRLREG3, temp)
      ''i2cObject.writeLocation(bmaddress, SLOPETH, $FF)  ''100 threshold
      ''i2cObject.writeLocation(bmaddress, SLOPETAPSENSINFO, $F0) ''XYZ WILL TRIGGER
      ''temp := i2cObject.readLocation(bmaddress, TCOX)
      ''temp |= $3
      ''i2cObject.writeLocation(bmaddress, TCOX, temp) ''slope dur set to 7 readings  
      waitcnt(clkfreq/10 + cnt)
      return 0
      
    PUB ReadTemp
      temp := i2cObject.readLocation(bmaddress, TEMPERATURE)
      temptemp := (temp/2+15)*9/5+32             
                           
    PUB showall
      Ser.clear
      Ser.str(String(" X: "))
      Ser.str(fstring.floattostring(pitch[index]))
      Ser.str(String(" "))
      Ser.str(String("Y: "))
      Ser.str(fstring.floattostring(roll[index]))
      Ser.str(String(" "))
      Ser.str(String("Z: "))
      Ser.str(fstring.floattostring(theta[index]))
      'Ser.str(String(" Temp:" ))
      'Ser.dec(temptemp) 
    
    PUB setAlarm
    
    PUB startup
      ''take count or do x number of loops
      ''wait a while to let things stabilize.  put it is a variable to allow pin entry post stabilization
      index := 0
      repeat index from 0 to numreadings - 1
        Ser.str(String(" init "))
        readingsx[index] := 0
        readingsy[index] := 0
        readingsz[index] := 0
        pitch[index] := 0
        roll[index] := 0
        theta[index] :=0
    
    I have followed the examples from the manual and I cannot run alarmcheck and main at the same time. Sorry about all the extra comments and probably a lack of good comments.
  • seiko99seiko99 Posts: 9
    edited 2012-08-11 14:17
    ok, I got it to work, I told the cognew command to start main and then called alarmcheck. Still don't get why it wouldn't work before
  • JLockeJLocke Posts: 354
    edited 2012-08-11 19:02
    It appears that in 'start' you set the direction of the 'led' pin. But you don't set the direction of the pin in 'alarmcheck'. Each cog has its own DIR register and has to be set independently. Try moving 'dira[led] := 1 out of the 'start' method and into the 'alarmcheck' method.
  • seiko99seiko99 Posts: 9
    edited 2012-08-11 20:15
    That I didn't try. It works. The manual is a little weak on this fact. The loose references in the coginit chapter point to it, but I pulled several hairs out.
    Thank you. Your profile pic leads me to think you are a tesla fan. Am I correct? I am in Victoria just south of you.
  • flipborderflipborder Posts: 1
    edited 2012-09-30 09:24
    seiko,

    did you ever get your spin code working for the BMA180? I just ordered one and it'd be great if I could use your code to take some readings!
Sign In or Register to comment.