;********************************************************************** ; This file is a basic code template for assembly code generation * ; on the PICmicro PIC16F874. This file contains the basic code * ; building blocks to build upon. * ; * ; If interrupts are not used all code presented between the ORG * ; 0x004 directive and the label main can be removed. In addition * ; the variable assignments for 'w_temp' and 'status_temp' can * ; be removed. * ; * ; Refer to the MPASM User's Guide for additional information on * ; features of the assembler (Document DS33014). * ; * ; Refer to the respective PICmicro data sheet for additional * ; information on the instruction set. * ; * ; Template file assembled with MPLAB V4.00 and MPASM V2.20.00. * ; * ;********************************************************************** ; * ; Filename: stepvalv.asm * ; Date: 12/7/01 * ; File Version: 1 * ; * ; Author: Bill O'Donnell * ; Company: UNLV Physics Dept * ; * ; * ;********************************************************************** ; * ; Files required: p16f874.inc * ; * ; set oscillator to HS (use 4MHz XTAL W/15pf caps) * ; turn off low voltage programming * ; power up timer and watch dog timer ON * ; * ;********************************************************************** ; * ; Notes: * ; * ; This program controls a stepper motor connected to a water * ; valve on Sheltons laser. It measures the water temp (LM35) * ; and temp set point (trim pot) and compares the two. * ; Current valve position and valve limits are also measured * ; with the onboard A/D. Water flow is adjusted such that the * ; solenoid valve on the laser remains open at all times * ; (no water hammer). * ; * ;********************************************************************** list p=16f874 ; list directive to define processor #include ; processor specific variable definitions __CONFIG _CP_OFF & _WDT_ON & _BODEN_ON & _PWRTE_ON & _HS_OSC & _WRT_ENABLE_ON & _LVP_ON & _CPD_OFF ; '__CONFIG' directive is used to embed configuration data within .asm file. ; The lables following the directive are located in the respective .inc file. ; See respective data sheet for additional information on configuration word. ;***** VARIABLE DEFINITIONS w_temp EQU 0x20 ; variable used for context saving status_temp EQU 0x21 ; variable used for context saving ; Note: keep all A/D memory locations in sequential order with high byte first ; Indirect addressing is used to put the A/D result in the variables below SP_TEMPH EQU 0x22 ; temp set point high byte (CH0, PIN 2) SP_TEMPL EQU 0x23 ; temp set point low byte W_TEMPH EQU 0x24 ; water temp high byte (CH1, PIN 3) W_TEMPL EQU 0x25 ; water temp low byte U_LIMH EQU 0x26 ; upper motor limit high byte (CH2, PIN 4) U_LIML EQU 0x27 ; upper motor limit low byte L_LIMH EQU 0x28 ; lower motor limit high byte (CH3, PIN 5) L_LIML EQU 0x29 ; lower motor limit low byte M_POSH EQU 0x2A ; motor position high byte (CH4, PIN 6) M_POSL EQU 0x2B ; motor position low byte CHAN EQU 0x2C ; counter for A/D channel # TEMP EQU 0x2D ; temporary variable DIFH EQU 0x2E ; temp diff high byte DIFL EQU 0x2F ; temp diff low byte TSTAT EQU 0x30 ; status reg for all calculated things AH EQU 0x31 ; number A high byte (for dif calc) AL EQU 0x32 ; number A low byte (for dif calc) BH EQU 0x33 ; number B high byte (for dif calc) BL EQU 0x34 ; number B low byte (for dif calc) STEP EQU 0x35 ; counter for motor step (0-3) SPEED EQU 0x36 ; DIFL from temp comparison (max 255) TEMP2 EQU 0x37 ; temp variable #2 ;***** CONSTANTS AND MASK DEFINITIONS TMP_HYS EQU d'10' ; temperature hysteresis (+/- counts) ; 1023 counts, 5V range, 100mV/C ~+/-0.5C MOT_HYS EQU d'10' ; motor hysteresis for limit switches ; move motor if > MOT_HYS past limit ; 1023 counts *(4V/5V), ~1/8 turn LIMDT EQU d'236' ; limit del (256-236)*2mS=40mS w/4MHz osc ENABLE EQU d'4' ; bit 4 PORTB to enable pin on H bridge ; bits used in TSTAT (status of comparisons with limit and temp pots) LLER EQU d'0' ; lower limit error (0=OK, 1=out of range) ULER EQU d'1' ; upper limit error (0=OK, 1=out of range) LLHY EQU d'2' ; 1 if limit dif > motor hysteresis ULHY EQU d'3' ; 1 if limit dif > motor hysteresis THY EQU d'4' ; 1 if temp dif > temp hysteresis, 0=OK TGL EQU d'5' ; 1 if temp >= set pt, 0 if temp < set pt DIR EQU d'6' ; dir of rotation (0 = down, 1 = up) PN EQU d'7' ; positive or negative (0 if A>B, 1 if B>A) ;********************************************************************** ORG 0x000 ; processor reset vector clrf PCLATH ; ensure page bits are cleared goto main ; go to beginning of program ORG 0x004 ; interrupt vector location movwf w_temp ; save off current W register contents movf STATUS,w ; move status register into W register bcf STATUS,RP0 ; ensure file register bank set to 0 movwf status_temp ; save off contents of STATUS register ; isr code can go here or be located as a call subroutine elsewhere bcf STATUS,RP0 ; ensure file register bank set to 0 movf status_temp,w ; retrieve copy of STATUS register movwf STATUS ; restore pre-isr STATUS register contents swapf w_temp,f swapf w_temp,w ; restore pre-isr W register contents retfie ; return from interrupt ;************************************************************************* ; set I/O directions and default values portio ; port A (RA7-RA6 unused, RA5 - RA0 inputs) bsf STATUS,RP0 ; bank 1 movlw b'00111111' movwf TRISA ; Port A all inputs bcf STATUS,RP0 ; bank 0 movlw b'00000000' movwf PORTA ; Port A all outputs low ; port B (RB7-RB5 unused outputs, RB4-RB0 outputs for stepper control) bsf STATUS,RP0 ; bank 1 movlw b'00000000' movwf TRISB ; Port B 3 inputs, 5 outputs bsf OPTION_REG,NOT_RBPU ; disable port B week pull-ups bcf STATUS,RP0 ; bank 0 movlw b'00000000' ; disable stepper motor movwf PORTB ; Port B all outputs low ; port C (all inputs) bsf STATUS,RP0 ; bank 1 movlw b'11111111' movwf TRISC ; Port C all inputs bcf STATUS,RP0 ; bank 0 movlw b'00000000' movwf PORTC ; Port C all outputs low ; port D (RD0-3 inputs, RD4-7 outputs for status LED's) bsf STATUS,RP0 ; bank 1 movlw b'00001111' movwf TRISD ; Port D all inputs bcf STATUS,RP0 ; bank 0 movlw b'00000000' movwf PORTD ; Port D all outputs low ; port E (RE7-RE3 unused, RE2-RE0 inputs) bsf STATUS,RP0 ; bank 1 movlw b'00000111' ; set for general purpose I/O movwf TRISE ; all inputs bcf STATUS,RP0 ; bank 0 movlw b'00000000' movwf PORTE ; all outputs low return ;************************************************************************* ; A/D converter set up adcon ; right justified, Vref+=Vcc, Vref-=Vss+GND, Fosc/32, use AN0-AN4 bsf STATUS,RP0 ; bank 1 bsf ADCON1,ADFM ; right justified output in ADRESH bcf ADCON1,PCFG3 ; PCFG = 0010 for AN5-AN0 enabled bcf ADCON1,PCFG2 ; with Vref+ = Vcc & Vref- = Vss bsf ADCON1,PCFG1 ; AN7-AN5 (RE2-RE0) digital I/O bcf ADCON1,PCFG0 ; ; use FOSC/32, A/D turned ON bcf STATUS,RP0 ; bank 0 bsf ADCON0,ADCS1 ; ADCS1,ADCS0 = 00 Fosc/2 bsf ADCON0,ADCS0 ; 01 = Fosc/8, 10 = Fosc/32, 11 = Frc bsf ADCON0,ADON ; turn on the A/D return ;************************************************************************* ; interrupt set up intset bcf INTCON,GIE ; disable all interrupts bcf INTCON,PEIE ; disable all peripheral interrupts return ;************************************************************************* ; timer and watchdog set up tmrset ; use prescaler with watchdog timer 0 (set for divide by 128) bsf STATUS,RP0 ; bank 1 bcf OPTION_REG,T0CS ; TMR0 uses FOSC/4 bcf OPTION_REG,T0SE ; TMR0 increments on rising edge bsf OPTION_REG,PSA ; use prescaler with watch dog timer bsf OPTION_REG,PS2 ; set MSB of prescaler bsf OPTION_REG,PS1 ; set middle bit of prescaler bsf OPTION_REG,PS0 ; set LSB of prescaler bcf STATUS,RP0 ; bank 0 ; use prescaler with timer 1 (set for divide by 2 - timer1 ON) ; timer1 is used for the delay between pulses to the stepper motor bcf T1CON,T1CKPS1 ; clear MSB of prescaler bsf T1CON,T1CKPS0 ; set LSB of prescaler bcf T1CON,T1OSCEN ; inverter off (save power) bsf T1CON,T1SYNC ; don't sync w/external clock bcf T1CON,TMR1CS ; use FOSC/4 for clock input bsf T1CON,TMR1ON ; timer 1 ON ; postscaler /16, prescaler /16, timer2 OFF) bsf T2CON,TOUTPS3 ; set MSB of postscaler bsf T2CON,TOUTPS2 ; set bit 2 of postscaler bsf T2CON,TOUTPS1 ; set bit 1 of postscaler bsf T2CON,TOUTPS0 ; set LSB of postscaler bcf T2CON,TMR2ON ; timer 2 OFF bsf T2CON,T2CKPS1 ; set MSB of prescaler bsf T2CON,T2CKPS0 ; set LSB of prescaler return ;************************************************************************* ; look up table for bipolar stepper motor ; bit #4 = 1 to enable SN754410 H-Bridge driver (disabled after the step) steptbl addwf PCL,1 ; jump to proper place in table ; AABB ; coil A & B ; +-+- ; polarity ; -------- retlw b'00011001' ; step #1 coil A pos, B neg retlw b'00011100' ; step #2 coil A pos, B pos retlw b'00010110' ; step #3 coil A neg, B pos retlw b'00010011' ; step #4 coil A neg, B neg ; this was the low power (low torque) table (1 coil energized only) ; retlw b'00011000' ; step #1 coil A positive current ; retlw b'00010100' ; step #2 coil A negative current ; retlw b'00010010' ; step #3 coil B positive current ; retlw b'00010001' ; step #4 coil B negative current ;************************************************************************* ; adjustable delay (max ~1/8 second with prescaler /2 and 4MHz Fosc) ; delay used after motor stepped to control rotation speed ; TMR1H should be set before calling delay (larger numbers = smaller del) delay bcf PIR1,TMR1IF ; clear timer1 overflow bit clrf TMR1L ; clear timer1 low byte tplp clrwdt ; clear watch dog timer btfss PIR1,TMR1IF ; check timer overflow bit goto tplp ; stay in loop if no overflow return ; if high then return ;************************************************************************* ; small delay of about 0.25ms (used to let the A/D settle between samples) ; prescaler /2 and 4MHz Fosc, check for bit #15 to go high smdel clrf TMR1L ; clear timer1 low byte clrf TMR1H ; clear timer1 high byte tplp2 clrwdt ; clear watch dog timer btfss TMR1L,7 ; check if timer bit high goto tplp2 ; if not stay in loop return ; if high then return ;************************************************************************* ; reads the 5 A/D channels in order 4, 3, 2, 1, 0 and stores the results ; using indirect addressing read_ad ; set up channel counter and ponter for indirect addressing movlw d'5' ; read 5 A/D channels (4,3,2,1,0) movwf CHAN ; counter for the channel # movlw 0x2B ; addr of last entry for A/D results movwf FSR ; goes into the pointer register ; select the A/D channel adlp decf CHAN,0 ; channels 1-5 = AD chan 0-4 movwf TEMP ; temp = W bcf ADCON0,CHS2 ; clear channel bits (chan = 0) bcf ADCON0,CHS1 bcf ADCON0,CHS0 bcf STATUS,C ; clear carry bit (for rotate) rlf TEMP,1 ; TEMP = TEMP rotated left 1 bit rlf TEMP,1 ; TEMP = TEMP rotated left 1 bit rlf TEMP,0 ; W = TEMP rotated left 1 bit iorwf ADCON0,1 ; select A/D channel ; delay - give A/D channel mux time to settle movlw d'8' ; call delay loop 8 times movwf TEMP2 ; for a total of ~2ms uptmp call smdel ; delay 0.25ms (time to settle) decfsz TEMP2,1 ; check if done 8 times goto uptmp ; if not stay in loop ; start A/D and wait for conversion to finish bsf ADCON0,GO_DONE ; start the conversion tplp3 btfsc ADCON0,GO_DONE ; wait for the conversion to finish goto tplp3 ; if not done stay in loop ; store A/D result bsf STATUS,RP0 ; bank 1 movf ADRESL,0 ; W = ADRESL bcf STATUS,RP0 ; bank 0 movwf INDF ; store W in loc pointed to by FSR decf FSR,1 ; point to next memory location movf ADRESH,0 ; W = ADRESH movwf INDF ; store W in loc pointed to by FSR decf FSR,1 ; point to next memory location ; check if all five channel are done, if not stay in loop decfsz CHAN,1 ; if not done with all 5 channels goto adlp ; stay in loop return ; else return ;************************************************************************* ; calculates the difference between two sixteen bit numbers A & B ; result in DIFH & DIFL, POSNEG=0X00 if A > B, else POSNEG=0x01 ; calculates A - B, the address of AH should be in A_ADDR with AL one ; byte higher, address for BH = B_ADDR with BL one byte higher dif ; subtract two 16 bit numbers with the result in DIFH & DIFL bcf TSTAT,PN ; assume A > B (clear PN bit) movf BL,0 ; w = BL subwf AL,0 ; w = AL - BL movwf DIFL ; store diff low byte movf BH,0 ; W = BH btfss STATUS,C ; if result neg incfsz BH,0 ; take care of the carry subwf AH,0 ; w = AH - BH movwf DIFH ; store diff high byte ; convert the two's compliment result to sign magnitude ; change all 1 -> 0 & 0 -> 1 then add one btfss DIFH,7 ; if MSB of DIFH = 0 then pos return ; and don't need to convert comf DIFH,1 ; compliment DIFH comf DIFL,0 ; W = compliment DIFL addlw d'1' ; increment (carry bit updated) movwf DIFL ; put back in DIFL btfsc STATUS,C ; if carry bit set incf DIFH,1 ; add cary to high byte bsf TSTAT,PN ; indicate A < B (set PN bit) return ;************************************************************************* ; compare motor position to limit pots and set TSTAT bits ; if motor past lower limit set LLER (out of bounds) ; if motor past upper limit set LLER (out of bounds) ; if LL dif > MOT_HYS set LLHY (if set & out of bounds move MTR) ; if UL dif > MOT_HYS set ULHY (if set & out of bounds move MTR) poschk ; check if motor position > lower limit movf M_POSH,0 ; w = motor position high byte movwf AH ; put in AH movf M_POSL,0 ; w = motor position low byte movwf AL ; put in AL movf L_LIMH,0 ; w = lower limit high byte movwf BH ; put in BH movf L_LIML,0 ; w = lower limit low byte movwf BL ; put in BL call dif ; calc A - B bcf TSTAT,LLER ; assume A > B btfsc TSTAT,PN ; if B > A then bsf TSTAT,LLER ; set lower limit error bit ; check if difference > hysteresis (important if pos > limit) bcf TSTAT,LLHY ; assume difference < hysteresis movf DIFH,1 ; update zero bit for DIFH btfss STATUS,Z ; 1=dif < 255, 0 = big dif bsf TSTAT,LLHY ; lower limit > hysteresis movf DIFL,0 ; w = difference low byte sublw MOT_HYS ; w = MOT_HYS - DIFL btfss STATUS,C ; if carry set MOT_HYS > DIFL bsf TSTAT,LLHY ; else set hysteresis bit ; check if motor position < upper limit movf U_LIMH,0 ; w = upper limit high byte movwf AH ; put in BH movf U_LIML,0 ; w = upper limit low byte movwf AL ; put in BL movf M_POSH,0 ; w = motor position high byte movwf BH ; put in AH movf M_POSL,0 ; w = motor position low byte movwf BL ; put in AL call dif ; calc A - B bcf TSTAT,ULER ; assume A > B btfsc TSTAT,PN ; if B > A then bsf TSTAT,ULER ; set upper limit error bit ; check if difference > hysteresis (important if pos > limit) bcf TSTAT,ULHY ; assume difference < hysteresis movf DIFH,1 ; update zero bit for DIFH btfss STATUS,Z ; 1=dif < 255, 0 = big dif bsf TSTAT,ULHY ; lower limit > hysteresis movf DIFL,0 ; w = difference low byte sublw MOT_HYS ; w = MOT_HYS - DIFL btfss STATUS,C ; if carry set MOT_HYS > DIFL bsf TSTAT,ULHY ; else set hysteresis bit return ;************************************************************************* ; compares the water temp to the temp set point pot ; if water temp > set point set TGL bit of TSTAT, else clear TGL bit ; if the dif > TMP_HYS set THY bit of TSTAT (1=move motor, 0=motor OK) tmpchk ; check if water temp > set point movf SP_TEMPH,0 ; w = temp set point high byte movwf AH ; put in AH movf SP_TEMPL,0 ; w = temp set point low byte movwf AL ; put in AL movf W_TEMPH,0 ; w = water temp high byte movwf BH ; put in BH movf W_TEMPL,0 ; w = water temp low byte movwf BL ; put in BL call dif ; calc A - B bcf TSTAT,TGL ; assume A > B btfsc TSTAT,PN ; if B > A then bsf TSTAT,TGL ; water temp > set point ; check if difference > hysteresis (move motor if dif > hyst) bcf TSTAT,THY ; assume difference < hysteresis movf DIFH,1 ; update zero bit for DIFH btfss STATUS,Z ; 1=dif < 255, 0 = big dif bsf TSTAT,THY ; lower limit > hysteresis movf DIFL,0 ; w = difference low byte sublw TMP_HYS ; w = TMP_HYS - DIFL btfss STATUS,C ; if carry set, TMP_HYS > DIFL bsf TSTAT,THY ; else set hysteresis bit return ;************************************************************************* ; check bits in TSTAT and move motor if necessary ; if motor past a limit by more than MOT_HYS move back a step and return ; else, if temp dif > TMP_HYS step motor in proper direction and return ; SPEED controls delay between steps (larger # = smaller delays) mtrmov ; check lower limit pot or if limit pots are reversed or too close btfss TSTAT,LLER ; check if lower limit error goto llimok ; if no error skip down btfsc TSTAT,ULER ; if both limit err bits are set return ; exit without moving motor btfss TSTAT,TGL ; if water temp > set point goto skd1 ; reverse motor direction ; if here LLER bit set so check if dif > hysteresis btfss TSTAT,LLHY ; if dif < hysteresis return ; exit without moving motor ; else move motor up one step with short delay between steps skd1 bsf TSTAT,DIR ; set motor dir for up movlw LIMDT ; use short del time between steps movwf SPEED ; put in SPEED before call to rotate call rotmot ; rotate the motor return ; and return ; check upper limit pot (no upper limit error if here) llimok btfss TSTAT,ULER ; check if upper limit error goto ulimok ; if no error skip down btfsc TSTAT,TGL ; if water temp < set point goto skd2 ; reverse motor direction ; if here ULER bit set so check if dif > hysteresis btfss TSTAT,ULHY ; if dif < hysteresis return ; exit without moving motor ; else move motor down one step with short delay between steps skd2 bcf TSTAT,DIR ; set motor dir for down movlw LIMDT ; use short del time between steps movwf SPEED ; put in SPEED before call to rotate call rotmot ; rotate the motor return ; and return ; check temperature set point (no limit errors if here) ulimok btfss TSTAT,THY ; if dif < hysteresis return ; exit without moving motor ; else step motor up or down a step with delay proportional to dif bsf TSTAT,DIR ; assume motor wants to go up btfsc TSTAT,TGL ; if water temp > set point bcf TSTAT,DIR ; set motor dir for down call rotmot ; rotate the motor (SPEED set in ; previouse call to spdset) return ;************************************************************************* ; rotate the motor one step, TSTAT,DIR sets direction of rotation ; value in SPEED controls delay between steps (larger # = smaller delays) ; the current step positon of the motor (0-3) is in STEP rotmot ; update step position (offset into lookup table for next step) btfss TSTAT,DIR ; if need to step up incf STEP,1 ; inc step counter btfsc TSTAT,DIR ; if need to step down decf STEP,1 ; dec step counter ; take care of the over flow (if 0XFF -> 3, if 4 -> 0) btfsc STEP,2 ; if STEP = 4 or 0XFF bcf STEP,2 ; now STEP = 0 or 0XFB btfss STEP,7 ; if MSB clear (i.e. STEP = 0) goto dwn1 ; skip donw (i.e. cary done) movlw d'3' ; new step count in w movwf STEP ; now STEP = 3 dwn1 movf STEP,0 ; w = offset into lookup table call steptbl ; get next motor position movwf PORTB ; step the motor movf SPEED,0 ; speed has value for TMR1H movwf TMR1H ; set delay time (big #=small del) call delay ; actual dealy loop bcf PORTB,ENABLE ; cut power to stepper driver return ;************************************************************************* ; call after tmpchk (DIF will have dif between measured and desired temp) ; sets the speed the motor will turn at (i.e. the delay between steps) spdset ; if temp dif > 255 set to 255 movlw 0XFF ; w is all ones movf DIFH,1 ; update zero bit for DIFL btfss STATUS,Z ; if DIF > 255 (i.e. DIFH <> 0) then movwf DIFL ; max out DIFL (all ones -> 255) ; set SPEED for large delay (small #) if close to temp set point ; set SPEED for small delay (large #) if far from temp set point movlw LIMDT ; w = # for smallest time between steps movwf SPEED ; store in SPEED comf DIFL,0 ; 0 -> 255, 1 -> 254, ..., 255 -> 0 subwf SPEED,1 ; delay value for moving motor btfss STATUS,C ; if result neg clrf SPEED ; set for maximum delay (~1/8 sec) return ;************************************************************************* ; update the status LED's depending on the TSTAT bits ; RD7 (pin 30) high when valve position below lower limit ; RD6 (pin 29) high when valve position above upper limit ; RD5 (pin 28) high when water temp > set point ; RD4 (pin 27) high when water temp dif > temp hysteresis ledupdt ; check lower limit bit bcf PORTD,7 ; turn off LED btfsc TSTAT,LLER ; check if motor below lower limit bsf PORTD,7 ; if high, turn on LED ; check upperr limit bit bcf PORTD,6 ; turn off LED btfsc TSTAT,ULER ; check if motor above upper limit bsf PORTD,6 ; if high, turn on LED ; check water temp > set point bit bcf PORTD,5 ; turn off LED btfsc TSTAT,TGL ; check if water temp > set point bsf PORTD,5 ; if high, turn on LED ; check water diff > hysteresis bit bcf PORTD,4 ; turn off LED btfsc TSTAT,THY ; check if dif in water temp > hysteresis bsf PORTD,4 ; if high, turn on LED return ;************************************************************************* main ; Beginnig of main routine start bcf STATUS,RP0 ; ensure start in bank 0 bcf STATUS,RP1 clrwdt ; clear watch dog timer call intset ; disable interrupts call portio ; set I/O directions and defaults call adcon ; set up A/D converter call tmrset ; set up timers and watchdog clrf STEP ; zero the step counter endlp ; read the motor pos, temperature, set point, and U/L limits clrwdt ; clear watch dog timer call read_ad ; read the five A/D channels clrwdt ; clear watch dog timer call poschk ; compare motor position to limit pots call tmpchk ; compare water temp to set point pot call spdset ; set speed to move the stepper motor ; Note: spdset must be called after tmpchk clrwdt ; clear watch dog timer call mtrmov ; move the motor if needed call ledupdt ; update the status LED's (RD4-7) goto endlp ; continue looping for ever END ; should never get here