//;***************************************************************************** //; HZRotor * //; Automatic rotor control firmware * //; Dr. Pedro E. Colla (LU7HZ) * //; 2012 Free for amateur uses * //;***************************************************************************** //; Excerpts of code for the rs232 serial interface taken from * //; Filename: PICrs232.asm * //; Date: 1-15-05 * //; File Version: 1 * //; Author: j baumeister * //; Company: himself * //;***************************************************************************** //; PIC configuration bits * //;***************************************************************************** // Osc Int GP4 * // Watchdog Off * // Code Protection Off * // Data Protection Off * // MCLR Off * // Power On Interrupt On * // Brown Out Detector Off * // * //;***************************************************************************** //_CONFIG _CP_OFF & _CPD_OFF & _BODEN_OFF & _MCLRE_OFF & _WDT_OFF & _PWRTE_ON & _INTRC_OSC_NOCLKOUT //;***************************************************************************** program HZRotor; //{$DEFINE DEBUG} {*Setup when testing with simulator *} //{$DEFINE DIAGNOSTIC} { Declarations section } //;***************************************************************************** //#define bit_K h'50' ; hex DA = 1200 bits/sec (218) * // ; hex 6b = 2400 bits/sec (107) * // SELECTED FOR THIS ==> ; hex 32 = 4800 bits/sec (50) <=== 4800,8,N,1 * // ; hex 17 = 9600 bits/sec (23) * // ; hex 0A =19200 bits/sec (10) * //****************************************************************************** Const bit_K : byte = 50; //value calculated with program PicLoops half_bit : byte = bit_K / 4; //1/4 loop as start bit delay CR : byte = 0x0d; NUL : byte = 0x00; NOCHAR : byte = 0xff; OK : byte = 0; MOVE : byte = 1; RUN : byte = 1; PRESS : byte = 1; RELEASE : byte = 0; STOP : byte = 0; MARGIN : byte = 2; DMARGIN : byte = MARGIN * 2; AZCAL : byte = 0; DEBOUNCE : byte = 100; EEPROMSIZE: byte = 127; CALIBRATE : byte = 0x00; MAXSIZE : byte = $08; {* Rotor callibration factors *} KAZL : Word = 18; KBZL : Word = 7; KAZH : Word = 72; KBZH : Word =556; DGAL : Word = 57; DGBL : Word = 4; DGAH : Word = 14; DGBH : Word = 77; MAXREFCAL : Word = 129; {* Other register definitions *} cw = 0 ; register; ccw = 1 ; register; rotor = 2 ; register; rxpin = 3 ; register; setauto = 4 ; register; txpin = 5 ; register; moving = 7 ; register; diagnostic= 6 ; register; bounce = 5 ; register; adcon = 4 ; register; done = 1 ; register; Label Init,bit_delay,loopback; Var {*--------------------------*} delay_cntr : Byte; {delay counter } long_cntr : Byte; {long delay counter } cnt : Byte; {bit count while rx/tx } rxchar : Byte; {received char } txchar : Byte; {transmitted char } ct : Byte; {character placeholder } cmdline : String[8]; {buffer to store command line} az,t,p,x : Byte; {azimuth handling vars } maxref,halfref: Byte; {callibration reference } {******************************************************************************} {* Rotor Control Word (RCW) *} {* (7) Rotor movement enabled (moving) *} {* (6) Diagnostig mode *} {* (5) GP4 debounce logic (bounce) *} {* (4) ADC Conversion state (adcon) *} {* (3) not used *} {* (2) not used *} {* (1) counterclockwise *} {* (0) clockwise *} {******************************************************************************} RCW : Byte; {Rotor control word } W1,W2 : Word; {Word temp variables } tmin,tmax : Byte; {Target limits } //*----------------------------------------------------------------------------* //* Boot process to initialize processor registers and program variables * //*----------------------------------------------------------------------------* Procedure BootFirmware; begin Asm bcf STATUS,RP0 ;bank 9 clrf GPIO ;clear outputs clrf INTCON ;clear interrupts movlw $07 ;turn comparator off movwf CMCON ; movlw $09 ;turn adc on and set word to left movwf ADCON0 ; ; bsf STATUS,RP0 ; bank 1 {$IFNDEF DEBUG} call $03FF ; get factory calibration value movwf OSCCAL ; set OSCCAL calibration value {$ENDIF} ;*---------[I/O Mapping]--------------------* ;| GP5 (out) (dig) pin 2 serial out | ;| GP4 (in) (dig) pin 3 set / start auto | ;| GP3 (in) (dig) pin 4 serial in | ;| GP2 (in) (an0) pin 5 rotor/reference V | ;| GP1 (out) (dig) pin 6 turn CCW rotor | ;| GP0 (out) (dig) pin 7 turn CW rotor | ;*------------------------------------------* movlw %00011100 ; TRISIO set movwf TRISIO ; all others inputs ; clrf ANSEL ; set all pins to digital mode movlw %00000100 ; ANSEL set AN2 pin 5 as analog movwf ANSEL ; clrf PIE1 ;Clear interrupts and misc registers clrf IOC ; clrf WPU ; clrf VRCON ; movlw %00001111 ;Set option_reg register movwf OPTION_REG ; ; bcf STATUS,RP0 ;switch back to bank 0 clrf GPIO ;clear I/O clrf ADRESH ;Initialize all variables clrf _RCW ; clrf _az ; clrf _t ; bsf GPIO,5 ;Set serial out high end; {$IFDEF DEBUG} GPIO.4 := 1; {$ENDIF} end; //*----------------------------------------------------------------------------* //* delay of one bit at the target speed * //* Routine and delay calculated with PicLoops.exe * //*----------------------------------------------------------------------------* Procedure bit_delay; begin {*----------------------------*} Asm {Each instruction takes 1 } movlw _bit_K {cycle or 1 uSec at 4 MHz } movwf _delay_cntr {except for decfsz which } {takes 2 instruction cycles } loop: { } nop {3 cycles per loop=3 uSecs } decfsz _delay_cntr,1 { } goto loop {Total delay is } { 50 x 3 + 4 = 154 uSecs } end; { } {*----------------------------*} end; //*----------------------------------------------------------------------------* //* delay of one bit at the target speed (porque hay dos??) * //*----------------------------------------------------------------------------* procedure long_delay; begin asm {*----------------------------*} movlw _DEBOUNCE {this is about 20 mSecs } movwf _long_cntr { } loop_R: { } call _bit_delay { } decfsz _long_cntr,1 { } goto loop_R { } end; {*----------------------------*} end; //*----------------------------------------------------------------------------* //* delay of half bit at the target speed * //* Routine and delay calculated with PicLoops.exe * //*----------------------------------------------------------------------------* procedure start_delay; begin Asm {*-----------------------------*} movlw _half_bit {this is a 1/4 bit delay } movwf _delay_cntr { } loop_R2: { } nop { } decfsz _delay_cntr,1 { } goto loop_R2 { } end; { } end; {*-----------------------------*} //*----------------------------------------------------------------------------* //* Transmit a single character over serial (blocking) * //*----------------------------------------------------------------------------* Procedure send232(c : byte); begin {*-----------------------------*} {$IFDEF DEBUG} {If debugging no point to } Exit; {actually send the character } {$ELSE} { } cnt := $08; {*-----------------------------*} txchar := c; { } GPIO.txpin := 0; {Set 8 bits to send, lower GP5 } bit_delay; {and wait for one bit length } {*-----------------------------*} Asm txloop: rrf _txchar,1 ;rotate right thru Carry bit 0 -> Carry flag btfss STATUS,C ;if carry set jump bcf GPIO,5 ;if not set clear GPIO bit 5 OFF (tx out) btfsc STATUS,C ;if carry clear jump bsf GPIO,5 ;if carry set GPIO bit 5 ON (tx out) call _bit_delay ;delay one bit (205 uSecs at 4800 bps) decfsz _cnt,1 ;loop back till complete 8 bits goto txloop ; bsf GPIO,5 ;set GPIO bit 5 ON as stop bit call _bit_delay ;wait for one stop bit end; {$ENDIF} end; //*----------------------------------------------------------------------------* //* Receive a single character (blocking while receiving) * //*----------------------------------------------------------------------------* Function get232(Var error : byte ) : byte; begin {*-----------------------------*} {$IFDEF DEBUG} {DEBUG segment } { } error := OK; {When debugging just return } result:= ';'; {; and no error to caller } Exit; {*-----------------------------*} {$ELSE} //*---------------------------------------------{-------------------------------} cnt := $08; {8 bits to receive } rxchar := NUL; {null to character } { } if GPIO.3 <> 0 then begin {Line is high? } error := NOCHAR; {Abandon } result:= NUL; { } exit; { } end; { } { } start_delay; {wait for 1/4 bit } if GPIO.3 <> 0 then begin {Still high? } error := NOCHAR; {No, abandon } result:= NUL; { } exit; { } end; { } { } Asm //----------------------------------------------{-------------------------------} receive: call _bit_delay btfss GPIO,3 ;check if GPIO bit 3 set bcf STATUS,C ;No clear Carry btfsc GPIO,3 ;check if GPIO bit 3 clear bsf STATUS,C ;No, set Carry rrf _rxchar,1 ;Rotate Carry into bit 7 rxchar decfsz _cnt,1 ;loop back till 8 bits received goto receive ; endrx: end; result := rxchar; {Result in rxchar } error := OK; {Flag no error (might be garbage)} {$ENDIF} end; //*----------------------------------------------------------------------------* //* Format and send a numeric value as a 3 digits ASCII * //*----------------------------------------------------------------------------* Procedure SendAz (b : Byte); Begin if RCW.diagnostic = 0 then begin {Normal or diagnostics? } { } if b < 5 then begin {Implements } W1 := 0; { Deg=1,8 Az - 7 (Az<103) } end else begin { Deg=7,2 Az -556 (Az>=103) } W2 := Word(az); if b < halfref then begin {Which is the specific } W1 := ((W2 * KAZL) / 10) - KBZL;{callibration of my rotor } end else begin {resistance curve (probably } W1 := ((W2 * KAZH) / 10) - KBZH;{not good) } end; { } end; { } { } if W1 < 180 then begin { } W1 := W1 + 180; { } end else begin { } W1 := W1 - 180; { } end; { } { } end else begin {If diagnostics } W1 := b; {Just send raw value received } end; { } WordToStrwithZeros(W1,cmdline); {Convert to ASCII *} { } p := 2; {Send the characters *} while p < 5 do { } begin {*ASCII numbers (5 bytes) *} Send232(cmdline[p]); {* 01234 *} inc(p); {* 00xxx (send only xxx) *} end; {* *} Send232(';'); {*Complete with ; *} cmdline := ''; {* *} end; {*---------------------------------------------------------------------------*} {* Establish max and min around target based on callibration MARGIN *} {*---------------------------------------------------------------------------*} Procedure SetTarget; begin {*------------------------------*} if t <= MARGIN then begin {In the CCW extreme? } t := MARGIN; { Don't compute less than zero } tmin := 0; { } tmax := DMARGIN; { } end else begin { } if t >= (maxref-MARGIN) then begin t := maxref - MARGIN; {No, on the CW extreme? } tmin := maxref - DMARGIN; {Don't compute past maximum } tmax := maxref; { } end else begin { } tmin := t - MARGIN; {Else compute a MARGIN band } tmax := t + MARGIN; { } end; { } end; { } if RCW.diagnostic = 1 then begin {If global diagnostics mode on } sendAz(tmin); {Print the Min-Target-Max values } sendAz(t); { } sendAz(tmax); { } end; end; {*---------------------------------------------------------------------------*} {* Process commands, parse supported commands and execute actions *} {*---------------------------------------------------------------------------*} procedure ProcessCommand; {* *} {* *} begin {* *} {* *} if strncmp(cmdline,'AI1;',4) = 0 then begin {*If command AI1; *} sendAz(az); {*Send the current azimuth *} if RCW.diagnostic = 1 then sendAZ(t); {*If in firmware check send *} Exit; {*also the current target *} end; {* *} { } if strncmp(cmdline,'AP1',3) = 0 then begin {*Most complex command *} { } p := 5; {*AP1xxx; *} x := 1; {*0123456 *} W1 := 0; { NNN } while (p >= 3) do begin {* ^p *} if (cmdline[p] >= '0') and {*Parse and take only numbers*} (cmdline[p] <= '9') then begin {* *} W1 := W1 + (x * (cmdline[p]-'0')); {* *} end else begin {* *} Exit; {*Garbage leave *} end; {* *} x := x * 10; {* *} dec(p); {*Take next and recycle *} end; {* *} {*---------------------------*} {*Start Target as 0 *} if RCW.diagnostic = 0 then begin { } { } If W1 > 180 then begin {Compute the regression model } W1 := W1 - 180; {for my "damaged" rotor. } end else begin { } W1 := W1 + 180; {If absolute linear it should } end; {be } { } if W1 < 180 then begin {360 ------ maxref } W2 := ((DGAL * W1) / 100) + DGBL; { W1 ------(maxref * W1)/360 } end else begin {Take care of overflow if } W2 := ((DGAH * W1) / 100) + DGBH; {maxref>183 cuz 183X360 } end; {exceeds 65535. In my circuit } {maxref would always be <150 } end else begin {While in diagnosis } W2 := W1; {Just use the input as target } end; { } t := lo(W2); { } SetTarget; { } if RCW.diagnostic = 1 then SendAz(Az); RCW.moving := MOVE; {*Start movement right away *} Exit; {* *} end; {* *} end; {* *} //*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=** //* Main Program Loop * //*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=** begin //*--------------------------------------------{-------------------------------*} {* Boot the firmware to start *} {*------------------------------*} BootFirmware; {$IFNDEF DEBUG} x:= 0; Repeat begin ct := EEPROM_Read(x); Send232(ct); inc(x); end; Until ((ct = 0x00) or (x>=EEPROMSIZE)); {$ENDIF} //*--------------------------------------------{-------------------------------*} {* Master Diagnostics Mode *} {* Enter mode if GP4 is pressed *} {* during initialization time *} {*------------------------------*} maxref := EEPROM_Read(CALIBRATE); {Read character from EEPROM } if GPIO.setauto = 0 then begin { } RCW.diagnostic := 1; { } ADCON0.done := RUN; {Read ADC again } while ADCON0.done = RUN do begin { } end; { } maxref := ADRESH; {Yes, then read target } while GPIO.setauto = 0 do begin { } end; { } Send232('D'); Send232('>'); EEPROM_Write(CALIBRATE,maxref); { } end; { } sendAZ(maxref); {*------------------------------*} halfref :=Word((maxref * 100) / MAXREFCAL); {Initialize serial i/o } //*--------------------------------------------{-------------------------------* {* Main mode of operation *} while true do begin {*------------------------------*} ct := Get232(p); {*Read Serial port queue *} {* *} if p = OK then begin {*If error skip *} ct := toUpper(ct); {*All characters uppercase *} if ct = CR then begin {*Replace by ";" *} ct := ';'; {*to allow for manual actions *} end; {* *} {* *} if length(cmdline) = NUL then begin {*First character received *} if (ct = 'A') or {*must be "A" or "S" or ";" *} (ct = ';') then begin {*and discard *} cmdline:=cmdline+char(ct); {* *} if ct = ';' then begin {*If only ; received then stop *} {$IFNDEF DEBUG} RCW.moving := STOP; {* *} {$ENDIF} cmdline := ''; {* *} end; {* *} end; {* *} end else begin {* *} if length(cmdline) >= MAXSIZE then{* *} begin {*Already 7 chars in buffer *} cmdline := ''; {*and no ";" received,discard *} end else begin {* *} cmdline:=cmdline+char(ct); {*Store new character *} if ct=';' then begin {*If ";" (or ) process *} ProcessCommand; {* *} cmdline := ''; {*and clean up buffer *} end; {* *} end; {* *} end; {* *} end; {* *} {*-----------------------------------------------------------------------*} {* Read the current rotor or reference position, kick auto movement *} {*-----------------------------------------------------------------------*} if RCW.adcon = STOP then begin {ADC flagged as on-going? } ADCON0.done := RUN; {No, trigger ADC process } RCW.adcon := RUN; {And flag it as on-going } end else begin { } if ADCON0.done = STOP then begin {Yes, did the ADC ended? } RCW.adcon := STOP; {Yes, flag the ADC as not going } if GPIO.setauto = 1 then begin {Is GP4 pressed? } az := ADRESH; {Not, then read is azimuth } end else begin { } long_delay; {No is a manual set of target } if GPIO.setauto = 0 then begin {Debounce after a delay } ADCON0.done := RUN; {Read ADC again } while ADCON0.done = RUN do begin { } end; { } t := ADRESH; {Yes, then read target } while GPIO.setauto = 0 do begin {Debounce } end; { } settarget; long_delay; {wait again } SendAZ(az); {Send current azimuth } RCW.moving := MOVE; {Force start moving } end; { } end; { } end; { } end; {*-----------------------------------------------------------------------*} {* Manage the actual movement of the rotor towards a new position *} {*-----------------------------------------------------------------------*} if RCW.moving = MOVE then begin {Is the rotor marked as moving? } // if RCW.diagnostic = 1 then SendAz(Az); if ((az+AZCAL) > tmax) then begin {Is Az ahead of target } GPIO.cw := STOP; {go clockwise towards target } GPIO.ccw := MOVE; { } end else begin { } if ((az+AZCAL) < tmin) then begin {Is Az behing target? } GPIO.cw := MOVE; {go counterclockwise towards } GPIO.ccw:= STOP; { } end else begin { } SendAZ(Az); {Update Azimuth upon stop } GPIO.cw := STOP; {Not, clear movement } GPIO.ccw:= STOP; { } RCW.moving:= STOP; { } end; { } end; { } end else begin {If not moving... } GPIO.cw := STOP; {Then ensure the actuators are } GPIO.ccw := STOP; {quiet } end; end; end.