Proportional fan source

	processor 12F675
	radix decimal
	#include  "p12F675.inc"

;Configuration bits:
;  Osc internal RC No clock, Watchdog off, Power-up timer on, Master clear internal

;GP5 is fan PWM out
;GP4 if fan full LED
;GP3 is unused
;GP2 is clock LED
;GP1 is fan off LED
;GP0/AN0 is temperature in

;------------- Adjustable parameters ----------------
kMinSpeed		equ	50	;Minimum fan speed out of 127
kMaxSpeed		equ	127	;Maximum fan speed, >kMinSpeed,  kErrThresh
kStartSpeed	equ	75	;Startup fan speed to discourage stalling
kTickCount	equ	1400	;Temperature test interval, clocked at 3900Hz
kTargetTemp	equ	160	;Target temperature, 0..1023, where 1023=5V
				;LM34 generates 10mV/F = 2.04 counts per degree F
kErrThresh	equ	1300	;Damping constant, 16-bit, 1024-32767

PWMBit		equ	5	;PWM out pin
FanMaxBit		equ	4	;Fan-max red LED pin
FanOffBit		equ	1	;Fan-off green LED pin
DebugBit		equ	2	;Toggling LED pin that shows we are alive

;=========== Useful macros =================

BANK0	MACRO
	BCF	STATUS,RP0	; clear bank select bits
	BCF	STATUS,RP1
	ENDM

BANK1	MACRO
	BSF	STATUS,RP0
	BCF	STATUS,RP1
	ENDM

IFWGEK	MACRO const,dest
	 addlw	0FFH-const+1
	 skpnc
	 goto	dest
	ENDM

IFWLEK	MACRO const,dest
	 addlw	0FFH-const
	 skpc
	 goto	dest
	ENDM

;============= Variables =================

Temp		equ	20H		;4 bytes
TickCount		equ	Temp+4		;2 bytes, count down
Err		equ	TickCount+2	;PWM error, 1 byte
Speed		equ	Err+1		;Actual fan speed
Flags		equ	Speed+1		;Flag bits
ErrSum		equ	Flags+1		;Temperature error, 2 bytes
FSpeed		equ	ErrSum+2		;Clipped fan speed

Saved_Status 	equ	5EH		;Interrupt temporaries
Saved_W		equ	5FH

;=======================================
	org	0

	goto	start

	org	4

;=========== Interrupt Handler  ==================

int	movwf	Saved_W		;Save w reg
	swapf	STATUS,W		;The swapf instruction, unlike the movf, affects NO status bits, which is why it is used here.
	clrf	STATUS		;Set to BANK0
	movwf	Saved_Status

	btfss	INTCON,T0IF	;Is this a Timer0 interrupt?
	goto	nottmr0

	;Yes. Rollover with no prescaler happens at 1MHz/256 = 3906Hz
	bcf	INTCON,T0IF	; Clear the interrupt

	;Decrement TickCount and test for zero
	decf	TickCount,F         ; Decrement low byte
        	incfsz	TickCount,W         ; Check for underflow
	incf    	TickCount+1,F       ; Update
	decf    	TickCount+1,F       ; Fixup
	movfw    	TickCount
        	iorwf	TickCount+1,W       ; Set Z bit
	bnz	nonTick

	;Counted down to 0: reset TickCount, set Flags[0]
	movlw	low(kTickCount)
	movwf	TickCount
	movlw	high(kTickCount)
	movwf	TickCount+1
	bsf	Flags,0

	;Toggle DebugBit to flash LED
	movlw	2
	xorwf	Flags,F		;Toggle Flags[1]
	bcf	GPIO,DebugBit
	btfsc	Flags,1		;Test and set led
	bsf	GPIO,DebugBit

nonTick
	;PWM generator
	;Decide whether or not to generate output. Speed is
	;the required density of 1-bits, 0..127 out of 127. We track the
	;error in Err. If Err is +ve we turn off and subtract
	;Speed from Err. If Err is -ve we turn on and add 127-Speed to Err

	btfsc	Err,7
	goto	PWMNeg

	;Err +ve, turn off
	movfw	Speed
	subwf	Err,F		;Subtract Speed from Err
	btfss	GPIO,PWMBit	;Is it aready off?
	goto	iexit		;If so do nothing; stops glitches
	bcf	GPIO,PWMBit	;Turn off
	goto	iexit

PWMNeg	movlw	0x7F
	addwf	Err,F		;Add 127 to Err
	movfw	Speed
	subwf	Err,F		;and subtract speed
	btfsc	GPIO,PWMBit	;Is it already on?
	goto	iexit		;If so do nothing; stops glitches
	bsf	GPIO,PWMBit	;Turn on
	goto	iexit

nottmr0	bcf	INTCON,GIE	;Interrupt not from Timer 0. Aaargh!

iexit	swapf	Saved_Status,W	; get saved status in w
	movwf	STATUS		; restore status ( and bank )
	swapf	Saved_W,F		; reload into self to set status bits
	swapf	Saved_W,W		; and restore
	retfie

;======================== Main ======================

start	;Power-on entry point
	;Calibrate the internal oscillator
	BANK1
	movwf	OSCCAL
	BANK0

	;Configure the I/O pins
	movlw	B'00111010'
	movwf	GPIO		;Set outputs high except for GP2
	movlw	7
	movwf	CMCON		;Disable the comparator
	movlw	B'10000001'	;Set ADC to right justified, Vref=Vdd, channel 0
	movwf	ADCON0
	clrwdt
	clrf	TMR0
	BANK1
	movlw	B'00001111'
	movwf	OPTION_REG	;Clear T0CS: internal clock, assign prescaler to WDT
	movlw	B'01010001'
	movwf	ANSEL		;AN0 only in use, ADC clock=Fosc/16
	movlw	B'00000001'  	;GP5-1 out, GP0 in
	movwf	TRISIO		;Configure 1=input 0=output
	movwf	WPU		;Enable pull-ups
	BANK0

	;Initialise local variables
	clrf	Err
	movlw	kStartSpeed	;Start up at kStartSpeed
	movwf	Speed
	movwf	FSpeed
	movlw	1
	movwf	TickCount
	clrf	TickCount+1
	clrf	Flags
	clrf	ErrSum
	clrf	ErrSum+1

	;Enable the Timer 0 interrupts and interrupts generally
	bsf	INTCON,T0IE
	bsf	INTCON,GIE

;=================== Main loop =================================

	;Wait for flag[0] to be set by interrupt
mloop	btfss	Flags,0
	goto	mloop

	bcf	Flags,0	;Clear flag[0] for next time

	;Read the temperature on AN0
	bcf	ADCON0,2	;Select AN0
	bcf	ADCON0,3
	movlw	8	;Wait 20usec
delay	addlw	0xFF
	skpnc
	goto	delay
	bsf	ADCON0,1	;Start a conversion
await	btfsc	ADCON0,1	;Wait for it to complete
	goto	await

	movfw	ADRESH	;Get upper 2 bits
	movwf	Temp+1
	BANK1
	movfw	ADRESL	;And lower 8 bits
	BANK0
	movwf	Temp

	;Calculate the 16-bit difference between reading and our target temperature
	movlw	low(kTargetTemp)
	subwf	Temp,F
	movlw	high(kTargetTemp)
	skpc
	movlw	high(kTargetTemp)+1
	subwf	Temp+1,F

	;Add this to the 16-bit error sum
	movfw	Temp
	addwf	ErrSum,F
	movfw	Temp+1
	skpnc
	incfsz	Temp+1,W
	addwf	ErrSum+1,F

	;Compare the error sum against high and low limits
	movlw	high(-kErrThresh)
	xorlw 	0x80
	movwf 	Temp
	movfw 	ErrSum+1
	xorlw 	0x80
	subwf 	Temp,W	; subtract high bytes
	bnz	cmpl
	movfw	ErrSum
	sublw	low(-kErrThresh)
cmpl	bnc	ToSpeed	;If no carry, ErrSum > -kErrThresh, nothing to do

	;Error is negative => fan is too fast => decrement fan speed
	movfw	FSpeed
	IFWLEK	kMinSpeed,negerr	;If FSpeed is > kMinSpeed
	decf	FSpeed,F		;decrement FSpeed
	bsf	GPIO,FanMaxBit	;turn fan-max LED off
negerr	movlw	low(kErrThresh)	;Add kErrThresh to ErrSum
	addwf	ErrSum,F
	movlw	high(kErrThresh)
	skpnc
	movlw	high(kErrThresh)+1
	addwf	ErrSum+1,F
	goto	ToSpeed

	;Error is positive => fan is too slow => increment fan speed
thi	movfw	FSpeed
	IFWGEK	kMaxSpeed,atMax
	incf	FSpeed,F
	bsf	GPIO,FanOffBit	;turn fan-off led off
	goto	poserr
atMax	bcf	GPIO,FanMaxBit	;turn fan-max LED on
poserr	movlw	low(-kErrThresh)	;Subtract kErrThresh from ErrSum
	addwf	ErrSum,F
	movlw	high(-kErrThresh)
	skpnc
	movlw	high(-kErrThresh)+1
	addwf	ErrSum+1,F

	;Translate FSpeed to Speed: identical but for kMinSpeed set Speed to 0,
	;and if Speed was 0 last tick and is kMinSpeed+1 now, use kStartSpeed
ToSpeed	movlw	kMinSpeed
	subwf	FSpeed,W		;FSpeed == kMinSpeed?
	bz	atMin

	movfw	Speed		;No, Was old Speed==0?
	bnz	notStart
	movlw	kStartSpeed	;Yes: set Speed to kStartSpeed to get fan started
	goto	setspeed
notStart	movfw	FSpeed		;No, copy FSpeed->Speed
setspeed	movwf	Speed
	goto	mloop

atMin	bcf	GPIO,FanOffBit	;Yes, turn fan-off LED on
	clrf	Speed		;and set Speed to 0
	goto	mloop

	end
Advertisements
  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: