;***************************************************************************
;             MAG1.asm      Magnetic Actuator Driver 
;***************************************************************************
;            Bruce Abbott   bhabbott@paradise.net.nz 
;
;      for Microchip PIC12C5xx/12F6xx running at @ 4MHz (INT RC Clock)
;
;===========================================================================
;                            Description
;
; Converts 1~2mS Servo pulse into PWM output, giving proportional control
; of a magnetic actuator.  
;
;
; ==========================================================================
;                          Summary of Changes
;
; 2004/2/2  V0.01 - Initial release 
;
; --------------------------------------------------------------------------
;

#DEFINE version  0
#DEFINE revision 1

;	 PROCESSOR PIC12C508
;        PROCESSOR PIC12F675
	
	ifdef	  __12C508
        INCLUDE   <P12C508.inc>
	__CONFIG  _MCLRE_OFF&_CP_OFF&_WDT_ON&_IntRC_OSC	
	else
        INCLUDE   <P12F675.inc>
	__CONFIG  _MCLRE_OFF&_CP_OFF&_WDT_ON&_BODEN_ON&_INTRC_OSC_NOCLKOUT
	endif

         radix     dec

	errorlevel 0,-305,-302

;#DEFINE NO_OSCCAL 	; enable if OSCCAL value was erased!

; Bit definitions for the GPIO register and the TRIS register

#DEFINE OUT1	 1    ; pin 6   
#DEFINE OUT2	 2    ; pin 5   
#DEFINE OUT3	 4    ; pin 3     
#DEFINE OUT4	 5    ; pin 2    
#DEFINE SERVO	 3    ; pin 4   servo pulse input 

#DEFINE TrisBits (1<<SERVO)	; Port pins that are inputs

#DEFINE LEFT	((1<<OUT1)|(1<<OUT2))
#DEFINE RIGHT	((1<<OUT3)|(1<<OUT4))

; Bits to be set with the OPTION instruction
;   No wake up
;   No weak pullups
;   Timer 0 source internal
;   Which edge is don't care
;
;   Prescaler to Watchdog, ~550mS timeout.
;
;
#DEFINE OptionBits B'11011101'

;===========================================================================
; Macro to create offsets for variables in RAM
;
	ifdef	__12C508	
ByteAddr	SET	7		; user RAM starts here
	else
ByteAddr	SET	32
	endif	

BYTE            MACRO	ByteName
ByteName        EQU	ByteAddr
ByteAddr	SET	ByteAddr+1
                ENDM

; ==========================================================================
;                 RAM Variable Definitions  
;

 	BYTE	pwm_start	; GPIO output at start of PWM cycle 
	BYTE	pwm_period	; PWM period counter 		
	BYTE	pwm_ratio	; PWM high reload value
	BYTE	pwm_count	; PWM high counter 
	
        BYTE	ServoCount	; measurement of servo pulse width      

        BYTE	Flags		; various boolean flags            

	BYTE	temp_1		; temporary storage 	
	BYTE	temp_2

#DEFINE PWMSTEPS  20		; no. of steps in a PWM cycle

;
; flag values
;
#DEFINE TIMEOUT	0		; watchdog timed out

;
; constants
;


;***************************************************************************
;                                Code
;***************************************************************************

	ORG	0
	goto	ColdStart

;-------------------------- version string ---------------------------------

	org	8		
	dt	"--MAG1--"
	dt	"--V"
	dw	version+"0"
	dt	"."
	dw	revision+"0"
	dt	"--"

; =============================================+============================
; Macro for generating short time delays
;
no_op           MACRO   count
no_op_count     SET     count
                WHILE   no_op_count>1
		goto	$+1		; 2 clocks
no_op_count     SET     no_op_count-2
                ENDW
		IF	no_op_count
		nop			; 1 clock
		ENDIF
                ENDM

;-----------------------------------------------------------------------
;                      Generate PWM output
;-----------------------------------------------------------------------
;
; Takes 12uS (including call&return) 
;
; Must be called every 20uS.   
;
;
;
DoPWM:	decfsz	pwm_period	;3    end of PWM cycle ?
	goto	pwm_step	;4(5) no, do the next PWM step
	movf	pwm_ratio,w	;5   
	movwf	pwm_count	;6
	movlw	PWMSTEPS	;7    yes, start another PWM cycle
	movwf	pwm_period	;8
	movf	pwm_start,w	;9 
	movwf	GPIO		;10
	retlw	0		;11,12	
pwm_step:
	nop			;6
	movlw	0		;7
	decf	pwm_count	;8   Count down.
	skpnz			;9   If count=0 then ...  
	movwf	GPIO		;10  ... turn PWM output OFF.
	retlw	0		;11,12


;============================================================================

ColdStart:		
	bcf	Flags,TIMEOUT
	btfss	STATUS,NOT_TO		; did Watchdog time out ? 
	bsf	Flags,TIMEOUT

	ifdef	__12C508
	ifdef	NO_OSCCAL		
	movlw	0x90			; replace with value for your PIC!
	endif
	movwf	OSCCAL			; set oscillator calibration 
	else 
	bsf	STATUS,RP0		; register bank 1 (12F629/75) 
	call	0x3ff			; get OSCCAL value
	movwf	OSCCAL			; set oscillator calibration 
        bcf	STATUS, RP0		; register bank 0 
        endif
                 
; Move prescaler from tmr0 to the watchdog, without causing accidental reset!

        clrwdt                    
        clrf	TMR0            
        movlw	OptionBits | 7  
        option                    	
        clrwdt
        movlw	OptionBits
        option
        clrwdt

; initialise I/O registers 

        clrf	GPIO			; all outputs low
	ifdef	__12C508
	movlw	TrisBits
	Tris	GPIO
	else
	bsf	STATUS,RP0		; register bank 1 
        movlw	TrisBits
        movwf	TRISIO			; set I/O pin directions
	ifdef	ANSEL
	clrf	ANSEL			; disable analog inputs (12F675)
	endif
	bcf	STATUS,RP0		; register bank 0
	movlw	b'00000111'		      
        movwf	CMCON			; Comparator off
	endif

; CPU specific stuff done, now we can start the main program!

       goto	Main		
							                               


;---------------------------------------------------------------------------
;                    Millisecond Delay Timer 
;---------------------------------------------------------------------------
; Input: W = number of milliseconds to wait x 2 (max 512mS)
;
dx2k:		movwf	temp_1		
_dx2k1:		movlw	(2000-2)/10	
		movwf	temp_2		
_dx2k2:		clrwdt			; avoid watchdog timeout			
		no_op	6						
		decfsz	temp_2		
		goto	_dx2k2		
		decfsz	temp_1		
		goto	_dx2k1		
		retlw	0		


;***************************************************************************
;				 Main
;***************************************************************************		


Main:		btfsc	Flags,TIMEOUT	; did the watchdog timeout ?
        	goto    Start		; oops! Try to recover...

		movlw	250		; wait 500mS for Rx to stabilise 
		call	dx2k		; 

Start:		clrf	Flags		; clear all flags

		clrf	pwm_ratio	; PWM off
		clrf	pwm_start
		movlw	1
		movwf	pwm_period	; 1st DoPWM inits counter	

wait_low:	clrwdt			;4
		no_op	4		;5~8
		call	DoPWM
		btfsc	GPIO,SERVO	;1     wait for servo pulse Low
		goto	wait_low	;2(3)
		nop			;3
wait_high:	clrwdt			;4
		no_op	4		;5~8
		call	DoPWM
		btfss	GPIO,SERVO	;1     wait for servo pulse High
		goto	wait_high	;2(3)
		
		clrf	ServoCount	;3
		no_op	2		;4,5
measure:	clrwdt			;6
		no_op	2		;7,8
		call	DoPWM
		incfsz	ServoCount,w	;1
		incf	ServoCount	;2     measure length of servo pulse
		btfsc	GPIO,SERVO	;3
		goto	measure		;4(5)
		no_op	3		;6~8
		call	DoPWM		
;
; limit range to 1.1~1.9mS   
;
limit:		movlw	1100/20		;1
		subwf	ServoCount	;2 
		skpc			;3
		clrf	ServoCount	;4  1.1mS minimum
		no_op	2		;5,6
;
; wait until the next PWM cycle starts. 
;
wait_pwm:	no_op	2		;7,8
		call	DoPWM
		decf	pwm_period,w	;1
		skpnz			;2
		goto	last_pwm	;3(4)
		nop			;4
		goto	wait_pwm	;5,6

last_pwm:	no_op	4		;5~8
		call	DoPWM		;
				
;
; Update PWM ratio, whilst completing the current PWM cycle. 
;
; split servocount into bi-directional PWM
;
Do_Servo:	movlw	PWMSTEPS	;1
		subwf	ServoCount,w	;2 right or left ?		
		skpc			;3
		goto	do_left		;4(5)

do_right:	movwf	pwm_ratio	;5 PWM = 0~20
		no_op	3		;6~8
		call	DoPWM
		movlw	0		;1 assume PWM will be OFF
		tstf	pwm_ratio	;2 
		skpz			;3 PWM On time = 0 ?
		movlw	RIGHT		;4 no, PWM will be ON
		movwf	pwm_start	;5
		nop			;6
		goto	new_pwm		;7,8

do_left:	no_op	3		;6~8
		call	DoPWM
		movlw	PWMSTEPS	;1
		movwf	pwm_ratio	;2
		movf	ServoCount,w	;3 PWM = 20~1
		subwf	pwm_ratio	;4		
		movlw	LEFT		;5
		movwf	pwm_start	;6
		no_op	2		;7,8

new_pwm:	call	DoPWM
		nop			;1
		goto	wait_low	;2,3 wait for next servo pulse




;------------------ Oscillator Calibration Subroutine ----------------------


		ifdef	NO_OSCCAL
		org	0x3ff
		ifdef	__12F675
		retlw	0x70	; replace with osccal value for your 12F675!	
		endif
		endif

		END


