; *******************************
; ***** Sample Header File *****
; *******************************
; Filename: rfCompass.bas
; Date:
; File Version:
; Written by: Chris Rowland
; Function:
; Last Revision:
; Target PICAXE: 14M2
; *******************************
; This uses a HMC4883L compass module to read the compass heading
; and transmit it using a 433MHz transmitter.
; The heading in degrees, a compass valid flag and the power supply
; voltage in 1/10V are transmitted.
; The program reduces the checking rate and stops transmitting
; the position if the heading has not changed, eventually only
; checking the position every minute.
; This should mean that the power requirement, from 2 AA batteries,
; should be low enough that there's no need for a switch and it
; should run continuously.
#picaxe 14M2
; define this to enable sending of data through the serial port.
;#define TEST
; pin definitions
{
; transmitter pins
Symbol txEnable = c.1
Symbol txData = c.0
; shutter pin. pulled high when the shutter is open
symbol shutter = c.4
symbol shutterPin = pinC.4
symbol shutterMask = %0001110000100110
; the HMC4883L uses sda and scl, these are pins b.3.and b.4
}
; registers
{
; Temporary registers
; W0 to W3 / B0 to B7 are only used in
; a subroutine
; compass vectors as words
symbol X = w4
symbol Y = w5
symbol Z = w6
; byte equivalents
symbol XL = b8
symbol XH = b9
symbol YL = b10
symbol YH = b11
symbol ZL = b12
symbol ZH = b13
; 1 if the compass data is valid, 0 if not
; (could be a bit)
symbol dataValid = b26
; count the number of times since the heading changed
symbol changeCount = w7
; compass heading in degrees
symbol heading = w8
symbol lastHeading = s_w1
symbol headingTolerance = w9
}
; Init
{
init:
low txEnable
low txData
low b.5
low b.1
low b.2
low c.2
low c.4
pause 60
call initCompass
pause 20
#ifdef TEST
sertxd ("hello",cr,lf)
sertxd ("X, Y, Z, hdg",cr, lf)
#endif
; enable weak pullups on all unconnected pins
;pullup shutterMask
disablebod
headingTolerance = 1
;==========================================================
}
; Main code loop
{
main:
setfreq M4
gosub readCompass
#ifdef TEST
sertxd(",", #changeCount, cr,lf)
gosub txXYZ
#endif
if dataValid = 1 then
#ifdef TEST
sertxd (",",#heading)
#endif
inc changeCount
; has the heading changed?
W0 = heading - lastheading
; Handle the wrap round 360 and remove the sign
select case w0
case 181 to 360
W0 = 360 - W0
case $ff4c to $ffff ; -1 to -180
W0 = -w0
case $fe98 to $ff4b ; -181 to -360
W0 = W0 + 360
end select
if W0 > headingTolerance then
; the heading has changed, send the position
gosub TransmitData
lastHeading = heading
changeCount = 0
headingTolerance = 1
else if changeCount < 5 then ; send a burst of positions
gosub TransmitData
endif
else
#ifdef TEST
sertxd (",?", cr, lf)
#endif
endif
setfreq k250 ; I get resets to init: if this is set to k31, setting to k250
nap 2
if changeCount < 2400 then
w0 = changeCount % 10
if w0 = 0 then gosub TransmitData
;goto main
; reduce the frequency to minimise the power consumption
else if changeCount < 9600 then
; after 5 minutes increase the tolerance but
; continue to check frequently
headingTolerance = 2
else
; after 20 mins we only check every minute to see if
; the heading has changed. When the heading changes
; we revert to the faster checks. This should hopefully
; keep the power consumption low enough when the system
; is inactive that we don't need to have a battery switch.
headingTolerance = 5
sleep 26
; wraps after about 42 days
endif
goto main
}
;==========================================================
; send data using the rf output
{
; 8 bytes of data are sent:
; hdddxbvv
; hddd is the heading in degrees ascii, bvv is the battery
; x is d if the heading is valid, x if it isn't
; voltage in 1/10 V as ascii
; e,g "h123db29" is a valid heading of 123 deg and a voltage of 2.9 V
symbol ADCON0 = $1f
TransmitData:
if dataValid = 0 then
return ; don't send anything if the compass valus is invalid
endif
; enable the ADC
peek ADCON0, b2
b2 = b2 or 1 ; set bit 0 to enable the ADC
poke ADCON0, b2
; get the supply voltage by reading the 1.024V
; adc calibration voltage using the supply voltage
; as the DAC reference.
calibadc w0
; disable the ADC to save power
peek ADCON0, b2
b2 = b2 and $FE ; clear bit 0 to disable the ADC
poke ADCON0, b2
; send XYZ and raw ADC setting.
; there is a possibility that the 'X' header byte could
; occur in the data. This will only cause a problem if
; the data is already unsynchronised so it isn't thought
; to be a problem.
high txEnable
;setfreq M4
rfout txData,("X",XL,XH,YL,YH,ZL,ZH,b0)
;setfreq M1
low txEnable
#ifdef TEST
sertxd("*")
#endif
return;
}
;==================================================
; compass control
{
; The compass module returns X,Y amd Z field strengths as 12 bit
; signed integers. The PicAxe works in unsigned arithmetic so in it's
; world negative numbers have the sign bit set and so are large.
; This code takes account of this and also does some scaling to avoid
; 16 bit multiplies and divides from overflowing.
; compass module I2C address
symbol HMC5883_I2C = $3c
initCompass:
hi2csetup i2cmaster, HMC5883_I2C, i2cslow, i2cbyte
pause 100
#ifdef TEST
;sertxd ("hi2cin",cr,lf)
;hi2cin $0A, (b0,b1,b2)
;sertxd ("id:",b0,b1,b2,cr,lf)
#endif
; 15 samples/sec, normal bias, gain 1, sleep mode
; gain 1 seems OK as long as the sensor is flat
hi2cout 0, ($10, %00100000, $03)
return
;---------------------------------------------------------
; read the raw compass values into X, Y and Z words
; as signed integers in the range -2048 to 2047
; set dataValid to 0 if any of the compass values
; are invalid, to 1 if they are all valid
readCompass:
hi2cout 2,(%00000001) ; single conversion
pause 7 ; wait for conversion
hi2cin 3,(XH,XL, ZH,ZL, YH,YL) ; read compass
if X=$F000 or Y=$F000 or Z = $F000 then
dataValid = 0
return
else
dataValid = 1
goto calcHeading
endif
return
}
; Calculate compass heading
{
; calculates the heading from the raw X and Y
; words. These must be in the range -2048 to +2047
; Uses W0, W1
; returns heading
; no offset correction
; scale correction from the calib
calcHeading:
; get absolute values of X and Y
W0 = Y
call calcAbs
W1 = W0
W0 = X
call calcAbs
; divide by the largest to avoid infinities
if W0 < W1 then
; angle is 0 to 45
call calcAngle
else
; angle is 90 to 45
swap w0, w1
call calcAngle
b2 = 90 - b2
endif
; work out what quadrant it's in
; unsigned so -Ve values are large
if X <= 2048 then
if Y <= 2048 then
; X+, Y+, 0 to 90
heading = b2
else
; X+ Y-, 180 to 90
heading = 180 - b2
endif
else
if Y <= 2048 then
; X-, Y+, 360 to 270
heading = 360 - b2
else
; X-. Y-, 180 to 270
heading = b2 + 180
endif
endif
; reverse the heading for negative Zs
if Z > $8000 then
heading = 360 - heading
endif
return
}
;======================================================
; utility subroutines
{
; convert W0 to an absolute value
calcAbs:
if W0 >= $8000 then
w0 = -w0
endif
return
;===================================================
; W0 and W1 contain the x and y values
; divide W0 by W1 to get the tangent and look it up
; in the table.
; W0 and W1 must be less than 2048 and
; W1 greater than W0
; returns angle in B2 in the range 0 to 45
calcAngle:
; scale W0 and W1 to get W0 100 times w1
; but avoid overflows
select case W0
case 0 to 655
W0 = W0 * 100
case 656 to 1310
W0 = W0 * 50
W1 = W1/2
else
W0 = W0 * 25
W1 = W1 / 4
end select
if W1 = 0 then
b2 = 0
return
endif
W0 = W0 / W1
; this gets us the tangent in the range 0 to 100
; corresponding to 0 to 45 degrees
; look up the angle using the low byte of
; the tangent
read b0, b2
return
; table of angles corresponding to tangents
; address is the tangent * 100, data is the
; angle in the range 0 to 45 degrees
EEPROM 0, ( 0, 1, 1, 2, 2, 3, 3, 4, 5, 5)
EEPROM 10,( 6, 6, 7, 7, 8, 9, 9,10,10,11)
EEPROM 20,(11,12,12,13,13,14,15,15,16,16)
EEPROM 30,(17,17,18,18,19,19,20,20,21,21)
EEPROM 40,(22,22,23,23,24,24,25,25,26,26)
EEPROM 50,(27,27,27,28,28,29,29,30,30,31)
EEPROM 60,(31,31,32,32,33,33,33,34,34,35)
EEPROM 70,(35,35,36,36,36,37,37,38,38,38)
EEPROM 80,(39,39,39,40,40,40,41,41,41,42)
EEPROM 90,(42,42,43,43,43,44,44,44,44,45)
EEPROM 100,(45)
}
; test function
{
#ifdef TEST
txsignedW:
if w0 < $8000 then
sertxd (#w0)
else
w0 = -w0
sertxd ("-",#w0)
endif
return
txXYZ:
w0 = X : call txsignedw
sertxd(",")
w0 = Y : call txsignedw
sertxd(",")
w0 = Z : call txsignedw
return
#endif
}