 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	<p16f648a.inc>	; 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
