Welcome to the Parallax forum! Please feel free to ask any questions here. There are many people in this forum that are more than willing to help.
The current code example on our product page is designed to work with the BS2p/pe/px. Below I have modified it so that it will work with the BS2/e/sx
' {$STAMP BS2sx}
' {$PBASIC 2.5}
' ============================================================================
' HMC5883L_Demo.bs2 - This is a Demo program for the HMC5883L Compass
' Module (PN: 29133). It displays the compass heading north, east, south and west
' clockwise from north.
'
' Author.... (C) John A. Williams
'
' Beau Schwabe (12-11-2013)
' Adapted from a BS2p/pe/px only version to be compatible with ALL BS2 versions
' ============================================================================
' -----[ Pins/Constants/Variables ]-------------------------------------------
SDA PIN 0 'P0 transceives to/from SDA
SCL PIN 1 'P1 sends clock pulses
WRITE_DATA CON $3C 'Used to perform a Write operation
READ_DATA CON $3D 'Used to perform a Read operation
MODE CON $02 'Read/Write Register, Selects the operating mode. Default = Single measurement
'Send $3C $02 $00 on power up to change to continuous measurement mode.
X_MSB CON $03 'Read Register, Output of X MSB 8-bit value.
X_LSB CON $04 'Read Register, Output of X LSB 8-bit value.
Z_MSB CON $05 'Read Register, Output of Z MSB 8-bit value.
Z_LSB CON $06 'Read Register, Output of Z LSB 8-bit value.
Y_MSB CON $07 'Read Register, Output of Y MSB 8-bit value.
Y_LSB CON $08 'Read Register, Output of Y LSB 8-bit value.
Cont_Mode CON $0
SCALEFACTOR CON 4081 'SCALEFACTOR = 65536 * (255/4095) = 4081
SCALEOFFSET CON 2048 'The output range of the HMC5883L is -2048 to 2047; therefore to get a 0 (LOWRANGE)to 4095 range
'to 4095 (HIGHRANGE) range 2048 must be added to the sensor value.
ATNFACTOR CON 127 'the ATN function input range is -127 TO 127. In order to get the positive scaled range of
'0 to 255 into the range of -127 to 127, 127 must be subtracted.
LOWRANGE CON 0 'see SCALEOFFSET
HIGHRANGE CON 4095 'see SCALEOFFSET
Ack CON 0 ' acknowledge bit
Nak CON 1 ' no ack bit
X VAR Word 'x sensor measured value
Y VAR Word 'y sensor measured value
Z VAR Word 'z sensor measured value
brads VAR Word 'angle in brads
degr VAR Word 'angle in degrees
azimuth VAR Word 'value calculated using X ATN -Y
i2cWork VAR Byte ' work byte for TX routine
slvAddr VAR Byte ' slave address
i2cAck VAR Bit ' Ack bit from device
addrLen VAR Nib ' 0, 1 or 2
devAddr VAR Word ' address in device
i2cData VAR Byte ' data to/from device
' -----[ Main Routine ]-------------------------------------------------------
PAUSE 10 ' Give sensor needed power up time
'I2COUT Pin, SlaveID , { Address { \LowAddress }, } [ OutputData ]
'I2COUT SDA, WRITE_DATA, MODE, [Cont_Mode] ' Send continuous output command
slvAddr = WRITE_DATA
addrLen = 1
devAddr = MODE
i2cData = Cont_Mode
GOSUB Write_Byte
DO
GOSUB GetRawReading ' Get RAW Compass reading
'RAW X,Y,Z data...
DEBUG HOME, "RAW X,Y,Z", CR
DEBUG "X = ",11, SDEC x, CR
DEBUG "Y = ",11, SDEC y, CR
DEBUG "Z = ",11, SDEC z, CR ' Print angle
DEBUG CR
'Calculate Azimuth
GOSUB GetAzimuth
'Calculated Heading in Degrees
brads = azimuth 'get angle
degr = brads */ 361 'convert to degrees
DEBUG CR
DEBUG "Heading in NORTH, EAST, SOUTH, and WEST clockwise from NORTH:", CLREOL, CR, CR
DEBUG "Degree " ,11, SDEC degr, CR 'display degrees
GOSUB GetHeading
LOOP
' -----[ Subroutines ]--------------------------------------------------------
GetRawReading: 'Compass module subroutine
PAUSE 400 'Wait for new data to become available
'I2CIN PIN, SlaveID, { Address { \LowAddress }, } [ InputData ]
'I2CIN SDA, READ_DATA, X_MSB, [X.HIGHBYTE] 'Read the data starting at x_msb 3
slvAddr = READ_DATA
addrLen = 1
devAddr = X_MSB
GOSUB Read_Byte
X.HIGHBYTE = i2cData
'I2CIN SDA, READ_DATA ,X_LSB, [X.LOWBYTE]
slvAddr = READ_DATA
addrLen = 1
devAddr = X_LSB
GOSUB Read_Byte
X.LOWBYTE = i2cData
'Do the same for Z and Y:
'I2CIN SDA, READ_DATA, Z_MSB, [Z.HIGHBYTE]
slvAddr = READ_DATA
addrLen = 1
devAddr = Z_MSB
GOSUB Read_Byte
Z.HIGHBYTE = i2cData
'I2CIN SDA, READ_DATA ,Z_LSB, [Z.LOWBYTE]
slvAddr = READ_DATA
addrLen = 1
devAddr = Z_LSB
GOSUB Read_Byte
Z.LOWBYTE = i2cData
'I2CIN SDA, READ_DATA, Y_MSB, [Y.HIGHBYTE]
slvAddr = READ_DATA
addrLen = 1
devAddr = Y_MSB
GOSUB Read_Byte
Y.HIGHBYTE = i2cData
'I2CIN SDA, READ_DATA ,Y_LSB, [Y.LOWBYTE]
slvAddr = READ_DATA
addrLen = 1
devAddr = Y_LSB
GOSUB Read_Byte
Y.LOWBYTE = i2cData
RETURN
GetAzimuth:
'The output range of the HMC5883L is -2048 to 2047 and the input range of the ATN function
'is -127 to 127. I used the method to scale the output to the input discribed in Parallax's Smart
'Sensors Student Guide v1 starting on page 76.
'SCALEFACTOR = 65536 * (255/4095) = 4081
'SCALEOFFSET = 2048
'ATNFACTOR = 127
'LOWRANGE = 0
'HIGRANGE = 4095 (2048 + 2047 = 4095)
x = x + SCALEOFFSET
x = (x MIN LOWRANGE MAX HIGHRANGE) ** SCALEFACTOR - ATNFACTOR
y = y + SCALEOFFSET
y = (y MIN LOWRANGE MAX HIGHRANGE) ** SCALEFACTOR - ATNFACTOR
DEBUG "SCALED X,Y", CR
DEBUG "X = ",11, SDEC x, CR
DEBUG "Y = ",11, SDEC y, CR
azimuth = x ATN -y
DEBUG "Azimuth ",11, SDEC azimuth, CR ' display scaled azimuth
DEBUG "COS Azimuth ",11, SDEC COS azimuth, CR ' display scaled cos azimuth
RETURN
GetHeading:
'Gives a heading in North, South, East and West.
IF degr = 90 THEN
DEBUG "NORTH"
DEBUG CLREOL
ENDIF
IF degr = 0 THEN
DEBUG "EAST"
DEBUG CLREOL
ENDIF
IF degr = 270 THEN
DEBUG "SOUTH"
DEBUG CLREOL
ENDIF
IF degr = 180 THEN
DEBUG "WEST"
DEBUG CLREOL
ENDIF
RETURN
' -----[ High Level I2C Subroutines]---------------------------------------
' Random location write
' -- pass device slave address in "slvAddr"
' -- pass address bytes (0, 1 or 2) in "addrLen"
' -- register address passed in "devAddr"
' -- data byte to be written is passed in "i2cData"
Write_Byte:
GOSUB I2C_Start ' send Start
i2cWork = slvAddr & %11111110 ' send slave ID
GOSUB I2C_TX_Byte
IF (i2cAck = Nak) THEN Write_Byte ' wait until not busy
IF (addrLen > 0) THEN
IF (addrLen = 2) THEN
i2cWork = devAddr.BYTE1 ' send word address (1)
GOSUB I2C_TX_Byte
ENDIF
i2cWork = devAddr.BYTE0 ' send word address (0)
GOSUB I2C_TX_Byte
ENDIF
i2cWork = i2cData ' send data
GOSUB I2C_TX_Byte
GOSUB I2C_Stop
RETURN
' Random location read
' -- pass device slave address in "slvAddr"
' -- pass address bytes (0, 1 or 2) in "addrLen"
' -- register address passed in "devAddr"
' -- data byte read is returned in "i2cData"
Read_Byte:
GOSUB I2C_Start ' send Start
IF (addrLen > 0) THEN
i2cWork = slvAddr & %11111110 ' send slave ID (write)
GOSUB I2C_TX_Byte
IF (i2cAck = Nak) THEN Read_Byte ' wait until not busy
IF (addrLen = 2) THEN
i2cWork = devAddr.BYTE1 ' send word address (1)
GOSUB I2C_TX_Byte
ENDIF
i2cWork = devAddr.BYTE0 ' send word address (0)
GOSUB I2C_TX_Byte
GOSUB I2C_Start
ENDIF
i2cWork = slvAddr | %00000001 ' send slave ID (read)
GOSUB I2C_TX_Byte
GOSUB I2C_RX_Byte_Nak
GOSUB I2C_Stop
i2cData = i2cWork
RETURN
' -----[ Low Level I2C Subroutines]----------------------------------------
' *** Start Sequence ***
I2C_Start: ' I2C start bit sequence
INPUT SDA
INPUT SCL
LOW SDA
Clock_Hold:
DO : LOOP UNTIL (SCL = 1) ' wait for clock release
RETURN
' *** Transmit Byte ***
I2C_TX_Byte:
SHIFTOUT SDA, SCL, MSBFIRST, [i2cWork\8] ' send byte to device
SHIFTIN SDA, SCL, MSBPRE, [i2cAck\1] ' get acknowledge bit
RETURN
' *** Receive Byte ***
I2C_RX_Byte_Nak:
i2cAck = Nak ' no Ack = high
GOTO I2C_RX
I2C_RX_Byte:
i2cAck = Ack ' Ack = low
I2C_RX:
SHIFTIN SDA, SCL, MSBPRE, [i2cWork\8] ' get byte from device
SHIFTOUT SDA, SCL, LSBFIRST, [i2cAck\1] ' send ack or nak
RETURN
' *** Stop Sequence ***
I2C_Stop: ' I2C stop bit sequence
LOW SDA
INPUT SCL
INPUT SDA
RETURN
Would the same code work with good ol' BS2? I've ordered the same compass and have a Boe-Bot where I could try it when the customs releases the package...
DONNO: Works, but "I2C_Start:" is missing "RETURN".
This is correct, the START sequence sets the SDA and SCL to inputs. SDA is made LOW to initiate a START(transition from HIGH to LOW). The pullup resistors on the I2C line would normally make both the SDA and SCL HIGH, but if another device on the line is still talking the SCL will remain LOW. The START sequence falls through to "Clock Hold" where it remains until the SCL line goes HIGH.
banjo: "Would the same code work with good ol' BS2?"
Yes, this is what I tested the code it with.
Comments
Welcome to the Parallax forum! Please feel free to ask any questions here. There are many people in this forum that are more than willing to help.
The current code example on our product page is designed to work with the BS2p/pe/px. Below I have modified it so that it will work with the BS2/e/sx
Thanks..
Are you sure?
I don't use Stamps much but I think "Clock_Hold" is part of the start subroutine.
BTW, The Propeller is not a Stamp.
Works, but "I2C_Start:" is missing "RETURN".
This is correct, the START sequence sets the SDA and SCL to inputs. SDA is made LOW to initiate a START(transition from HIGH to LOW). The pullup resistors on the I2C line would normally make both the SDA and SCL HIGH, but if another device on the line is still talking the SCL will remain LOW. The START sequence falls through to "Clock Hold" where it remains until the SCL line goes HIGH.
banjo:
"Would the same code work with good ol' BS2?"
Yes, this is what I tested the code it with.