' PROGRAM: tuner_code.bs2 Revision 6/25/01 ' written by Jim Garland W8ZR ' {$stamp BS2sx} 'Required statement - specifies Stamp version ' ============================================================== ' Program controls automatic antenna tuner. This version uses a rotary solenoid to turn a 12 position ' bandswitch, and stepper motors to control two variable capacitors. In auto mode, tuner measures TX frequency, looks up corresponding ' settings in memory, and moves steppers and solenoid to the settings. The variable capacitors and switched inductor can also be ' manually controlled with optical encoders (caps) or up/down buttons (inductor). The tuner shifts to manual ' mode whenever the rotary encoders are turned. The tuner powers up by retrieving the last ' settings (automatically stored in EEPROM) and switching to auto mode. There are 4 buttons, which have dual purposes. ' All but the UP/DWN buttons have confirming beeps (short for 1 press, long if held down). ' UP/DWN Buttons: In auto mode, steps through the memories, in manual mode steps through the inductor settings. ' AUTO/SAVE Button: One press shifts tuner to automatic mode. If held down, stores current setting in memory. ' IN/OUT-RESET Button: One press toggles tuner on-line/off-line. If held down, initializes the steppers and rotary solenoid to zero. ' The display shows a 2 sec startup message and then displays the C1,C2, and L settings (top line), and the memory ' frequency (second line). A message also indicates when tuner is off-line. ' Port Connections & INPUT/OUTPUT Status ' P0: IN Encoder1 A ' P1: IN Encoder1 B ' P2: IN Encoder2 A ' P3: IN Encoder2 B ' P4: IN UP button ' OUT Speaker ' P5: IN DOWN button ' IN Reset limit detect ' P6: OUT Auto Mode LED ' P7: IN IN/OUT-RESET button ' P8: IN AUTO/SAVE button ' P9 OUT IN/OUT relay ' P10: OUT Ext.capacitor relay ' P11: OUT Rotary solenoid step ' P12: OUT Stepper2 step ' P13: OUT Stepper1 step ' P14: OUT Stepper rotation CW=0,CCW=1 ' P15: IN TX frequency detect ' P16: OUT LCD serial data '=============================================================================================== 'Default C1, C2, and L Data in EEPROM (134 bytes) ' C1 Data (134 values) stored in EEPROM addresses 0-144 ($000-$08F) DATA 16,11,14,21,31,35,23,41,21,51,101,61,19,73,1,83,73,93,77,69 '160meters (0-19) DATA 81,101,13,23,13,55,33,13,65,33,45,55,61,77,89,99,11,23,33,45,55,67,77,87,89 '80m(20-44) DATA 41,23,123,87,65,21,35,11,100,29,33,35,21,43,49 '40meters (45-59) DATA 31,101,65,19,39 '30meters (60- 64) DATA 20,85,17,93,13,101,51,11,41,21,100,51,33,13,65,33,45,29 '20 meters (65-82) DATA 17,77,87,99,11,79 '17 meters (83- 88) DATA 15,43,55,65,77,89,99,11,159 '15meters (89-97) DATA 12,98,99 '12meters (98- 100) '10 meters (99-134) DATA 101,30,10,146,20,30,35,22,40,21,50,200,60,19,70,1,80,170,90,160,100,150,110,140,120,130,250,130,14,65,33,44,204,109 ' C2 Data (134 values) stored in EEPROM addresses 145-288 ($090-$11F) DATA @$090, 16,10,46,20,30,38,22,40,24,50,100,60,18,70,2,80,17,90,60,16 '160meters (0-19) DATA 82,100,14,20,100,50,13,14,64,32,44,56,66,78,88,98,12,22,32,44,54,66,76,48,88 '80m(20-44) DATA 42,22,12,88,66,22,30,10,99,20,30,36,24,40,48 '40meters (45-59) DATA 32,100,60,18,38 '30meters (60- 64) DATA 20,80,10,90,16,100,15,10,40,20,13,50,10,14,64,34,44,28 '20 meters (65-82) DATA 17,76,88,98,22,78 '17 meters (83- 88) DATA 15,44,54,66,76,88,99,12,58 '15meters (89-97) DATA 12,12,28 '12meters (98- 100) '10 meters (101-134) DATA 10,30,10,46,20,30,35,22,40,21,50,100,60,19,70,1,80,17,90,16,96,10,100,40,20,30,25,30,14,65,33,44,24,102 ' L Data (11 values) stored in EEPROM addresses 289-304 ($120-$12F) ' and startup Addr1,stpcnt1,stpcnt2,SWcnt ($12C-$12F) DATA @$120,1,2,3,4,5,6,7,8,9,10,11,0,101,30,98,6 '======================================================================================= ' SYMBOL TABLE ' ======================== ' User Supplied Parameter vernier con 10 'encoder vernier constant (an even number, twice the vernier ratio) ' ======================== old1A var bit 'previous A-output, encoder1 old1B var bit 'previous B-output encoder1 old2A var bit 'previous A output, encoder2 old2B var bit 'previous B output, encoder2 stpcnt1 var byte 'stepper1 count stpcnt2 var byte 'stepper2 count SWcnt var byte 'rotary switch count memval var byte 'C1,C2, or L memory variable stp var byte 'step variable btnwk1 var byte 'button workspace variable btnwk2 var byte 'button workspace variable btnwk3 var byte 'button workspace variable btnwk4 var byte 'button workspace variable x var byte 'gen purpose variable y var byte 'gen purpose variable z var byte 'gen purpose variable freq var word 'frequency variable freq1 var word 'frequency variable addr1 var byte 'memory address variable (0-135) addr2 var word 'memory address variable (145-288) addr3 var word 'memory address variable (289-304) loopcnt1 var byte 'loop counter loopcnt2 var word 'loop counter N96 con $40F0 'LCD 9600 baud I con 254 'LCD instruction toggle CLR con 1 'LCD clear display DIRS=%0111111001000000 'P0-P5,P7-P8,P15 set to input, others to output '======================================================================================= ' PROGRAM BEGINS HERE 'Retrieve last stepper settings from EEPROM (stored before previous turnoff) READ $12C,addr1 READ $12D,stpcnt1 READ $12E,stpcnt2 READ $12F,SWcnt 'Initialize LCD, display opening message top: PAUSE 1000 'wait 1sec for LCD to wake up SEROUT 16,n96,[I,CLR] 'clear LCD PAUSE 1 'wait 1msec for LCD 'display program name and revision number SEROUT 16,n96,[" W8ZR EZ-Tuner",I,194,"Rev. 6/24/01"] PAUSE 2000 'show startup msg 2 secs SEROUT 16,n96,[I,CLR] 'clear LCD 'Format LCD for displaying C1, C2, L, and frequency at following locations: L1_C4(131):stpcnt1, 'L1_C14(141):stpcnt2, L2_C6(197):freq, L2_14(205):SWcnt SEROUT 16,n96,[I,128,"C1:",I,138,"C2:",I,192,"Freq:",I,203,"L:"] '========================================================================================= 'Initialize tuner to ON-LINE and AUTO mode OUT9=1 'Set mode to ON-LINE PUT 2,0 'Set mode flag to AUTO OUT6=1 'Light LED z=vernier/2 'set z at midpoint of vernier 'now go to comp_freq '========================================================================================= 'Take retrieved stepper settings, compute the frequency segment and move stepper to settings. comp_freq: 'Input addr1 and branch to corresponding band. LOOKDOWN addr1,<[20,45,60,65,83,89,98,101,135],x BRANCH x,[freq160,freq80,freq40,freq30,freq20,freq17,freq15,freq12,freq10] '========================================================================================= 'Routine measures transmitter frequency at P15. First part of routine polls P15 for 400uS to 'see if signal present. If no signal detected (x=0), skips to rd_buttons. 'Purpose is to avoid slowing down loop unless valid signal present, and to prevent measurement ' errors by ensuring signal is present when freq-measuring gate opens. 'Second part of routine measures frequency twice, with gate of 250*0.4usec=0.1sec, 'and requries both measurements to agree. Reason for two measurements is to prevent errors caused by 'tracking of modulated SSB signals. get_freq: COUNT 15,1,x 'poll for signal, return 0 if NO,low integer if YES IF x=0 THEN rd_buttons 'skip if no signal present COUNT 15,250,freq 'otherwise,measure frequency of signal COUNT 15,250,freq1 'measure it again IF NOT freq = freq1 THEN rd_buttons 'skip if measurements not the same freq=freq+(freq/1186) 'trim freq to correct for BS2sx xtal error old1A=IN0 : old1B=IN1 'update old1A and old1B to fix encoder slippage during count 'now go to freq_tune '=============================================================================================== ' Input transmit frequency from get_freq. If in amateur band, compute the memory addresses and the frequency ' segment corresponding to the addresses. Then retrieve settings from memory and move stepper to the settings. ' If frequency not in amateur band,take no action, but go back to get_freq and poll TX frequency. ' Display shows the lower end of the memory frequency segment, not the actual TX freq. ' Note that addr1 (address of C1) is computed from the get_freq TX frequency. Addr2 tracks addr1 ' but is offset by $090 (addr2=addr1+$090).Addr3 is the band index and is obtained from a band lookup table. freq_tune: LOOKDOWN freq,<[1800,2001,3500,4001,7000,7351,10100,10201,14000,14351,18068,18169,21000,21451,24890,24991,28000,29701],x BRANCH x,[get_freq,band160,get_freq,band80,get_freq,band40,get_freq,band30,get_freq,band20,get_freq,band17,get_freq,band15,get_freq,band1 2,get_freq,band10] GOTO get_freq 'if freq>29700, take no action & read the buttons band160: addr1=freq/10-180 'addr1=0-19,addr2=144-163 for 160m band, 10kHz steps freq160: LOOKUP addr1/10,[$120,$121],addr3 'addr3=288,289 for 1800-1899kHz and 1900-1999kHz freq=10*addr1+1800 'compute freq corresponding to addr1 GOTO mem_step 'retrieve settings and move steppers band80: addr1=freq/20-155 'addr1=20-44, addr2=164-188 for 80m band, 20kHz steps freq80: LOOKUP addr1/30,[$122,$123],addr3 'addr3=290,291 for 3500-3699kHz and 3700-3999kHz freq=20*addr1+3100 'compute freq corresponding to address GOTO mem_step 'retrieve settings and move steppers band40: addr1=freq/20-305 'addr1 = 45-59, addr2=189-203 for 40m band, 20kHz steps freq40: addr3=$124 'addr3=292 freq=20*addr1+6100 'compute freq corresponding to addr1 GOTO mem_step 'retrieve settings and move steppers band30: addr1=freq/20-445 'addr1=60-64, addr2=204-208 for 30m band. 20kHz steps freq30: addr3=$125 'addr3=293 \freq=20*addr1+8900 'compute freq corresponding to addr1 GOTO mem_step 'retrieve settings and move steppers band20: addr1=freq/20-635 'addr1=65-82, addr2=209-226 for 20m band, 20kHz steps freq20: addr3=$126 'addr3=294 freq=20*addr1+12700 'compute freq corresponding to addr1 GOTO mem_step 'retrieve settings and move steppers band17: addr1=freq/20-820 'addr1=83-88, addr2=227-232 for 17m band, 20kHz steps freq17: addr3=$127 'addr3=295 freq=20*addr1+16400 'compute freq corresponding to addr1 GOTO mem_step 'retrieve settings and move steppers band15: addr1=freq/50-331 'addr1=89-97, addr2=233-241 for 15m band, 50kHz steps freq15: addr3=$128 'addr3=296 freq=50*addr1+16550 'compute freq corresponding to addr1 GOTO mem_step 'retrieve settings and move steppers band12: addr1=freq/50-399 'addr1=98-100, addr2=242-244 for 12m band, 50kHz steps freq12: addr3=$129 'addr3=297 freq=50*addr1+19950 'compute freq corresponding to addr1 GOTO mem_step 'retrieve settings and move steppers band10: addr1=freq/50-459 'addr1=101-134, addr2=245-278 for 10m band, 50kHz steps freq10: addr3=$12A 'addr3=298 freq=50*addr1+22950 'compute freq corresponding to addr1 GOTO mem_step 'retrieve settings and move steppers '================================================================================================ ' Input addr1, addr3, current encoder counts, and compute addr2, ' Then retrieve the settings from memory, and move both steppers and rot.solenoid to the memory ' settings. Note that the rotary solenoid only rotates in the CW direction. ' When done, go back and check for new frequency. Also switches ext. cap. on if freq is in 160m band. mem_step: addr2=addr1+$090 'compute addr2 IF addr1<20 THEN cap_on 'turn on ext. cap if inside 160m band OUT10=0 'turn off ext. cap if outside 160m band ms1: READ addr1,memval 'retrieve memval for addr1 IF memval > stpcnt1 THEN CWmemstep1 'test for CW or CCW direction IF memval < stpcnt1 THEN CCWmemstep1 'and move stepper ms2: READ addr2,memval 'retrieve memval for addr2 IF memval > stpcnt2 THEN CWmemstep2 IF memval < stpcnt2 THEN CCWmemstep2 ms3: READ addr3,memval 'retrieve memval for addr3 IF memval > SWcnt THEN pulse3 'step rotary solenoid CW IF memval < SWcnt THEN CCWmemstep3 ' ms4: GOSUB display GOTO get_freq 'if no change, check for new frequency cap_on: OUT10=1 'switch on ext. cap GOTO ms1 'and return '================================================================================================ ' Stepper driver and rotary solenoid routines for CW and CCW rotation. No CCW routine is needed for ' rotary solenoid, since it only turns in CW direction. CWmemstep1: OUT14=0 'set stepper1 to CW GOTO pulse1 CWmemstep2: OUT14=0 'set stepper2 to CW GOTO pulse2 ' ==================================================== CCWmemstep1: OUT14=1 'set stepper1 to CCW GOTO pulse1 CCWmemstep2: OUT14=1 'set stepper2 to CCW GOTO pulse2 CCWmemstep3: memval=memval+12 'advance memval one revolution GOTO pulse3 ' ===================================================== pulse1: stp = ABS(memval-stpcnt1) 'calculate no. of steps needed FOR x = 1 TO stp PULSOUT 13,10 'send stepper to destination PAUSE 10 'wait for stepper to finish NEXT stpcnt1=memval 'update stpcnt1 GOTO ms2 pulse2: stp = ABS(memval-stpcnt2) 'calculate no. of steps needed FOR x = 1 TO stp PULSOUT 12,10 'send stepper to destination PAUSE 10 'wait for stepper to finish NEXT stpcnt2=memval 'update stpcnt2 GOTO ms3 pulse3: stp = memval-SWcnt 'calculate no. of steps needed FOR x = 1 TO stp PULSOUT 11,65000 'pulse solenoid for 52msec (65K x 0.8usec) PAUSE 100 'wait 200msec for solenoid to finish NEXT SWcnt=memval//12 'update SWcnt, (mod 12 to keep memval<12) GOTO ms4 'and return '=============================================================================================== ' Read the UP, DOWN, AUTO/SAVE, and IN-OUT/RESET buttons. If pressed, take appropriate action. ' and if not pressed, read the encoders rd_buttons: BUTTON 4,0,254,1,btnwk1,1,upbutton 'UP button pressed? BUTTON 5,0,254,1,btnwk2,1,dwnbutton 'DOWN button pressed? BUTTON 8,0,254,255,btnwk3,1,auto_save 'AUTO mode if 1 press,SAVE if held down iobtn: BUTTON 7,0,254,255,btnwk4,1,IO_reset 'Toggle IN/OUT if 1 press, RESET if held down ' The variable loopcnt1 is used to count loops for the AUTO/SAVE and IO/RESET buttons, which have ' a second function if held down.The logic flow is more complicated for the IO/RESET button, because ' one press toggles between IN-LINE and OFF-LINE, while holding the button down resets the tuner. ' The logic flow is easier for the AUTO/SAVE button, because there is no toggling of the auto mode. loopcnt1=loopcnt1+1 MAX 255 'count loops for SAVE & RESET buttons BRANCH out9,[iobtn,rd_encoders] 'if OFF-LINE read I/O button until changed '=============================================================================================== 'Auto_save BUTTON command checks status of port 8. Normally,the loop counter = 255 'when the button is first pressed, so the routine changes the mode to AUTO and resets loopcnt to 0. After a delay 'of 254 counts the button status is checked again, and if still pressed the routine branches to SAVE and stores 'the current stepper settings in EEPROM auto_save: IF loopcnt1=253 THEN save 'save if loop counter=253, otherwise set AUTO mode auto: loopcnt1=0 'reset loop counter PUT 2,0 'set mode flag to auto mode OUT6=1 'light LED FREQOUT 4,200,1000 'short beep to confirm mode change GOTO rd_encoders 'then exit save: WRITE addr1,stpcnt1 'save settings WRITE addr2,stpcnt2 WRITE addr3,SWcnt FREQOUT 4,300,800 'long beep to confirm save (3 tones) FREQOUT 4,300,1000 ' FREQOUT 4,500,1200 ' GOTO rd_encoders 'exit in auto mode '=================================================================================================== 'The IO_reset BUTTON instruction checks status of port 7. Normally,the loop counter = 255 'when the button is first pressed, so the routine toggles the IN/OUT status and resets loopcnt to 0. After a delay 'of 254 loops,the button status is checked again, and if still pressed the routine branches to RESET. IO_reset: IF loopcnt1=253 THEN reset 'reset if loop counter=254, otherwise toggle IN/OUT IF loopcnt1=255 THEN zeroloop 'zero loop counter only if starting count=255 'otherwise go to inout inout: TOGGLE 9 'Toggle port 9 (1=IN-Line, 0=OFF-Line) FREQOUT 4,200,1000 'short beep to confirm mode change BRANCH out9,[offline,online] 'read output latch and branch offline: SEROUT 16,n96,[I,192," OFF-LINE "] 'display off-line msg GOTO iobtn 'loop to I/O button until pressed online: SEROUT 16,n96,[I,192,"Freq:",I,197,DEC freq," ",I,203,"L:",I,205,DEC SWcnt," "] 'restore 2nd line of display GOTO rd_encoders 'and continue zeroloop: loopcnt1=0 GOTO inout reset: FREQOUT 4,300,800 'long beep to confirm reset(3 tones) FREQOUT 4,300,1000 ' FREQOUT 4,500,1200 ' OUT9=0 'take tuner OFF-LINE OUT14=1 'set stepper direction to CCW and goto reset1 ' ======================================================= reset1: dir5=0 ' PULSOUT 13,10 'pulse stepper1 PAUSE 20 'wait for stepper to finish IF IN5=0 THEN upstep1 'is limit reached? GOTO reset1: 'if not,loop until limit reached upstep1: TOGGLE 14 'set stepper direction to CW PULSOUT 13,10 'step one position to free up limit PULSOUT 13,10 'step second position to free up limit PAUSE 500 'TEMPORARY - delay for pushbutton limit stpcnt1=2 'set stpcnt1 to match position TOGGLE 14 'set stepper direction to CCW GOTO reset2 'now goto reset 2 (stepper2) ' ======================================================= reset2: PULSOUT 12,10 'pulse stepper2 PAUSE 20 'wait for stepper to finish IF IN5=0 THEN upstep2 'limit reached? GOTO reset2: 'if not,loop until limit reached upstep2: TOGGLE 14 'set stepper direction to CW PULSOUT 12,10 'step one position to free up limit PULSOUT 12,10 'step second position to free up limit PAUSE 500 'delay for pushbutton limit stpcnt2=2 'set stpcnt2 to match position TOGGLE 14 'set stepper directiion to CCW 'now go to reset3 (stepper 3) ' ======================================================== reset3: PULSOUT 11,65000 'pulse rotary solenoid 52 msec PAUSE 100 'wait for solenoid to catch up IF IN5=0 THEN finish 'finish if limit reached (no upstep needed) GOTO reset3 'if not,loop until limit reached finish: SWcnt=0 'set SWcnt = 0 GOTO top 'go to start of program '=============================================================================================== '=============================================================================================== rd_encoders: enc1: IF IN0=old1A AND IN1=old1B THEN enc2 'read ports P0,P1, if no change read 2nd encoder PUT 2,1 'if changed, set mode flag to Manual OUT6=0 'turn off LED IF IN1=old1A THEN CCWenc1 'test for CCW rotation and go to CCW if bits match 'if not, go to CWenc CWenc1: '=================================== ' Encoder Vernier Routine - CW z=z-1 'decrement vernier counter old1A=IN0 : old1B=IN1 'update old1A and old1B if z>0 then enc1 'loop until count reaches zero z=vernier/2 'then reset count to vernier midpoint 'and continue ' ==================================== IF stpcnt1=102 THEN enc1 'loop again if stepper at maximum CW stpcnt1=stpcnt1+1 'increment encoder count,maximum is 102 OUT14=0 'set rotation for CW GOTO pulser1 'and move stepper1 CCWenc1: '=================================== ' Encoder Vernier Routine - CCW z=z+1 'increment vernier counter old1A=IN0 : old1B=IN1 'update old1A and old1B if z0 THEN enc2 'loop until count reaches 0 z=vernier/2 'then reset count to vernier midpoint 'and continue ' ==================================== IF stpcnt2=102 THEN rd_buttons 'loop again if stepper 2 at max cw stpcnt2=stpcnt2+1 'increment encoder count,stop at 102 OUT14=0 'set rotation to CW GOTO pulser2 'and move stepper2 CCWenc2: '=================================== ' Encoder Vernier Routine - CCW z=z+1 'increment vernier count old2A=IN2 : old2B=IN3 'update old2A and old2B IF z