Shop OBEX P1 Docs P2 Docs Learn Events
HMC5883L Magnetometer PASM OBJ — Parallax Forums

HMC5883L Magnetometer PASM OBJ

ShawnaShawna Posts: 508
edited 2013-05-17 14:30 in Propeller 1
Hey everyone,

I am trying to put together an object in PASM to read the HMC5883L magnetometer. To be honest I am not real good with PASM so I tried to tear apart Jason's ITG3200 Sport pasm object and use it for the HMC5883L. I am stuck.

I get what I perceive as good reading for the X, Y and Z axis but when I tilt the board so that the X axis climbs from a negative number to a positive number all 3 of my axis values freak out. As X goes from - 1 to 0 to 1 and beyond 1 all three axis values are garbage. I am not sure what to think. I wrote a simple program in spin to read the HMC using the Basic I2C Driver and I do not have any problems with garbage values. And the problem only occurs when the X axis crosses from negative to positive, there seems to be no problem when the other 2 axis cross over. I am not sure how to explain the problem any better. When I say garbage values all three values jump into the 29000 range and just bounce around so bad I cannot read them. If someone would be interested in giving it a once over, I would appreciate it.

This is the simple main object
CON
        _clkmode = xtal1 + pll16x                                               'Standard clock mode * crystal frequency = 80 MHz
        _xinfreq = 5_000_000


VAR
  long  X,Y,Z
   
OBJ
  HMC      : "HMC5883L V1.0 PASM"
  Debug    : "Parallax Serial Terminal"
PUB Main


  waitcnt(clkfreq/100_000 + cnt)                        'Wait while compass has time to startup.
  Debug.Start(115_200)
  HMC.Start( 17, 16 )


  Repeat


    waitcnt(clkfreq/15 + cnt)
    
    X := HMC.GetRawX
    Y := HMC.GetRawY
    Z := HMC.GetRawZ


    Debug.Clear
    Debug.Str(String("X="))
    Debug.Dec(x)
    Debug.NewLine


    Debug.Str(String("Z="))
    Debug.Dec(z)
    Debug.NewLine


    Debug.Str(String("Y="))
    Debug.Dec(y)

And this is my HMC PASM Code
CON
        _clkmode = xtal1 + pll16x                                               'Standard clock mode * crystal frequency = 80 MHz
        _xinfreq = 5_000_000


VAR
  Byte Cog


  Long mX,mY,mZ  


PUB Start( _SCL, _SDA ) : Status


  ComputeTimes
  i2cSCL := 1 << _SCL
  i2cSDA := 1 << _SDA
  Status := Cog := cognew(@Init_HMC5883L, @mX) + 1


PUB Stop
''Stops the Cog
  if Cog
    cogstop(Cog~ - 1)


PUB GetRawX
  return mx


PUB GetRawY
  return my


PUB GetRawZ
  return mz
    
PRI ComputeTimes                                       '' Set up timing constants in assembly
                                                       '  (Done this way to avoid overflow)
  i2cDataSet := ((clkfreq / 10000) *  350) / 100000    ' Data setup time -  350ns (400KHz)
  i2cClkLow  := ((clkfreq / 10000) * 1300) / 100000    ' Clock low time  - 1300ns (400KHz)
  i2cClkHigh := ((clkfreq / 10000) *  600) / 100000    ' Clock high time -  600ns (400KHz)
  i2cPause   := clkfreq / 100000                       ' Pause between checks for operations


 
DAT
        org   0


Init_HMC5883L


                        mov             p1, par                         ' Get data pointer
                        mov             pmX, p1                         ' Store the pointer to the rx var in HUB RAM
                        add             p1, #4
                        mov             pmY, p1                         ' Store the pointer to the ry var in HUB RAM
                        add             p1, #4
                        mov             pmZ, p1                         ' Store the pointer to the rz var in HUB RAM
                        add             p1, #4


                        mov             i2cTemp,i2cPause
                        add             i2cTemp,CNT                     ' Wait 10us before starting
                        waitcnt         i2cTemp,#0


                        call            #i2cReset        
                        call            #SetConfig


                        mov             loopCount, CNT
                        add             loopCount, loopDelay


:loop
                        call    #HMCReadValues


                        wrlong  imX, pmX
                        wrlong  imY, pmY
                        wrlong  imZ, pmZ


                        waitcnt loopCount, loopDelay
                        
                        jmp     #:Loop


'------------------------------------------------------------------------------
HMCReadValues
                        mov     i2cAddr, #$03          ' Address of Data Output X MSB Register
                        mov     i2cDevID, Mag_ID       ' Device ID of the HMC5883L 
                        call    #StartRead             ' Tell the I2C device we're starting


                        mov     i2cMask, i2cWordReadMask
                        test    i2cTestCarry, #0 wc     ' Clear the carry flag to make reads auto-increment        
                        call    #i2cRead                                          
                        call    #i2cRead


                        'Sign extend the 15th bit
                        test    i2cData, i2cWordReadMask     wc
                        muxc    i2cData, i2cWordMask
                        mov     imX, i2cData




                        mov     i2cMask, i2cWordReadMask
                        test    i2cTestCarry, #0 wc      ' Clear the carry flag to make reads auto-increment                       
                        call    #i2cRead
                        call    #i2cRead


                        'Sign extend the 15th bit
                        test    i2cData, i2cWordReadMask     wc
                        muxc    i2cData, i2cWordMask
                        mov     imZ, i2cData




                        mov     i2cMask, i2cWordReadMask
                        test    i2cTestCarry, #0 wc      ' Clear the carry flag to make reads auto-increment                       
                        call    #i2cRead
                        call    #i2cRead


                        'Sign extend the 15th bit
                        test    i2cData, i2cWordReadMask     wc
                        muxc    i2cData, i2cWordMask
                        mov     imY, i2cData


                        call    #i2cStop                        


HMCReadValues_Ret       ret


'------------------------------------------------------------------------------
SetConfig
:HMCSetConfig           mov     i2cDevID, Mag_ID       'Device ID for the ITG3200
                        
                        mov     i2cAddr, #0
                        mov     i2cValue, #$70        'Normal Measurement Mode, 8 samples averaged per output, 15Hz
                        call    #i2cWriteRegisterByte


                        mov     i2cAddr, #1
                        mov     i2cValue, #$A0            'Gain = 390
                        call    #i2cWriteRegisterByte


                        mov     i2cAddr, #2
                        mov     i2cValue, #$00            'Set to continous read
                        call    #i2cWriteRegisterByte
SetConfig_Ret           
                        ret


'------------------------------------------------------------------------------
StartRead
                        call    #i2cStart
                        mov     i2cData, i2cDevID
                        mov     i2cMask, #000000
                        call    #i2cWrite


                        mov     i2cData, i2cAddr
                        mov     i2cMask,#000000
                        call    #i2cWrite


                        call    #i2cStart
                        mov     i2cData, i2cDevID
                        or      i2cData, #1
                        mov     i2cMask, #000000
                        call    #i2cWrite
                                                
StartRead_Ret           ret        


'------------------------------------------------------------------------------
i2cWriteRegisterByte
                        call    #i2cStart
                        mov     i2cData, i2cDevID
                        mov     i2cMask,#000000
                        call    #i2cWrite


                        mov     i2cTime,i2cClkLow
                        add     i2cTime,cnt             ' Allow for minimum SCL low
                        waitcnt i2cTime, #0


                        mov     i2cData, i2cAddr
                        mov     i2cMask,#000000
                        call    #i2cWrite


                        mov     i2cTime,i2cClkLow
                        add     i2cTime,cnt             ' Allow for minimum SCL low
                        waitcnt i2cTime, #0


                        mov     i2cData, i2cValue
                        mov     i2cMask,#000000
                        call    #i2cWrite


                        call    #i2cStop                                                                         


i2cWriteRegisterByte_Ret
                        ret


'------------------------------------------------------------------------------
'' Low level I2C routines.  These are designed to work either with a standard I2C bus
'' (with pullups on both SCL and SDA) or the Propellor Demo Board (with a pullup only
'' on SDA).  Timing can be set by the caller to 100KHz or 400KHz.


'------------------------------------------------------------------------------
'' Do I2C Reset Sequence.  Clock up to 9 cycles.  Look for SDA high while SCL
'' is high.  Device should respond to next Start Sequence.  Leave SCL high.


i2cReset                andn    dira,i2cSDA             ' Pullup drive SDA high
                        mov     i2cBitCnt,#9            ' Number of clock cycles
                        mov     i2cTime,i2cClkLow
                        add     i2cTime,cnt             ' Allow for minimum SCL low
:i2cResetClk            andn    outa,i2cSCL             ' Active drive SCL low
                        or      dira,i2cSCL            
                        waitcnt i2cTime,i2cClkHigh
                        or      outa,i2cSCL             ' Active drive SCL high
                        or      dira,i2cSCL
                        andn    dira,i2cSCL             ' Pullup drive SCL high
                        waitcnt i2cTime,i2cClkLow       ' Allow minimum SCL high
                        test    i2cSDA,ina         wz   ' Stop if SDA is high
              if_z      djnz    i2cBitCnt,#:i2cResetClk ' Stop after 9 cycles
i2cReset_ret            ret                             ' Should be ready for Start      


'------------------------------------------------------------------------------
'' Do I2C Start Sequence.  This assumes that SDA is a floating input and
'' SCL is also floating, but may have to be actively driven high and low.
'' The start sequence is where SDA goes from HIGH to LOW while SCL is HIGH.


i2cStart
                        or      outa,i2cSCL             ' Active drive SCL high
                        or      dira,i2cSCL
                        or      outa,i2cSDA             ' Active drive SDA high
                        or      dira,i2cSDA
                        
                        mov     i2cTime,i2cClkHigh
                        add     i2cTime,cnt             ' Allow for bus free time
                        waitcnt i2cTime,i2cClkLow


                        andn    outa,i2cSDA             ' Active drive SDA low
                        waitcnt i2cTime,#0


                        andn    outa,i2cSCL             ' Active drive SCL low
i2cStart_ret            ret                             


'------------------------------------------------------------------------------
'' Do I2C Stop Sequence.  This assumes that SCL is low and SDA is indeterminant.
'' The stop sequence is where SDA goes from LOW to HIGH while SCL is HIGH.
'' i2cStart must have been called prior to calling this routine for initialization.
'' The state of the (c) flag is maintained so a write error can be reported.


i2cStop
                        or      outa,i2cSCL             ' Active drive SCL high


                        mov     i2cTime,i2cClkHigh
                        add     i2cTime,cnt             ' Wait for minimum clock low
                        waitcnt i2cTime,i2cClkLow


                        or      outa,i2cSDA             ' Active drive SDA high
                        waitcnt i2cTime,i2cClkLow
                        
                        andn    dira,i2cSCL             ' Pullup drive SCL high
                        waitcnt i2cTime,i2cClkLow       ' Wait for minimum setup time


                        andn    dira,i2cSDA             ' Pullup drive SDA high
                        waitcnt i2cTime,#0              ' Allow for bus free time


i2cStop_ret             ret


'------------------------------------------------------------------------------
'' Write I2C data.  This assumes that i2cStart has been called and that SCL is low,
'' SDA is indeterminant. The (c) flag will be set on exit from ACK/NAK with ACK == false
'' and NAK == true. Bytes are handled in "little-endian" order so these routines can be
'' used with words or longs although the bits are in msb..lsb order.


i2cWrite                mov     i2cBitCnt,#8
                        mov     i2cTime,i2cClkLow
                        add     i2cTime,cnt             ' Wait for minimum SCL low
:i2cWriteBit            waitcnt i2cTime,i2cDataSet


                        test    i2cData,i2cMask    wz
              if_z      or      dira,i2cSDA             ' Copy data bit to SDA
              if_nz     andn    dira,i2cSDA
                        waitcnt i2cTime,i2cClkHigh      ' Wait for minimum setup time
                        or      outa,i2cSCL             ' Active drive SCL high


                        waitcnt i2cTime,i2cClkLow
                        
                        andn    outa,i2cSCL             ' Active drive SCL low
                        
                        ror     i2cMask,#1              ' Go do next bit if not done
                        djnz    i2cBitCnt,#:i2cWriteBit
                        
                        andn    dira,i2cSDA             ' Switch SDA to input and
                        waitcnt i2cTime,i2cClkHigh      '  wait for minimum SCL low
                        
                        or      outa,i2cSCL             ' Active drive SCL high


                        waitcnt i2cTime,i2cClkLow       ' Wait for minimum high time
                        
                        test    i2cSDA,ina         wc   ' Sample SDA (ACK/NAK) then
                        andn    outa,i2cSCL             '  active drive SCL low
                        andn    outa,i2cSDA             '  active drive SDA low
                        or      dira,i2cSDA             ' Leave SDA low
                        rol     i2cMask,#16             ' Prepare for multibyte write
                        
                        waitcnt i2cTime,#0              ' Wait for minimum low time
                        
i2cWrite_ret            ret


'------------------------------------------------------------------------------
'' Read I2C data.  This assumes that i2cStart has been called and that SCL is low,
'' SDA is indeterminant.  ACK/NAK will be copied from the (c) flag on entry with
'' ACK == low and NAK == high.  Bytes are handled in "little-endian" order so these
'' routines can be used with words or longs although the bits are in msb..lsb order.


i2cRead                 mov     i2cBitCnt,#8
                        andn    dira,i2cSDA             ' Make sure SDA is set to input
                        
                        mov     i2cTime,i2cClkLow
                        add     i2cTime,cnt             ' Wait for minimum SCL low
:i2cReadBit             waitcnt i2cTime,i2cClkHigh


                        or      outa,i2cSCL             ' Active drive SCL high
                        waitcnt i2cTime,i2cClkLow       ' Wait for minimum clock high
                        
                        test    i2cSDA,ina         wz   ' Sample SDA for data bits
                        andn    outa,i2cSCL             ' Active drive SCL low


              if_nz     or      i2cData,i2cMask         ' Accumulate data bits
              if_z      andn    i2cData,i2cMask
                        ror     i2cMask,#1              ' Shift the bit mask and
                        djnz    i2cBitCnt,#:i2cReadBit  '  continue until done
                        
                        waitcnt i2cTime,i2cDataSet      ' Wait for end of SCL low


              if_c      or      outa,i2cSDA             ' Copy the ACK/NAK bit to SDA
              if_nc     andn    outa,i2cSDA
                        or      dira,i2cSDA             ' Make sure SDA is set to output


                        waitcnt i2cTime,i2cClkHigh      ' Wait for minimum setup time
                        
                        or      outa,i2cSCL             ' Active drive SCL high
                        waitcnt i2cTime,i2cClkLow       ' Wait for minimum clock high
                        
                        andn    outa,i2cSCL             ' Active drive SCL low
                        andn    outa,i2cSDA             ' Leave SDA low


                        waitcnt i2cTime,#0              ' Wait for minimum low time


i2cRead_ret             ret
'------------------------------------------------------------------------------


'' Variables for the gyro routines


p1                      long    0
pT                      long    0                       ' Pointer to Temperature in hub ram
pmX                     long    0                       ' Pointer to X rotation in hub ram
pmY                     long    0                       ' Pointer to Y rotation in hub ram
pmZ                     long    0                       ' Pointer to Z rotation in hub ram


imX                     long    0                       ' Interim mX value
imY                     long    0                       ' Interim mY value
imZ                     long    0                       ' Interim mZ value


i2cWordReadMask         long    000000_00000000
i2cWordMask             long    $ffff0000
loopDelay               long    80_000_000 / 16         '16Hz 
loopCount               long    0


'' Variables for i2c routines
    
i2cTemp                 long    0
i2cCount                long    0
i2cValue                long    0
i2cDevID                long    0
i2cAddr                 long    0
i2cDataSet              long    0                       ' Minumum data setup time (ticks)
i2cClkLow               long    0                       ' Minimum clock low time (ticks)
i2cClkHigh              long    0                       ' Minimum clock high time (ticks)
i2cPause                long    0                       ' Pause before re-fetching next operation
i2cTestCarry            long    1                       ' Used for setting the carry flag
     
'' Local variables for low level I2C routines


i2cSCL                  long    0                       ' Bit mask for SCL
i2cSDA                  long    0                       ' Bit mask for SDA
i2cTime                 long    0                       ' Used for timekeeping
i2cData                 long    0                       ' Data to be transmitted / received
i2cMask                 long    0                       ' Bit mask for bit to be tx / rx
i2cBitCnt               long    0                       ' Number of bits to tx / rx


'These values are the 7 bit I2C device IDs, shifted up one so the read bit can simply be OR'd on 
Mag_ID                 long   $3C                                              






        FIT   496

Here is the zipped file
HMC5883L V1.0 PASM.zip

Thanks
Shawn

Comments

  • JasonDorieJasonDorie Posts: 1,930
    edited 2013-05-15 16:58
    Shawn - I have a version of my ITG3200 code that reads the HMC magnetometer. I'm at the office right now, but I can post the code later tonight.

    I noticed that the HMC sets one or more outputs to -4096 (I think) if anything exceeds the range you've specified, and the numbers I got didn't entirely make sense to me. I was able to correct it by increasing the field strength range value. I think the default is 1.3, but I had to go up to 4... I think? (this is from memory) Could that explain the results you're getting?
  • StephenMooreStephenMoore Posts: 188
    edited 2013-05-15 17:21
    In order to read data doesn't the device ID need to be $3D ($1E << 1 + $01)?
  • ShawnaShawna Posts: 508
    edited 2013-05-15 18:14
    Hey Jason,
    I do not think the field strength setting is the problem. The field strength is a gain that is applied to all 3 axis. If I understand the data sheet correctly no matter what the gain is set to, the output is always between -2048 and 2047. If the output overflows past this range it outputs -4096 until another valid reading has been measured. I had this problem with my Basic I2C driver version of the code. As soon as I changed the gain the problem went away.

    I paused the PST window while I was playing with the sensor. When my x axis would increase greater than 1, the values flaked out. As I was pausing and un-pausing the PST window it seemed like every other displayed value was reasonable. The bad values started at 27000 and increased as I tilted the magnetometer further. I am pretty sure that I messed something up in the PASM code but I don't see my mistake. So the gist of it is when x > 1 every other value seems to be reasonable, it may not be a 50 to 50 ratio but I think I am getting valid readings.
    I don't really understand the numbers completly either, but if you put the x and y axis values in ATAN2 calculator you get a value of 0 - 359 degrees.

    If you could post the ITG code, that would be awesome. I want to figure out what is wrong with my code (which is really a poorly done translation of your code), being able to look at your code I bet I can figure out where I went wrong. Actually when I started writing this post, the code was not working at all. An when I was ready to hit the post quick reply button I noticed a problem with my scl sda lines. So the post went from hey fix my code it doesn't work at all, to hey fix my code it kind of works.:smile:

    Thanks
    Shawn
  • JasonDorieJasonDorie Posts: 1,930
    edited 2013-05-15 18:16
    Stephen - It does, but the driver takes care of it. The OR i2CData, #1 in the StartRead code masks in the low-1-bit for the read.

                            call    #i2cStart
                            mov     i2cData, i2cDevID
                            or      i2cData, #1
                            mov     i2cMask, #000000
                            call    #i2cWrite
    
  • ShawnaShawna Posts: 508
    edited 2013-05-15 18:29
    Ya I didn't fully understand the Device ID part of the code but I know from experience with the Basic I2C Driver object that you use the same device Id for the read and write and the driver takes care of the read/write bit. That used to really screw me up.
  • StephenMooreStephenMoore Posts: 188
    edited 2013-05-15 18:51
    Got it.......
  • JasonDorieJasonDorie Posts: 1,930
    edited 2013-05-16 13:34
    I've attached the version of the ITG3200 object I've modified to read all three sensors on the SparkFun 9DOF sensor stick. (ITG3200, ADXL345, HMC5883)
  • ShawnaShawna Posts: 508
    edited 2013-05-16 15:13
    Thank you Jason
  • ShawnaShawna Posts: 508
    edited 2013-05-17 09:20
    Just wrote a short spin program to uses Jason's 9DOF-PASM obj. If my math is right I can read all 9 raw values out of the obj in 181.8uS. It was taking me 15mS just to read the ACC and MAG with a Spin I2C Driver.

    Awesome!!!!!!!!!!!!!
  • JasonDorieJasonDorie Posts: 1,930
    edited 2013-05-17 14:30
    Lol - I had no idea yours was written in Spin. Yeah, PASM is a LOT faster. The driver actually has to issue a bunch of waits to make sure it doesn't go faster than the 400kHz the device allows.

    I think the Spin -> PASM difference is about 400:1 (approximate ballpark) though that does depend on what you're actually doing. If you're doing a lot of multiply / divide operations, you'll be spending more time in native PASM, so the ratio will be somewhat lower.

    Anyway, happy I could help. :)
Sign In or Register to comment.