HMC5883L Magnetometer PASM OBJ
Shawna
Posts: 508
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
Awesome!!!!!!!!!!!!!
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.