title "Exhaust Gas Temperature adapter" ;***************************************************************************** ; Exhaust Gas Temperature ADAPTER ; ; Converts an RC servo control pulse to a PWM variable voltage. ; a 900 microsecond pulse width corresponds to a 0 volt output while ; a 2100 microsceond pulse width corresponds to a 5 volt output. ; ; ; 15 January 2010, Mike Powell ; ;***************************************************************************** list p=16f648a ; list directive to define processor #include ; processor specific variable definitions __CONFIG _CP_OFF & _WDT_OFF & _HS_OSC & _PWRTE_ON & _LVP_OFF ;set code protection off, watch dog timer off, select the high speed oscillator ;power-up timer enabled, low voltage programming off ; Registers CBLOCK 0x020 fNew_Pulse ; Flag indicating new RC servo pulse has been measured fNegative ; Flag indicating negative arithmetic result Egt_PWMh_L ; Egt duty cyle - 16 bits. Used to set the high Egt_PWMh_H ; duty cyle of the EGT control output Egt_PWMl_L ; Egt duty cyle - 16 bits. Used to set the low Egt_PWMl_H ; duty cyle of the EGT control output PWM_Ctr_L ; Pulse Width Modulation counter - 16 bits PWM_Ctr_H ; count variable used inside ISR P_A ; PORTA output signal ENDC ; CBLOCK 0x030 A_H ; A - 24 bits. Stores A value for A_MINUS_B call A_M ; A_L ; B_H ; B - 24 bits. Stores B value for A_MINUS_B call B_M ; B_L ; C_H ; C - 24 bits. Stores C value for A_MINUS_B call C_M ; C_L ; ENDC CBLOCK 0x070 W_Temp ; These three are used to store W, STATUS & PCLATH Status_Temp ; registers during an interrupt. The location in Pclath_Temp ; block 0X070 is important because the same physical ENDC ; registers are in all four register pages. RESET_VECTOR CODE 0x000 ; Reset comes to this address goto INITIALIZE INTERRUPT_VECTOR CODE 0x004 ; All interrupts come here ;******************************************************************** ; ; ISR - INTERRUPT SERVIDE ROUTINE ; ; The ISR responds to rising and falling edges on the RB0/INT input, ; and to the periodic TIMER1 interrupt. The RBO/INT input connects to ; the RC servo control line. Code in the ISR uses TIMER2 to measure ; the width of the pulse on the control line. The TIMER1 interrupt ; drives the creation of the PWM output which connects to the ; EGT. The duty cycle of the output is controlled by the value of ; Egt_PWM_H and Egt_PWM_L. ; ;******************************************************************** ;************************************************************ ; This section stores a few of the critical registers ; that will be changed by the following interrupt ; service code. We'll restore the proper values to ; those registers just before returning from the ; interrupt. ;************************************************************ movwf W_Temp ; Store important registers swapf STATUS, w ; Why use swapf instead of movf? clrf STATUS ; because swapf doesn't change STATUS movwf Status_Temp ; bits, but movf affects the ZERO bit. movf PCLATH, w ; We want to save and later restore movwf Pclath_Temp ; these registers without changing clrf PCLATH ; anything. ;****************** END of store registers ****************** btfsc PIR1, TMR2IF ; Did TIMER2 cause the interrupt? goto PWM ; Yes, go service the timer interrupt ; No, must be an RB0/INT interrupt ;************************************************************ ; TIME_SERVO uses TIMER1 to measure the width of the ; RC Servo control signal connected to the RB0/INT ; pin. On the rising edge we'll initialize the time ; registers and turn it on. On the falling edge we'll ; turn it off, transfer the count to holding variables ; and set the flag for code outside the ISR to ; process it. ;************************************************************ TIME_SERVO bcf INTCON, INTE ; Disable external interrupts on RB0/INT btfss PORTB, 0 ; Is the pulse just starting? goto PULSE_LOW ; No, it just ended clrf TMR1H ; Yes, Pulse just went high clrf TMR1L ; set up TIMER1 movlw 0x31 ; TIMER1 prescale = 8, timer on movwf T1CON ; clock is every 1.6 microseconds bsf STATUS, RP0 ; Select page 1 bcf OPTION_REG, INTEDG ; Enable interrupt on falling edge bcf INTCON, INTF ; Make sure the interrupt flag is clear bsf INTCON, INTE ; Enable external interrupts on RB0/INT bcf STATUS, RP0 ; Select page 0 goto INTR_DONE PULSE_LOW nop nop nop clrf T1CON ; Turn off TIMER1 bsf STATUS, RP0 ; Select page 1 bsf OPTION_REG, INTEDG ; Enable interrupt on rising edge bcf INTCON, INTF ; Make sure the interrupt flag is clear bsf INTCON, INTE ; Enable external interrupts on RB0/INT bcf STATUS, RP0 ; select page 0 bsf fNew_Pulse, 7 ; Set flag indicating new width measurement goto INTR_DONE ;********************* END of TIME_SERVO ******************** ;************************************************************ ; PWM creates the EGT control output ; by counting down the 16 bit period value. When it ; underflows, the EGT PWM signal is inverted and the ; counter is reset to the current duty cycle value. ;************************************************************ PWM bcf PIR1, TMR2IF ; clear the TIMER2 interrupt flag movlw .1 ; subwf PWM_Ctr_L, 1 ; btfsc STATUS, C ; C=0 means negative result goto END_PWM ; not zero means we're done movlw .1 ; subwf PWM_Ctr_H, 1 ; btfsc STATUS, C ; C=0 means negative result goto END_PWM ; not zero means we're done comf P_A, 1 ; flip the EGT out state movf P_A, 0 movwf PORTA btfss P_A, 0 ; Are we timing a high output? goto PWM_LOW ; No, go handle the low time PWM_HIGH movf Egt_PWMh_L, 0 ; Load the PWM counter with movwf PWM_Ctr_L ; EGT high count value movf Egt_PWMh_H, 0 movwf PWM_Ctr_H goto END_PWM PWM_LOW movf Egt_PWMl_L, 0 ; Load the PWM counter with movwf PWM_Ctr_L ; EGT low count value movf Egt_PWMl_H, 0 movwf PWM_Ctr_H END_PWM ;************************ END of PWM ************************ ;************************************************************ ; INTR_DONE restores critical PIC registers to ; pre-interrupt values so wonky things won't happen. ;************************************************************ INTR_DONE movf Pclath_Temp, w ; restore important registers movwf PCLATH swapf Status_Temp, w movwf STATUS swapf W_Temp, f swapf W_Temp, w retfie ;*****************END of INTR_DONE*************************** ;***********************END OF INTERRUPT SERVICE ROUTINE************* ;******************************************************************** ; ; INITIALIZATION configures the PIC for this application ; ; Initialize PORTA<0> as an output ; Initialize PORTB<0> as an input ; Configure TIMER1 to interrupt every 26 microseconds ; Clear TIMER2, set clock input ; Enable global interrupts ; ;******************************************************************** INITIALIZE bcf STATUS, RP0 bcf STATUS, RP1 ;************************************************************ ; Configure I/O pins ;************************************************************ bsf STATUS, RP0 ; select page 1 for TRIS and ADCON1 access movlw 0x07 ; turn off the input comparators movwf CMCON ^ 0X080 ; or PORTA won't respond as needed movlw 0xFF movwf TRISB ^ 0X080 ; sets PORTB<7:0> as inputs movlw 0xFE movwf TRISA ^ 0X080 ; sets PORTA<0> as output bcf STATUS, RP0 ; back to page 0 ;************************************************************ ;************************************************************ ; Initialize variables ;************************************************************ clrf PORTA clrf fNew_Pulse movlw .119 ; Initial value for EGT control duty cycle movwf Egt_PWMl_L ; movlw .1 ; movwf Egt_PWMl_H ; movlw .119 ; movwf Egt_PWMh_L ; movlw .1 ; movwf Egt_PWMh_H ; movlw .119 ; movwf PWM_Ctr_L ; movlw .1 ; movwf PWM_Ctr_H ; ;************************************************************ ;************************************************************ ; Enable the RB0/INT interrupt to interrupt on rising edge ;************************************************************ bsf STATUS, RP0 ; select page 1 bsf OPTION_REG, INTEDG bcf INTCON, INTF ; Make sure the interrupt flag is clear bsf INTCON, INTE ; Enable external interrupts on RB0/INT bcf STATUS, RP0 ; select page 0 ;************************************************************ ;************************************************************ ; Configure TIMER2 to interrupt every 26 microseconds ;************************************************************ bsf STATUS, RP0 ; select page 1 movlw .129 movwf PR2 ^ 0x080 ; loads the TIMER2 PERIOD reg bcf STATUS, RP0 ; back to page 0 movlw 0x04 ; TIMER2 should interrupt every 26 usec movwf T2CON ^ 0x080 ; prescale = 1, TMR2 on, Postscale = 1 ;************************************************************ ;************************************************************ ; Enable all interrupts ;************************************************************ bsf INTCON, GIE ; Enable gloabl interrupts bsf INTCON, PEIE bsf STATUS, RP0 bsf PIE1 ^ 0x080, TMR2IE ; Enable interrupts from TIMER2 bcf STATUS, RP0 ;************************************************************ ;******************* End of initialization ************************** ;******************************************************************** ; This is the "idle loop" where the PIC waits for ; something interesting to happen ;******************************************************************** IDLELOOP btfsc fNew_Pulse, 7 ; All we have to do here is continually goto CALC_DUTY_CYCLE ; check for the presence of a new RC goto IDLELOOP ; servo pulse width measurement. ;************************ End of idle loop ************************** ;******************************************************************** ; CALCULATE DUTY CYCLE ; ; The RC servo control signal is a pulse that varies from 900 to 2100 ; microseconds in width. The duty cycle of the EGT control ; signal should vary in proportion to the width of the RC pulse with ; 900 microseconds producing a nominal 0% duty cycle output and 2100 ; microseconds producing 100%. The PIC produces the EGT signal by ; individually controlling the widths of the high and low portions of ; the PWM output signal. This section calculates these widths and ; stores the values for use by the ISR. ; ;******************************************************************** CALC_DUTY_CYCLE movf TMR1L, 0 ; Transfer TIMER1 count to A variable movwf A_L movf TMR1H, 0 movwf A_M clrf A_H TOO_WIDE_CHECK movlw .33 ; Load B with 1312, the timer count movwf B_L ; for the maximum RC Servo pulse width movlw .5 movwf B_M clrf B_H call A_MINUS_B ; btfsc STATUS, C ; Check for a too wide pulse goto TOO_WIDE TOO_NARROW_CHECK movlw .50 ; Load B with 562, the count offset movwf B_L ; for the RC Servo pulse width movlw .2 movwf B_M clrf B_H call A_MINUS_B ; Remove the width offset btfss STATUS, C ; Check for a too narrow pulse goto TOO_NARROW movf C_L, 1 ; Test for zero net pulse width btfss STATUS, Z goto PULSE_OKAY movf C_M, 1 ; Test for zero net pulse width btfss STATUS, Z goto PULSE_OKAY movf C_H, 1 ; Test for zero net pulse width btfss STATUS, Z goto PULSE_OKAY goto TOO_NARROW PULSE_OKAY movf C_L, 0 movwf Egt_PWMh_L movf C_M, 0 movwf Egt_PWMh_H movf C_L, 0 movwf B_L movf C_M, 0 movwf B_M clrf C_H clrf B_H movlw .238 movwf A_L movlw .2 movwf A_M clrf A_H call A_MINUS_B movf C_L, 0 movwf Egt_PWMl_L movf C_M, 0 movwf Egt_PWMl_H clrf fNew_Pulse goto IDLELOOP TOO_WIDE clrf C_H ; Load C with maximum pulse width movlw .2 movwf C_M movlw .238 movwf C_L goto PULSE_OKAY TOO_NARROW movlw .1 ; Load C with minimum pulse width movwf C_L clrf C_M clrf C_H goto PULSE_OKAY ;*********************** End of CALC_PERIOD ************************* ;******************************************************************** ; A_MINUS_B ; ; This is a callable routine that subtracts B from A and places the ; result in C. All variables are 24 bits wide. A and B are not changed ; ;******************************************************************** A_MINUS_B clrf fNegative movf A_L, 0 ; movwf C_L movf A_M, 0 ; movwf C_M movf A_H, 0 ; movwf C_H movf B_L, 0 subwf C_L, 1 ; btfsc STATUS, C ; Check for borrow goto MID_BYTE movlw 1 subwf C_M, 1 ; Borrow from middle byte btfsc STATUS, C ; Check for borrow goto MID_BYTE movlw 1 subwf C_H, 1 ; Borrow from high byte btfsc STATUS, C goto MID_BYTE bsf fNegative, 0 MID_BYTE movf B_M, 0 ; Pick up middle byte of B subwf C_M, 1 ; Middle byte of C = A - B btfsc STATUS, C ; Check for borrow goto HIGH_BYTE movlw 1 subwf C_H, 1 btfsc STATUS, C goto HIGH_BYTE bsf fNegative, 0 HIGH_BYTE movf B_H, 0 ; Pick up high byte of B subwf C_H, 1 ; High byte of C = A - B btfsc fNegative, 0 bcf STATUS, C return ;************************ End of A_MINUS_B ************************** END ; of everything