HMC5883L Magnetometer PASM OBJ
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
And this is my HMC PASM Code
Here is the zipped file
HMC5883L V1.0 PASM.zip
Thanks
Shawn
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
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?
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.
Thanks
Shawn
call #i2cStart mov i2cData, i2cDevID or i2cData, #1 mov i2cMask, #000000 call #i2cWriteAwesome!!!!!!!!!!!!!
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.