#picaxe 28x1
setfreq em16
#REM
VK5JST antenna analyser program based on the original 2005
code by James Tregellas, VK5JST and contributions by David
Milne, VK3DPM in 2005 and Stan Madore, VA3SMM in 2010 and
John Dekker, ZL2TTM. This code has been reworked to such an
extent that it is probably a distinct derivative work.
Copyright is now also USA (c) 2012 Bryan R Leipper as a
derivitave work but VK5JST's restrictions on use
provide a good basis for defining allowable usage.
*********************************************
LEGAL RESTRICTIONS ON PROGRAM USE re VK5JST
This program was fully protected by the provisions of the
Commonwealth Copyright Act (Australia). Although any form
of reproduction contravenes the Act, the author is not
concerned about bona fide electronic hobbyists (and
particularly radio amateurs) making single copies of it for
their own personal use. However, commercial organisations
should note that no significant part of this software can be
offered for sale or sold, either on its own or as part of an
assembly, unless a licence to do so has been obtained from
myself-James Tregellas, 14 Sheringa Drive, Morphett Vale 5162
June 2005
***************************
REVISION INFORMATION
May 2013 K1CD V 1.01 restructured, updated
June 2013 V1.02 disable auto-off, voltage sample changes
***************************
FEATURES
-battery voltage and version display on start
-compile options to monitor activity in debug terminal
- if SWR is not subject to calculation, display LC
- if oscillator kaput, like a band switch position without an
inductor, then display battery voltage with error message
#ENDREM
; ------------------------------------
; debug and verify conditional use flags for IDE
; #define WATCH_IT ; send serial debug information
; At 16 MHz clock, this uses the IDE terminal at 19200
; to report debug information.
; #define SAMPLE_DATA ; use sample data as for simulation
; Allows verification of calculation results by plugging in
; known values instead of using ADC values. This is handy
; with the emulator to check arithmetic.
; Enable code blocks for options if installed.
; There are several input pins that could be used
; to turn on or off these options like the gate
; fast or slow option else something like VK5SMM
; uses for the LC option. Or one can take different
; paths depending upon calculation results such as showing
; LC when SWR goes out of range
#define OPT_LC_ENABLED ; L/C calculator
; #define OPT_DIAGNOSTIC ; show ADC raw
; ****************************************
; variable and storage usage definitions
; ****************************************
; ** input sources **
symbol in_osc_volts = 0 ; ADC inputs
symbol in_ref_volts = 1
symbol in_load_volts = 2
symbol in_battery_volts = 3
symbol gate_select_switch = pin2 ; binary inputs
symbol in_osc_tap = pin3
; ** flags ** (w0 and b1:b0) set when condition exists
; w0 is for flags, b1:b0, bits 0-7 amd error status
; symbol isAlive = bit0 ; for auto-off ideas
symbol isSlowGate = bit1 ; slow gat (5 digit freq)
symbol isCalcSWR = bit2 ; calculate SWR
symbol isCalcLC = bit3 ; calculate L/C
symbol isLeadingZero = bit7
symbol main_loop_status = b1
; 0 if no unusual situations encountered
; 1 if error reported on line 2 and loop aborted
; frequency usually reported on line 1
; 2 if oscillator error (kaput) reported on line 2
; battery status reported on line 1
; 3 if SWR calculation failed
; calculate and report L and C on line 2
; frequency, resistance, and reactance needed for followup calculations
symbol osc_freq = w1
symbol calc_R = w2
symbol calc_X = w3
; not needed after X and R calculations so can be tromped on later
symbol osc_volts = w4 ; adc voltage readings
symbol ref_volts = w5
symbol load_volts = w6
symbol temp_R = w7 ; temp variable R
symbol temp_X = w8 ; temp variable X
symbol calc_S = w9 ; only used for SWR calculation and display
; w10 reserved for display routine variables
symbol disp_out = b21 ; character to write to display
symbol disp_ctrl = b20 ; write control variable and used
; by number format routine as input to indicate desired precision
;** variable usage **
symbol disp_val = w11 ; input to post_number routine to
; format with disp_ctrl digits precision
symbol ij = w12 ; for various calculations, subroutine output
symbol i = b25 ; bytes for loop counting
symbol j = b24
symbol kl = w13
symbol k = b26
; use 28X1 system variables set for X2 only - change to
; upper standard registers if using an X2: s_w0 to s_w4
symbol tmp_mn = s_w0
symbol tmp_op = s_w1
symbol calc_C = s_w2
symbol calc_L = s_w3
; *********** constants ******************
symbol battery_too_low = 115 ; 11.5 volts
symbol inactivity_timeout = 600 ; 10 minutes
; symbol ohm_symbol = 0xf4 ; see display character table
; symbol micro_symbol = 0xe4
symbol infinity_symbol = 0xf3
; symbol reference_impedance = 50
; EEPROM initialized for display line data that is copied
; to the scratchpad
;01234567890abcde
EEPROM 0, ("Bat=00.0v ") ; ptr = 0 - 15
EEPROM (" . MHz R=000",$f4) ; ptr = 16 - 31
EEPROM ("SWR= . X=000",$f4) ; ptr = 32 - 47
EEPROM ("00000nH 00000pF") ; ptr = 48 - 63
EEPROM ("RF OSC KAPUT ") ; ptr = 64 - 79 errors start here
EEPROM ("LOAD SHORT OPEN ") ; ptr = 80 - 95
EEPROM ("VK5JST/K1CD 1.02") ; ptr = 96 - 111
EEPROM (" ") ; ptr = 112 - 127 debug stuff
gosub reset_display_buffer ; copies EEPROM to scratchpad
#IFDEF WATCH_IT
sertxd("initialize",cr,lf) ; assume WATCH_IT defined
#ENDIF
; INITIALISE Hitachi HD44780 chip or equivalent
pins = 0 ;clear all output lines
pause 800 ;wait 200ms for lcd to reset
pins = 48 ;set to 8 bit operation
pulsout 3,4 ;send data by pulsing enable line
pause 40 ;wait 10ms
pulsout 3,4
pulsout 3,4
let pins = 32 ;set to 4 bit operation
pulsout 3,4
pulsout 3,4
let pins = 128 ;set to 2 line operation
pulsout 3,4
disp_ctrl = 14 ;screen on, cursor on operation
gosub wrins ;write instruction to lcd
disp_ctrl = 12 ; hide cursor
gosub wrins
; startup display for battery and version
gosub check_bat ; returns battery voltage in disp_val
; and also checks for low battery
ptr = 4 ; fill in display line numbers in scratch pad
disp_ctrl = 3 ; at position 4 for 3 digits
gosub post_number ; to scratchpad for display
gosub clear_display
ptr = 0 ; write line starting at position 0
gosub wr_line1 ; battery voltage
ptr = 96
gosub wr_line2 ; program identification
; set options at startup depending upon option switches
; component mode toggles on fast to slow gate
isSlowGate = gate_select_switch
isCalcSWR = 1 ; default option
; LC or other option calc based on startup switch may
; be put in here but additional option switches are
; probably a better method
pause 8000 ; display battery volts & version for 2 seconds
; auto-off activty tracking idea
; SetTimer t1s_16 ; initialize timer to count seconds
; isAlive = 0 ; for keep alive function
main_loop_status = 0 ; no errors (yet!)
;****************************************************
; ** end initialization and setup **
; ** start calculation run ** (top of loop)
;****************************************************
Begin_Loop:
; ** frequency measurement **
; 16 MHz Picaxe samples at 250us
; input is divided by 1024 or is 2.4% low - count period should be 4096
; for 1 sec - adjust to fine tune for various errors
#IFDEF SAMPLE_DATA
ij = 2000 ; count value to use
isSlowGate = 0 ; fast gate 2000=20 MHz
disp_ctrl = 4 ; if fast gate else 5 for slow
#ELSE
readadc10 in_osc_volts,osc_volts ; read 10 bit twice
readadc10 in_ref_volts,ref_volts ; then averages and scale
readadc10 in_load_volts,load_volts ; for better resolution
If isSlowGate = 1 Then
count 3,4075,ij
disp_ctrl = 5 ; 5 digit resolution
Else
count 3,407,ij
disp_ctrl = 4 ; 4 digit resolution
EndIf
; for isAlive and auto-off ideas, compare ij and osc_freq to see
; if there has been a change.
#ENDIF ; sample data
osc_freq = ij
disp_val = osc_freq
ptr = 16 ; fill in display line numbers here
gosub post_number
; *****************************************
; CALCULATE RESISTANCE AND REACTANCE
; ****************************************
; input 3 voltages. calculate R and X components of load voltage
; if slow gate, then voltages were read with 10 bits prior to
; frequency counting to allow 2 samples which are then averaged
; and reduced back down to 8 bit.
; output R and X impedance components
#IFDEF SAMPLE_DATA
osc_volts = 240
ref_volts = 239
load_volts = 24
#ELSE
; take after count voltage readings at 10 bit resolution
; average the two and scale to 8 bit.
readadc10 in_osc_volts,calc_S
readadc10 in_ref_volts,temp_R
readadc10 in_load_volts,temp_X
osc_volts = osc_volts + calc_S >> 3
ref_volts = ref_volts + temp_R >> 3
load_volts = load_volts + temp_X >> 3
#ENDIF
if osc_volts < 50 then ;no rf oscillator output to drive network
ptr = 64 ;display message "RF OSC KAPUT" at start of display line 2
gosub wr_line2
main_loop_status = 2 ; put bat voltage on line one on this error
else if ref_volts < 5 then ;no load current. Display "open" on line 2
ptr = 85 ;EEPROM ("LOAD SHORT OPEN ") ; ptr = 80 - 95
for i = 1 to 5
@ptrinc = " " ; blank out the word short
next
ptr = 80
gosub wr_line2
main_loop_status = 1
else if load_volts < 5 then ;no load volts. Display "short" on line 2
ptr = 91
for i = 1 to 4
@ptrinc = " " ; blank out the word open
next
ptr = 80
gosub wr_line2
main_loop_status = 1
EndIf
; over-range (greater than 255) values are a matter of calibration and
; 8 bit sampling so they are not checked here
#IFDEF WATCH_IT
sertxd("O=",#osc_volts," R=",#ref_volts," L=",#load_volts,cr,lf)
#ENDIF
If main_loop_status > 0 Then ; an error message was printed
; pause 2000 ; add a half second delay to loop ??
goto finish_loop ; always continue through this label
EndIf
; in complex plane, source (oscillator) volts is hypotenuse
; For resistance load only, ref+load = osc, x axis only
; For reactance load, ref+load > osc and gets bigger with
; reactance up to where load has Y or reactive component
; only and the only R is in the reference - a 90 degree triangle
; with the osc as hypotenuse and ref as base.
ij = ref_volts + load_volts
If ij <= osc_volts Then ; must all be X axis only
#IFDEF WATCH_IT
sertxd("legs shorter than hypotenuse, R only",cr,lf)
#ENDIF
temp_X = 0 ; no reactance for load
temp_R = load_volts
Else ; solve triangles for unknown sides
calc_S = osc_volts * osc_volts ;VIN squared
calc_R = ref_volts * ref_volts ;V50 squared
calc_X = load_volts * load_volts ;VOUT squared
ij = calc_R + calc_X
if calc_S < ij then ; < 90 degree triangle
#IFDEF WATCH_IT
sertxd("accute angle => X only",cr,lf)
#ENDIF
temp_X = load_volts
temp_R = 0
Else ; everything looks valid, solve for R and X
ij = calc_s - ij ; calculate resistive component of voltage
kl = 2 * ref_volts
temp_R = ij / kl ; Load Resistance Voltage Component
; calculate reactive component
ij = temp_R * temp_R ; load resistance voltage squared
calc_X = calc_X - ij ; rhs calc_X is total load voltage squared
temp_X = sqr calc_X ; reactive voltage component
EndIf
EndIf
#REM
Use ohms law to calculate resistance from voltage via current
through the reference resistor which is ref_volts/50 (50 is
the value of the reference resistance). The load reactance
components are then v=IR => R=v/I=v/(ref_volts/50) so
R=v*50/ref_volts. For dimensional analysis and units,
note that the voltages divide out leaving the reactance
components in same units as the 50 reference resistance.
That is why it is the ratios between the input voltages
that is important rather than absolute values in volts.
#ENDREM
calc_X = temp_X * 50 / ref_volts ; calc_X is load reactance
calc_R = temp_R * 50 / ref_volts ; calc_R is load resistance
disp_val = calc_R ; post resistance value in display buffer
ptr = 28 ; fill in display line numbers in scratch pad
disp_ctrl = 3 ; at position 28 for 3 digits
gosub post_number
disp_val = calc_X ; post reactance for display
ptr = 44
disp_ctrl = 3
gosub post_number
; ************************** SWR *******************************
if isCalcSWR = 1 Then ; calculate VSWR from X and R
; SWR = |A+B| / |A-B| where A and B min and max impedance values
If Calc_R < 5 Then ; SWR >> 10
tmp_mn = 0 ; can calculate if R > 0 but no reason to
Else If Calc_R < 110 AND Calc_X < 200 Then ; nominal values range
; ********** for best results, look for values in this range *************
tmp_mn = calc_R
tmp_op = calc_X
kl = 50 ; 50 ohm reference
Else If Calc_R < 350 AND Calc_X < 350 Then ; scale by 2
; either R or X above 350 goes out of range for easy calc
tmp_mn = calc_R / 2
tmp_op = calc_X / 2
kl = 25
Else ; word overflow or other problem to calculate
tmp_mn = 0
main_loop_status = 1 ; was 0 or else wouldn't get here
EndIf
If tmp_mn > 0 Then
temp_X = tmp_op * tmp_op ; reactance squared used in both A and B
temp_R = tmp_mn + kl ; resistance +/- 50 to square for A and B
temp_R = temp_R * temp_R
calc_L = temp_R + temp_X
calc_L = sqr calc_L ; A value
if tmp_mn > kl then ; no negative numbers allowed!
temp_R = tmp_mn - kl
else ; and it gets squared, anyway
temp_R = kl - tmp_mn
endif
temp_R = temp_R * temp_R
calc_C = temp_R + temp_X
calc_C = sqr calc_C ; B value
calc_S = calc_L + calc_C ; A+B (numerator)
calc_S = calc_S * 100 ; scale for hundredths display
temp_X = calc_L - calc_C ; A-B (denominator, assume A>B)
calc_S = calc_S / temp_X ; SWR
Else
main_loop_status = 1
EndIf
If main_loop_status = 0 Then ; disp_val = SWR to display
disp_val = calc_S ; post SWR value in display buffer
ptr = 36 ; fill in display line numbers in scratch pad
disp_ctrl = 3 ; at position 36 for 3 digits
gosub post_number
EndIf
EndIf
#IFDEF OPT_LC_ENABLED
#REM
resonant frequency [f0 = 1 / (2π · √(L · C))]
capacitance is inverse of [2π · f · X] in Farads
inductance [X / ( 2π · f)] in Henries
A key issue is to try to preserve the significant digits from
the frequency and reactance measures.
A common factor = scaling / (2π · frequency)
e.g. LC_prescale = 400,000/2π = 63661
scaling starts with the frequency scaling already done by hardware
and count gate time. That comes to 10^4 for a fast gate and 10^3
for a slow gate. Then there's the 2^2 and 10^5 in the LC_prescale.
Stan VA3SMM calculates it this way:
W4 = raw freq count, 2Mhz=2000, 32Mhz=32000 [osc_freq, slow gate]
W6 = reactance -- W2=resistance [calc_X]
w4=w4/100 'Decrease the freq resolution to improve the division result
w5=w6*159/w4 'Calculate L but keep the dividend value as high as possible
w5=w5*10 ' and re-adjust for the deliberate error
w3=63662/w4 'Calculate C but keep the dividend value as high as possible
w3=w3*10/w6 'It's really 400000/2*Pi then adjust for the deliberate error
w3=w3*10/4 'And again. w3 = C pF, w5 = L nH
#ENDREM
; calculate L and C if option set or SWR calculation hit error
If isCalcLC = 1 OR main_loop_status = 1 Then
If isSlowGate = 1 Then
ij = osc_freq / 100
Else
ij = osc_freq / 10
EndIf
calc_L = calc_X * 159 / ij
calc_L = calc_L * 10
calc_C = 63662 / ij
calc_C = calc_C * 10 / calc_X
calc_C = calc_C * 10 / 4
disp_val = calc_L
ptr = 48
disp_ctrl = 5
gosub post_number
disp_val = calc_X
ptr = 57
disp_ctrl = 5
gosub post_number
main_loop_status = 3 ; used just to indicate to print LC rather than SWR
EndIf
#ENDIF
; **********************************************************************
; tidy up, update LCD display, check inactivity timer and battery voltage
; and get ready for another run
finish_loop:
; write out results of calculations to display
; gosub clear_display ; maybe but avoid if not needed
If main_loop_status = 2 Then ; print battery
ptr = 0
Else ; otherwise freq and R
ptr = 16
EndIf
gosub wr_line1
If main_loop_status = 3 Then ; print LC on line 2
ptr = 48
gosub wr_line2
Else If main_loop_status = 0 Then ; print SWR line
ptr = 32
gosub wr_line2
; else an error was printed in line 2 already
EndIf
; reset everything for next run through the loop
main_loop_status = 0
#IFDEF SAMPLE_DATA
END ; simulation run does not loop
#ENDIF
; check for inactivity time-out or low voltage for
; isAlive and auto-off ideas
i = gate_select_switch
If i <> isSlowGate Then
gosub clear_display
isSlowGate = i
EndIf
Gosub reset_display_buffer
Gosub check_bat ; voltage left in display buffer for next loop
GOTO Begin_Loop
; -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#REM
******** get battery voltage and check for good battery ****
return battery voltage in disp_val
External resistors are scaled to give a resulting count of around
120 for a 12 volt battery-16K from +12volt to adc input 3 and 3K9
from adc input 3 to ground. adjust the adc count by multiplication
and then division so that the display shows the correct voltage -
#ENDREM
check_bat:
readadc in_battery_volts,disp_val
disp_val = disp_val * 103 ; ADC to voltage calibration values
disp_val = disp_val / 100 ; 3% determined empirically
; keep display battery line current for diagnostic or error use
ptr = 4 ; fill in display line numbers in scratch pad
disp_ctrl = 3 ; at position 4 for 3 digits
gosub post_number
#IFDEF WATCH_IT
sertxd("bat= ",#disp_val,"v",cr,lf)
#ENDIF
If disp_val < battery_too_low Then
gosub clear_display
ptr = 0 ; write line starting at position 0
gosub wr_line1
pause 4000
; auto-off could be invoked here
EndIf
return
;***********************************
; HD44780 LCD DISPLAY ROUTINES
;***********************************
wrchr: ;WRITE CHARACTER in disp_out
pins=disp_out&240 ;mask high nibble of disp_out
high 2 ;make sure RS is high(character mode)
pause 4
pulsout 3,4 ;send data by pulsing enable line
disp_ctrl=disp_out*16 ;put low nibble of disp_out into
pins=disp_ctrl&240 ;mask the high nibble
high 2 ;make sure RS is high
pause 4
pulsout 3,4 ;send data by pulsing enable line
Return
#REM
LCD control codes used (See Hitachi spec sheet):
1 Clear display and move to the start of the first line
2 Move the cursor and display window to the start of the first line
12 Hide cursor
14 Turn visual LCD screen (and cursor) on
128 Move cursor to the start of the first line
192 Move cursor to the start of the second line
#ENDREM
wrins: ;WRITE INSTRUCTION in disp_ctrl
pins=disp_ctrl&240 ;mask high nibble of disp_ctrl into i
low 2 ;make sure RS is low(instruction mode)
pause 4
pulsout 3,4 ;send data by pulsing enable line
disp_out=disp_ctrl*16 ;put low nibble of disp_ctrl
pins=disp_out&240 ;mask the high nibble of i
low 2 ;make sure RS is low
pause 4
pulsout 3,4 ;send data by pulsing enable line
high 2 ;back to character mode
pause 4
Return
;** ptr set to 16 characters in scratch to put on display line
wr_line1:
disp_ctrl=128
gosub wrins
goto wr_line
wr_line2:
disp_ctrl=192 ; setup to display on line 2
gosub wrins
wr_line:
for i = 1 to 16
disp_out = @ptrinc
gosub wrchr
next i
Return
clear_display:
disp_ctrl=1
gosub wrins
Return
; *********************************************
; number display and adjustment routines
; *********************************************
; ptr set to scratchpad buffer position
; disp_ctrl indicates number of desired digits,
; disp_val is value to post
; leading zeros suppression, decimal point skip,
; (assume position in field is appropriate for scaling)
; overrange infinity when number exceeds ctrl_disp digits
; **********************************************************
post_number: ; auto over-range detection for 3 - 5 digits
If disp_ctrl = 3 AND disp_val > 999 Then
disp_ctrl = disp_ctrl + 8
Else If disp_ctrl = 4 AND disp_val > 9999 Then
disp_ctrl = disp_ctrl + 8
Else If disp_ctrl = 5 AND disp_val > 55000 Then
disp_ctrl = disp_ctrl + 8
EndIf
If disp_ctrl > 5 Then ; over range signaled
disp_ctrl = disp_ctrl - 8 ; adj for # blanks to install
If disp_ctrl > 5 Then
Return ; an error in the call
endif
@ptrinc = infinity_symbol ; at start of field
For i = 2 to disp_ctrl
@ptrinc = " "
Next
Else
isLeadingZero = 0 ; set on encountering first nonzero
j = disp_ctrl
For i = 1 TO j
If @ptr = "." then
INC ptr ; increment to skip decimal points
EndIf
DEC disp_ctrl ; decrement because positions start at 0
k = disp_val DIG disp_ctrl ; most significant digit of interest
If k > 0 OR isLeadingZero = 1 Then
@ptrinc = k + "0"
isLeadingZero = 1
Else if i < j then
@ptrinc = " "
EndIf
Next
EndIf
Return
; ***************************************************
reset_display_buffer: ; copy EEPROM to scratchpad
For i = 0 to 127 ; refresh scratchpad
read i,j ; just to make sure
ptr = i ; display glitches
put i,j ; get reset to original
Next i
Return
; end of listing