 LIST P=PIC16F684, R=DEC
 INCLUDE "p16f684.inc"
 ERRORLEVEL -302         		; no bank warnings
 ERRORLEVEL -311				; no HIGH warning

 __CONFIG _FCMEN_OFF & _IESO_OFF & _BOD_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT

#define tMin	11
#define	tMax	60
#define COUNT_TIME	60
#define load	PORTC,0
#define vref	PORTC,1
#define FB		PORTC,2
#define clock	PORTC,3
#define datain	PORTC,4
#define alarm   100				; sound alarm threschold (value in mkrn/h)


; data segment
 CBLOCK 0x20                   
 pulses:COUNT_TIME				; array of pulses for each second
 del, tmp, cnt, sum				; variables for passing the parameters
 doze:3, n:3					; radiation doze to display	  
 thousands, hundreds, tens, units	; storage for digits
 pulseCnt, t0Cnt				; pulse counters
 dp								; setup for decimal points
 w_save, stat_save				; interrupt stuff
 vRefVal, vRefChk				; voltage reference variables
 data1, data2, data3, data4		; LCD data to display
 buzzT							; buzzer period setup
 ENDC

; code segment
 ORG 0                      	; start program at the beginning of mem
	goto	main

 ORG 4							; ISR
	movwf	w_save				; save Wreg
	swapf	STATUS, w			; save stat reg without
	movwf	stat_save			; changing flags

	btfsc	PIR1, TMR1IF		; TMR1 interrupt?
	 goto	T1_int				; NO - proceed

T0_int
	incf	t0Cnt, f			; update higher order byte of TMR0 counter
	bcf		INTCON, T0IF		; clear interrupt flag
	goto	ISR_end

T1_int
	movf	TMR0, w				; save current values of TMR0 counter
	clrf	TMR0
	movwf	n					; storage for the lower part of TMR0 counter
	movf	t0Cnt, w
	movwf	n+1					; save the upper part of TMR0 counter

	movlw	128					; set next timer interrupt event
	movwf	TMR1H				; 1 sec apart

	bsf		dp, 0				; set flag to update display
	bcf		PIR1, TMR1IF		; clear interrupt flag

ISR_end
	swapf	stat_save, w		; get original flags in STATUS
	movwf	STATUS
	swapf	w_save, f			; restore Wreg		
	swapf	w_save, w
	retfie    

code1		; gfabpcde
	andlw	0x0F
	addwf	PCL, f	
	retlw	b'01110111'			; code for 0
	retlw	b'00010100'			; code for 1
	retlw	b'10110011'			; code for 2
	retlw	b'10110110'			; code for 3
	retlw	b'11010100'			; code for 4
	retlw	b'11100110'			; code for 5
	retlw	b'11100111'			; code for 6
	retlw	b'00110100'			; code for 7
	retlw	b'11110111'			; code for 8
	retlw	b'11110110'			; code for 9
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank

code2		; abpegdcf		
	andlw	0x0F
	addwf	PCL, f
	retlw	b'11010111'			; code for 0
	retlw	b'01000010'			; code for 1
	retlw	b'11011100'			; code for 2
	retlw	b'11001110'			; code for 3
	retlw	b'01001011'			; code for 4
	retlw	b'10001111'			; code for 5
	retlw	b'10011111'			; code for 6
	retlw	b'11000010'			; code for 7
	retlw	b'11011111'			; code for 8
	retlw	b'11001111'			; code for 9
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank

code3		; degfabpc
	andlw	0x0F
	addwf	PCL, f
	retlw	b'11011101'			; code for 0
	retlw	b'00000101'			; code for 1
	retlw	b'11101100'			; code for 2
	retlw	b'10101101'			; code for 3
	retlw	b'00110101'			; code for 4
	retlw	b'10111001'			; code for 5
	retlw	b'11111001'			; code for 6
	retlw	b'00001101'			; code for 7
	retlw	b'11111101'			; code for 8
	retlw	b'10111101'			; code for 9
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank

code4		; fabcdepg
	andlw	0x0F
	addwf	PCL, f
	retlw	b'11111100'			; code for 0
	retlw	b'00110000'			; code for 1
	retlw	b'01101101'			; code for 2
	retlw	b'01111001'			; code for 3
	retlw	b'10110001'			; code for 4
	retlw	b'11011001'			; code for 5
	retlw	b'11011101'			; code for 6
	retlw	b'01110000'			; code for 7
	retlw	b'11111101'			; code for 8
	retlw	b'11111001'			; code for 9
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank
	retlw	0					; blank

main
	bcf		STATUS, RP0			; activate BANK 0
	clrf	PORTA				; initialize PORT A
	clrf	PORTC				; initialize PORT C
	movlw	0x07
	movwf	CMCON0				; comparators OFF
	bsf    	STATUS, RP0      	; switch to BANK 1

 	; ADC and PWM configuration
	clrf	TRISA ^ 0x80
	clrf	TRISC ^ 0x80
	bsf		TRISA ^ 0x80, 2		; enable input on pin RA2 for counting pulses
	bsf		TRISC ^ 0x80, 1 	; enable input on pin AN5 (RC1) for vref
	bsf		TRISC ^ 0x80, 2 	; enable input on pin AN6 (RC2) for FB
	clrf	ANSEL ^ 0x80
	bsf		ANSEL ^ 0x80, 5		; configure AN5 as analog input
	bsf		ANSEL ^ 0x80, 6		; configure AN6 as analog input
	movlw	b'01000001'
	movwf	OSCCON ^ 0x80		; oscillator freq = 1 MHZ
	movlw	0xFF
	movwf	PR2 ^ 0x80			; PWM period 244Hz
	clrf	ADCON1 ^ 0x80		; set ADC clock = Fosc/2
	bsf		PIE1, TMR1IE		; enable TMR1 interrupt 
	bcf    	STATUS, RP0        	; back to BANK 0

	clrf	INTCON				; enable periferal interrupts
	bsf		INTCON, PEIE		; (needed for TMR1)
	bsf		INTCON, T0IE		; enable TMR0 interrupt
	movlw	b'00010101'			; ADC setup:
	movwf 	ADCON0 				; Left justify, Vdd Vref, AN5, On	
	movlw	5					; Timer 2 prescaler: 5 for 1:4 or 7 for 1:16
	movwf	T2CON				; enable Timer 2 with prescaler
	movlw	0x0C				; enable single output PWM
	movwf	CCP1CON 	
	movlw	15
	movwf	CCPR1L				; setup initial duty period
	movlw	b'00001111'			; enable TMR1 oscillator	
	movwf	T1CON				; set TMR1 prescaler to 1:1

; INITIALIZATION
	movlw	0xFF
	movwf	thousands			; display 0
	movwf	hundreds
	movwf	tens
	clrf	units
	clrf	dp
	call	display				

	clrf	doze				; clear the doze and current TMR0 values
	clrf	doze+1
	clrf	doze+2
	clrf	TMR0
	clrf	t0Cnt
	clrf	pulseCnt
 	movlw 	pulses				; initialize pointer to the pulse storage
 	movwf	FSR	
	movlw	1
	movwf	vRefChk				; reset vref checking period
	clrf	buzzT

	clrf	TMR1L
	movlw	128					; set next timer interrupt event
	movwf	TMR1H				; 1 sec apart
	bcf		PIR1, TMR1IF		; clear TMR1 interrupt flag
	bcf		INTCON, T0IF		; clear TMR0 interrupt flag
	bsf		INTCON, GIE			; enable interrupts globally

loop
	decf	vRefChk, f
	btfss	STATUS, Z
	 goto	$+6
	bsf		ADCON0, GO			; check voltage at LM285
	btfsc	ADCON0, GO			; and wait for ADC completion	
	 goto 	$-1
	movf	ADRESH, w
	movwf	vRefVal
	
	movlw	0x0C
	xorwf	ADCON0, f			; switch IDC channel to AN6
	movlw	1					; aquisition time
	movwf	del
	call	delay

	bsf		ADCON0, GO			; check voltage at voltage divider
	btfsc	ADCON0, GO			; and wait for ADC completion	
	 goto 	$-1
	movf	ADRESH, w
	
	subwf	vRefVal, w
	btfsc	STATUS, Z
	 goto	dd					; voltages match - do nothing

	btfsc	STATUS, C
	 goto	incr
	movlw	tMin				; vRef < vDiv: check if T_PWM is above min
	subwf	CCPR1L, w
	btfsc	STATUS, C
	 decf	CCPR1L, f			; decrement T_PWM
	goto	dd

incr:
	movlw	tMax				; vRef > vDiv: check if T_PWM is below max
	subwf	CCPR1L, w
	btfss	STATUS, C
	 incf	CCPR1L, f			; increment T_PWM

dd:
	movlw	0x0C
	xorwf	ADCON0, f			; swich ADC channel back to AN5

	btfsc	dp, 0				; check if display update is needed
	 call	process				; 0.8 (2.64) msec processing	

	movf	buzzT, w			; check for sound alarm
	btfsc	STATUS, Z
	 goto	$+3
	call	buzz				; turn on buzzer for 10msec
	goto	loop

	movlw	10					; short delay 
	movwf	del					; to let PWM generate several cycles
	call	delay			
  	goto 	loop				; loop back

;##################### PROCEDURES ##########################################
process							; udate the doze and display it
	bsf		STATUS, RP0			; switch to BANK 1
	bsf		OSCCON ^ 0x80, 5	; set oscillator freq 4MHz
	bcf		STATUS, RP0			; back to BANK 0

	movf	n, w
	movwf	INDF				; add the last counted value to doze
	addwf	doze, f
	incf	FSR, f
	movf	n+1, w
	movwf	INDF
	btfsc	STATUS, C
	 incfsz	INDF, w
	  addwf	doze+1, f
	btfsc	STATUS, C
	 incf	doze+2, f
	incf	FSR, f

	movf	FSR, w				; take the pointer mod COUNT_TIME
	sublw	pulses+COUNT_TIME
	btfss	STATUS, Z
	 goto	$+3
	movlw	pulses
	movwf	FSR

	incf	pulseCnt, f
	movlw	COUNT_TIME/2
	subwf	pulseCnt, w
	btfss	STATUS, Z			; check if we have the last COUNT_TIME
	 goto	$+12				; samples in memory
	movf	INDF, w				; in which case we subtract the corresponding
	subwf	doze, f				; sample (2-byte value) from doze (3-byte value)
	incf	FSR, f		
	movf	INDF, w	
	btfss	STATUS, C
	 incfsz	INDF, w		
	  subwf	doze+1, f
	btfss	STATUS, C
	 decf	doze+2, f
	decf	FSR, f	
	decf	pulseCnt, f

	movf	doze, w				; copy doze counter to n for further processing
	movwf	n
	movf	doze+1, w
	movwf	n+1
	movf	doze+2, w
	movwf	n+2					; at this point n = doze (3-byte value)
	bcf		STATUS, C			; compute n *= 2	
	rlf		n, f
	rlf		n+1, f
	rlf		n+2, f
	movf	doze, w				; compute n = doze*3 by adding doze to n
	addwf	n, f
	movf	doze+1, w
	btfsc	STATUS, C
	 incfsz	doze+1, w
	  addwf	n+1, f
	movf	doze+2, w
	btfsc	STATUS, C
	 addlw	1
	addwf	n+2, f				; correct bit C is not needed here
	
	bcf		STATUS, C			; compute the final value of n in mkrn/h
	rrf		n+2, f				; n = (doze*3)/2
	rrf		n+1, f
	rrf		n, f

	call	bin2BCD				; convert n into BCD
	call	display				; update display

	bsf		STATUS, RP0			; switch to BANK 1
	bcf		OSCCON ^ 0x80, 5	; set oscillator freq 1MHz
	bcf		STATUS, RP0			; back to BANK 0
	return

bin2BCD							; split n into thousands/hundreds/tens/units
	clrf	sum					; current sum of digits		
	movlw	0xFF
	movwf	thousands			; initialize digit counters with -1
	movwf	hundreds
	movwf	tens
	movwf	units
	clrf	dp					; no decimal point

	movlw	5
	movwf	buzzT				; setup alarm time of 50msec
	movlw	LOW	alarm			; check if alarm is needed
	subwf	n, w				
	movlw	HIGH alarm		
	movwf	tmp					; 3-byte subtraction
	btfss	STATUS, C
	 incfsz	tmp, w				; compare doze with alarm
	  subwf	n+1, w
	movlw	UPPER alarm		
	movwf	tmp	
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+2, w
	btfss	STATUS, C
	 clrf	buzzT				; no alarm is needed	

	movlw	LOW	100000			; determine the range of the result
	subwf	n, w				; (<10,000 or 10,000 <..<100,000 or 100,000 <)
	movlw	HIGH 100000		
	movwf	tmp					; 3-byte subtraction
	btfss	STATUS, C
	 incfsz	tmp, w				; compare doze with 100,000	
	  subwf	n+1, w
	movlw	UPPER 100000		
	movwf	tmp	
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+2, w
	btfsc	STATUS, C
	 goto	above100000	

	movlw	LOW	10000			; now doze < 100,000
	subwf	n, w				; compare doze with 10,000
	movlw	HIGH 10000		
	movwf	tmp					; 3-byte subtraction
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+1, w		
	clrw
	clrf	tmp	
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+2, w
	btfsc	STATUS, C
	 goto	above10000	
	goto	below10000			; the range is determined

above100000
	bsf		dp, 1				; set decimal point after the first 2 digits
	movlw	LOW	100000			; compute the first digit
	subwf	n, f				
	movlw	HIGH 100000		
	movwf	tmp					; 3-byte subtraction
	btfss	STATUS, C
	 incfsz	tmp, w				; compare doze with 100,000	
	  subwf	n+1, f
	movlw	UPPER 100000		
	movwf	tmp	
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+2, f
	incf	thousands, f
	btfsc	STATUS, C
	 goto	$-14

	movlw	LOW	100000			; restore n
	addwf	n, f
	movlw	HIGH 100000
	movwf	tmp
	btfsc	STATUS, C
	 incfsz	tmp, w
	  addwf	n+1, f
	movlw	UPPER 100000
	btfsc	STATUS, C
	 addlw	1	
	addwf	n+2, f

	movlw	LOW	10000			; at this point n<100,000 but might be 3-byte
	subwf	n, f				; compute the second digit
	movlw	HIGH 10000		
	movwf	tmp					; 3-byte subtraction
	btfss	STATUS, C
	 incfsz	tmp, w				; compare doze with 10,000	
	  subwf	n+1, f
	clrw
	clrf	tmp	
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+2, f
	incf	hundreds, f
	btfsc	STATUS, C
	 goto	$-14

	movlw	LOW	10000			; restore n
	addwf	n, f
	movlw	HIGH 10000
	movwf	tmp
	btfsc	STATUS, C
	 incfsz	tmp, w
	  addwf	n+1, f
	clrw
	btfsc	STATUS, C
	 addlw	1	
	addwf	n+2, f

	movlw	LOW	1000			; at this point n<10,000 and is at most 2-byte
	subwf	n, f				; compute the third digit
	movlw	HIGH 1000		
	movwf	tmp					; 2-byte subtraction
	btfss	STATUS, C
	 incfsz	tmp, w				; compare doze with 1,000	
	  subwf	n+1, f
	incf	tens, f
	btfsc	STATUS, C
	 goto	$-9

	movlw	LOW	1000			; restore n
	addwf	n, f
	movlw	HIGH 1000
	btfsc	STATUS, C
	 addlw	1	
	addwf	n+1, f

	movlw	100					; at this point n<1,000 and is at most 2-byte
	subwf	n, f				; compute the fourth digit
	clrf	tmp					; 2-byte subtraction
	clrw
	btfss	STATUS, C
	 incfsz	tmp, w				; compare doze with 1,000	
	  subwf	n+1, f
	incf	units, f
	btfsc	STATUS, C
	 goto	$-9
	return

above10000
	bsf		dp, 2				; set decomal point after the first digit
	movlw	LOW	10000			; at this point n<100,000 but might be 3-byte
	subwf	n, f				; compute the first digit
	movlw	HIGH 10000		
	movwf	tmp					; 3-byte subtraction
	btfss	STATUS, C
	 incfsz	tmp, w				; compare doze with 10,000	
	  subwf	n+1, f
	clrf	tmp	
	clrw
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+2, f
	incf	thousands, f
	btfsc	STATUS, C
	 goto	$-14

	movlw	LOW	10000			; restore n
	addwf	n, f
	movlw	HIGH 10000
	movwf	tmp
	btfsc	STATUS, C
	 incfsz	tmp, w
	  addwf	n+1, f
	clrw
	btfsc	STATUS, C
	 addlw	1	
	addwf	n+2, f

	movlw	LOW	1000			; at this point n<10,000 and is at most 2-byte
	subwf	n, f				; compute the third digit
	movlw	HIGH 1000		
	movwf	tmp					; 2-byte subtraction
	btfss	STATUS, C
	 incfsz	tmp, w				; compare doze with 1,000	
	  subwf	n+1, f
	incf	hundreds, f
	btfsc	STATUS, C
	 goto	$-9

	movlw	LOW	1000			; restore n
	addwf	n, f
	movlw	HIGH 1000
	btfsc	STATUS, C
	 addlw	1	
	addwf	n+1, f

	movlw	100					; at this point n<1,000 and is at most 2-byte
	subwf	n, f				; compute the third digit
	clrf	tmp					; 2-byte subtraction
	clrw
	btfss	STATUS, C
	 incfsz	tmp, w				; compare doze with 100	
	  subwf	n+1, f
	incf	tens, f
	btfsc	STATUS, C
	 goto	$-9

	movlw	100					; restore n
	addwf	n, f
	clrw
	btfsc	STATUS, C
	 addlw	1	
	addwf	n+1, f

	movlw	10					; at this point n<100 and is 1-byte
	subwf	n, f				; compute the fourth digit
	incf	units, f
	btfsc	STATUS, C
	 goto	$-4
	return

below10000
	movlw	LOW 1000			; 16-bit subtraction
	subwf	n, f
	movlw	HIGH 1000		
	movwf	tmp	
	btfss	STATUS, C
	 incfsz	tmp, w		
	  subwf	n+1, f
	incf	thousands, f
	btfsc	STATUS, C
	 goto	$-9

	movlw	LOW 1000			; restore n
	addwf	n, f
	movlw	HIGH 1000
	btfsc	STATUS, C
	 addlw	1
	addwf	n+1, f

	movlw	100					; compute hundreds
	subwf	n, f
	clrf	tmp
	clrw
	btfss	STATUS, C
	 incfsz tmp, w
	  subwf n+1, f
	incf	hundreds, f
	btfsc	STATUS, C
	 goto	$-9

	movlw	100					; restore n
	addwf	n, f				; at this point |n| < 100

	movlw	10					; compute tens
	subwf	n, f
	incf	tens, f
	btfsc	STATUS, C
	 goto	$-3
	addwf	n, w	
	movwf	units				; compute units

	movf	thousands, w		; shut down the first digit if it is 0
	addwf	sum, f
	btfss	STATUS, Z
	 bsf	dp, 3				; turn on decimal point if the n>=1000
	btfsc	STATUS, Z
	 comf	thousands, f		; set thousands=0xFF to turn it off
	movf	hundreds, w			; shut down the second digit in case of
	addwf	sum, f				; two leading zeros
	btfsc	STATUS, Z
	 comf	hundreds, f
	movf	tens, w				; shut down tens in case of 3 leading zeros
	addwf	sum, w
	btfsc	STATUS, Z
	 comf	tens, f
	return

display							; compose 4 bytes to send to the LCD controller
	movf	thousands, w
	call	code1
	movwf	data1
	movwf	data4
	movlw	0x0F
	andwf	data4, f
	movlw	0xF0
	andwf	data1, f

	movf	hundreds, w
	call	code2
	movwf	data2
	andlw	0x0F
	iorwf	data1, f			; data1 is ready
	movlw	0x30
	andwf	data2, w
	iorwf	data4, f
	movlw	0xC0
	andwf	data2, f

	movf	units, w
	call	code4
	movwf	data3
	movlw	1
	andwf	data3, w
	iorwf	data2, f
	bcf		data3, 0

	movf	tens, w
	call	code3
	movwf	tmp
	andlw	0x03
	iorwf	data3, f			; data3 is ready
	movlw	0xC0
	andwf	tmp, w
	iorwf	data4, f			; data4 is ready
	rrf		tmp, w
	andlw	0x1E
	iorwf	data2, f			; data2 is ready
		
	btfsc	dp, 3				; decimal points setup
	 bsf	data4, 3
	btfsc	dp, 2
	 bsf	data4, 5
	btfsc	dp, 1
	 bsf	data3, 1

	movlw	8					; shift in the first data byte
	movwf	cnt					; into the LED controller chip
	bcf		datain
	rlf		data1, f
	btfsc	STATUS, C
	 bsf	datain				; data line is set up
	bsf		clock				; toggle the clock pin
	bcf		clock
	decfsz	cnt, f
	 goto	$-7

	bsf		cnt, 3				; shift in the second data byte	
	bcf		datain				; into the LED controller chip
	rlf		data2, f
	btfsc	STATUS, C
	 bsf	datain				; data line is set up
	bsf		clock				; toggle the clock pin
	bcf		clock				
	decfsz	cnt, f
	 goto	$-7

	bsf		cnt, 3				; shift in the third data byte
	bcf		datain				; into the LED controller chip
	rlf		data3, f
	btfsc	STATUS, C
	 bsf	datain				; data line is set up
	bsf		clock				; toggle the clock pin
	bcf		clock
	decfsz	cnt, f
	 goto	$-7

	bsf		cnt, 3				; shift in the fourth data byte	
	bcf		datain				; into the LED controller chip
	rlf		data4, f
	btfsc	STATUS, C
	 bsf	datain				; data line is set up
	bsf		clock				; toggle the clock pin
	bcf		clock
	decfsz	cnt, f
	 goto	$-7

	bsf		load				; toggle the load pin
	bcf		load
	return

buzz							; turn on buzzer for 10msec
	movlw	25					; buzzer freq 2400Hz (T=416usec)
	movwf	del
buzz_loop
	bsf		PORTA, 0
	movlw	12					; buzzer is on for 12*4*4+8=200usec
	addlw	-1
	btfss	STATUS, Z
	 goto	$-2
	bcf		PORTA, 0
	movlw	12					; buzzer is off for the second half-period
	addlw	-1
	btfss	STATUS, Z
	 goto	$-2
	decfsz	del, f
	 goto	buzz_loop	
	decf	buzzT, f
	return

delay							; a delay for del milliseconds
	movlw 	50
	
	sublw	1					; this loop takes 20us*50 = 1ms
	sublw	0					; for PIC @ 1 Mhz
	btfss	STATUS, Z
	goto 	$-3

	decfsz	del, f
	 goto 	delay
	return
 END