;**********************************************************************
;   This file is a basic code template for assembly code generation   *
;   on the PIC16F873A. This file contains the basic code              *
;   building blocks to build upon.                                    *  
;                                                                     *
;   If interrupts are not used all code presented between the ORG     *
;   0x004 directive and the label main can be removed. In addition    *
;   the variable assignments for 'w_temp' and 'status_temp' can       *
;   be removed.                                                       *                         
;                                                                     *
;   Refer to the MPASM User's Guide for additional information on     *
;   features of the assembler (Document DS33014).                     *
;                                                                     *
;   Refer to the respective PIC data sheet for additional             *
;   information on the instruction set.                               *
;                                                                     *
;**********************************************************************
;                                                                     *
;    Filename:	    vfdclock.asm                                   *
;    Date:                                                            *
;    File Version:                                                    *
;                                                                     *
;    Author:                                                          *
;    Company:                                                         *
;                                                                     * 
;                                                                     *
;**********************************************************************
;                                                                     *
;    Files required:                                                  *
;                                                                     *
;                                                                     *
;                                                                     *
;**********************************************************************
;                                                                     *
;    Notes:                                                           *
;                                                                     *
;                                                                     *
;                                                                     *
;                                                                     *
;**********************************************************************


	list		p=16f873A	; list directive to define processor
	#include	<p16f873A.inc>	; processor specific variable definitions
	
	__CONFIG _CP_OFF & _WDT_OFF & _BODEN_ON & _PWRTE_ON & _HS_OSC & _WRT_OFF & _LVP_OFF & _CPD_OFF

; '__CONFIG' directive is used to embed configuration data within .asm file.
; The lables following the directive are located in the respective .inc file.
; See respective data sheet for additional information on configuration word.

; Port A bits
#define	CDSIN	0		; CDS analog input for dimmer control
#define	DIG_6	1		; DIGIT 6 output
#define	DIG_5	2		; DIGIT 5 output
#define	DIG_4	3		; DIGIT 4 output
#define	BEEPER	4		; BEEPER output (negative)
#define	DIG_3	5		; DIGIT 3 output

; Port B bits
#define	SEG_G	0		; SEGMENT G output
#define	SEG_DP	1		; SEGMENT DP output
#define	DIG_1	2		; DIGIT 1 output
#define	DIG_2	3		; DIGIT  2 output
#define	SW1		4		; SW 1 in (negative)
#define	SW2		5		; SW 2 in (negative)
#define	SW3		6		; SW 3 in (negative)
#define	SW4		7		; SW 4 in (negative)

; Port C bits
#define	SEG_F	0		; SEGMENT F output
#define	SEG_H	1		; SEGMENT H output
#define	PWM		2		; PWM output
#define	SEG_A	3		; SEGMENT A output
#define	SEG_B	4		; SEGMENT B output
#define	SEG_C	5		; SEGMENT C output
#define	SEG_E	6		; SEGMENT E output
#define	SEG_D	7		; SEGMENT D output


#define DIGIT_BASE	0x20		; Digit buffer base address

; BCD buffers
digit_1			equ	0x20	; Digit 1 data
digit_2			equ	0x21	; Digit 2 data
digit_3			equ	0x22	; Digit 3 data
digit_4			equ	0x23	; Digit 4 data
digit_5			equ	0x24	; Digit 5 data
digit_6			equ	0x25	; Digit 6 data

; BCD buffer control bit
#define	WITH_DP	7		; With DP
#define	BLANKING	6		; Turn off the digit

; Display control regs
digit_ptr			equ	0x26	; Digit pointer
digit_ctrl		equ	0x27	; Digit control reg
#define		digit_on	0	; Control flag digit ON

digit_on_time		equ	0x28	; ON duration  (for brightness control)
digit_off_time	equ	0x29	; OFF duration (for brughtness control)
digit_temp		equ	0x2a	; Digit function internal temp

; Current time holding reg
time_sec			equ	0x30	; Time second
time_min			equ	0x31	; Time min
time_hr			equ	0x32	; Time hour
count50			equ	0x33	; 1/50 divide counter

; BCD conversion
bcd_d1			equ	0x34	; BCD 1's  order output
bcd_d10			equ	0x35	; BCD 10's order output
bcd_d100			equ	0x36	; BCD 100's order output
bcd_temp			equ	0x37	; BCD converter internal temp

wait_cnt			equ	0x38

; SW bounce remover
sw_prev			equ	0x39
sw_latch			equ	0x3a

;***** VARIABLE DEFINITIONS
w_temp		EQU	0x70		; variable used for context saving
status_temp	EQU	0x71		; variable used for context saving
pclath_temp	EQU	0x72		; variable used for context saving



;**********************************************************************
	ORG		0x000		; processor reset vector

	nop					; nop required for icd
	goto	main			; go to beginning of program


	ORG		0x004		; interrupt vector location

	movwf	w_temp		; save off current W register contents
	movf	STATUS,w		; move status register into W register
	bcf		STATUS,RP0	; ensure file register bank set to 0
	movwf	status_temp	; save off contents of STATUS register
	movf	PCLATH,w		; move pclath register into w register
	movwf	pclath_temp	; save off contents of PCLATH register

; isr code can go here or be located as a call subroutine elsewhere

	btfsc	INTCON, TMR0IF
	goto	isr_tmr0
	
	btfsc	PIR1, TMR1IF
	goto	isr_tmr1
	goto	isr_tmr1

; isr return code	
isr_ret
	bcf		STATUS,RP0	; ensure file register bank set to 0
	movf	pclath_temp,w	; retrieve copy of PCLATH register
	movwf	PCLATH		; restore pre-isr PCLATH register contents
	movf	status_temp,w	; retrieve copy of STATUS register
	movwf	STATUS		; restore pre-isr STATUS register contents
	swapf	w_temp,f
	swapf	w_temp,w		; restore pre-isr W register contents
	retfie				; return from interrupt

main
; remaining code goes here

	; Initialize
	; Setup port direction
	banksel	TRISA

	movlw	B'11000001'		; NA/NA/G3/BZ/G4/G5/G6/CDS(analog)
	movwf	TRISA

	movlw	B'11110000'		; SW4/SW3/SW2/SW1/G2/G1/DP/G
	movwf	TRISB

	clrf	TRISC			; D/E/C/B/A/PWM/H/F (all output)

	; Setup weak pull-up with PORT B and TMR0 prescaler
	; Setup OPTION_REG
	movlw	B'00000100'		; /RBPU, Sel Fosc/4, TMR0 Prescaler = 1/32
	movwf	OPTION_REG

	; Start PWM 50kHz, Duty=0.5
	banksel	PR2
	movlw	D'63'			; PR2 <= 63
	movwf	PR2
	banksel	CCPR1L
	movlw	D'20'			; CCPR1L <= 128 >> 2
	movwf	CCPR1L	
	movlw	B'00001100'		; Select PWM mode
	movwf	CCP1CON
	bsf		T2CON, TMR2ON		; Start PWM

	; Setup interrupt
	banksel	PIE1
	bsf		PIE1, TMR1IE		; Enable TMR1 INT
	
	banksel	INTCON
	bsf		INTCON, PEIE		; Enable peripheral INT (used by TMR1)
	bsf		INTCON, TMR0IE	; Enable TMR0 INT 
	bsf		INTCON, GIE		; Enable Global INT

	; Setup A/D converter
	banksel	ADCON1
	movlw	B'00001110'		; ADFM=0, AN0=analog, other ANs = digital
	movwf	ADCON1

	banksel	ADCON0
	movlw	B'11000001'		; Int RC clock, channel = 0, ADON
	movwf	ADCON0

	; Setup TMR1
	movlw	B'00000101'		; Prescaler = 1/1, Sel Fosc/4, TMR1 ON
	movwf	T1CON

	movlw	0x07				; Set 0x07 pritor to access tables
	movwf	PCLATH

	clrf	time_sec			; Clear time at boot
	clrf	time_min
	clrf	time_hr
	movlw	D'50'
	movwf	count50

	; Turn off beeper (negative)
	bsf		PORTA, BEEPER

; Free running "main" loop head
loop

	; SW bounce remover
	movfw	PORTB
	andlw	0xf0
	xorwf	sw_prev, W
	btfss	STATUS, Z
	goto	sw_end

sw1_check
	btfss	PORTB, SW1
	goto	sw1_pressed
	bcf		sw_latch, SW1
	goto	sw2_check

sw1_pressed
	btfsc	sw_latch, SW1
	goto	sw2_check
	bsf		sw_latch, SW1
	; SW1 pressed
	; increment hour
	movlw	D'23'
	xorwf	time_hr, W
	btfss	STATUS, Z
	incf	time_hr, F
	btfsc	STATUS, Z
	clrf	time_hr

sw2_check
	btfss	PORTB, SW2
	goto	sw2_pressed
	bcf		sw_latch, SW2
	goto	sw3_check

sw2_pressed
	btfsc	sw_latch, SW2
	goto	sw3_check
	bsf		sw_latch, SW2
	; SW2 pressed
	; increment min
	movlw	D'59'
	xorwf	time_min, W
	btfss	STATUS, Z
	incf	time_min, F
	btfsc	STATUS, Z
	clrf	time_min

sw3_check
	btfss	PORTB, SW3
	goto	sw3_pressed
	bcf		sw_latch, SW3
	goto	sw4_check

sw3_pressed
	btfsc	sw_latch, SW3
	goto	sw4_check
	bsf		sw_latch, SW3
	; SW3 pressed
	; reset sec
	clrf	time_sec
	movlw	D'50'
	movwf	count50

sw4_check
	btfss	PORTB, SW4
	goto	sw4_pressed
	bcf		sw_latch, SW4
	goto	sw_end

sw4_pressed
	btfsc	sw_latch, SW4
	goto	sw_end
	bsf		sw_latch, SW4
	; SW4 pressed
	; toggle pwm on/off (display on/off)
	btfss	T2CON, TMR2ON
	goto	pwm_enable

	; pwm_disable
	bcf		T2CON, TMR2ON	
	clrf	CCP1CON			; Clear CCP1CON makes pwm pin low level.
	goto	sw_end

pwm_enable
	movlw	B'00001100'		; Select PWM mode
	movwf	CCP1CON
	bsf		T2CON, TMR2ON		; Start PWM

sw_end
	movfw	PORTB
	andlw	0xf0
	movwf	sw_prev

	; Copy sec to digit5,6
	movfw	time_sec
	call	getbcd
	movfw	bcd_d1
	movwf	digit_6
	movfw	bcd_d10
	movwf	digit_5
	
	; Copy min to digit3,4
	movfw	time_min
	call	getbcd
	movfw	bcd_d1
	addlw	B'10000000'
	movwf	digit_4
	movfw	bcd_d10
	movwf	digit_3

	; Copy hour to digit1,2
	movfw	time_hr
	call	getbcd
	movfw	bcd_d1
	addlw	B'10000000'
	movwf	digit_2
	movfw	bcd_d10
	movwf	digit_1

	; Brightness control
	;call	wait_10us			; This sampling time seems not to be required.
	;call	wait_10us			; Because it always connected to ch1 ?
	bsf		ADCON0, GO_DONE
wait_ad_done
	btfsc	ADCON0, GO_DONE
	goto	wait_ad_done

	; if advalue > 123 goto max_brightness
	movfw	ADRESH
	sublw	D'123'
	btfss	STATUS, C
	goto	max_brightness

	; where x <= 123
	; On  time = 256 - (253 - x) = 3 + x
	; Off time = 256 - (117 + x) = 139 - x
	movfw	ADRESH
	sublw	D'253'
	movwf	digit_on_time

	movlw	D'117'
	addwf	ADRESH, W
	movwf	digit_off_time
	goto	loop

max_brightness
	; On  time = 256-130 = 126
	; Off time = 256-240 = 16 
	movlw	D'130'
	movwf	digit_on_time
	movlw	D'240'
	movwf	digit_off_time
	goto	loop


; Dynamic display driver
; Invoked when TMR0 overflows
isr_tmr0
	
	bcf		INTCON, TMR0IF

	btfss	digit_ctrl, digit_on
	goto	isr_tmr0_off_digit

	bcf		digit_ctrl, digit_on

	; Set ON duration
	movfw	digit_on_time
	movwf	TMR0

	; Read segment data from buffer
	movlw	DIGIT_BASE		; w = *(DIGIT_BASE + digit_ptr)
	addwf	digit_ptr, W
	movwf	FSR
	movfw	INDF

	movwf	digit_temp
	
	; Check blanking bit 
	btfsc	digit_temp, BLANKING
	goto	isr_ret

	; Turn on DP if required
	btfsc	digit_temp, WITH_DP
	bsf		PORTB, SEG_DP

	; Get font data
	call	getfont

	; Handle segment G
	movwf	digit_temp		; Segment G resides on PORTB (as PWM uses PORTC<2>)
	btfsc	digit_temp, 2
	bsf		PORTB, SEG_G

	; RC2(CCP1) is used for PWM, mask it.
	andlw	B'11111011'	
	movwf	PORTC
	
	; Turn on corresponding digit
	movfw	digit_ptr
	xorlw	0
	btfsc	STATUS, Z
	bsf		PORTB, DIG_1

	movfw	digit_ptr
	xorlw	1
	btfsc	STATUS, Z
	bsf		PORTB, DIG_2

	movfw	digit_ptr
	xorlw	2
	btfsc	STATUS, Z
	bsf		PORTA, DIG_3

	movfw	digit_ptr
	xorlw	3
	btfsc	STATUS, Z
	bsf		PORTA, DIG_4

	movfw	digit_ptr
	xorlw	4
	btfsc	STATUS, Z
	bsf		PORTA, DIG_5

	movfw	digit_ptr
	xorlw	5
	btfsc	STATUS, Z
	bsf		PORTA, DIG_6

	goto	isr_ret
	
isr_tmr0_off_digit

	bsf		digit_ctrl, digit_on

	; Set off duration
	movfw	digit_off_time
	movwf	TMR0

	; Turn off all digit/segment
	movlw	B'11010001'
	andwf	PORTA, F

	movlw	B'11110000'
	andwf	PORTB, F

	clrf	PORTC

	; Increment digit pointer
	movfw	digit_ptr			; if(digit_ptr == 5)
	xorlw	D'5'				;   digit_ptr = 0;
	btfss	STATUS, Z			; else
	incf	digit_ptr, F		;   digity_ptr ++;
	btfsc	STATUS, Z
	clrf	digit_ptr

	goto	isr_ret

	
; Clock manipulator
; Invoked when TMR1 overflows
isr_tmr1

	bcf		PIR1, TMR1IF

	; Timer counter compensation
	movlw	0x02
	addwf	TMR1L, F
	btfsc	STATUS, C
	incf	TMR1H, F
	movlw	0x06
	movwf	TMR1H

	; 50Hz divider
	decfsz	count50, F
	goto	isr_ret
	movlw	D'50'
	movwf	count50

	; Enter here at every second
	
	; Second
	incf	time_sec, F
	movfw	time_sec
	xorlw	D'60'
	btfss	STATUS, Z
	goto	isr_ret
	clrf	time_sec

	; Minute
	incf	time_min, F
	movfw	time_min
	xorlw	D'60'
	btfss	STATUS, Z
	goto	isr_ret
	clrf	time_min

	; Hour
	incf	time_hr, F
	movfw	time_hr
	xorlw	D'24'
	btfss	STATUS, Z
	goto	isr_ret
	clrf	time_hr
	goto	isr_ret


; BCD conversion
; Input W , Output 3 digit of bcd at bcd_d100, bcd_d10, bcd_d1
getbcd
	clrf	bcd_d100
	clrf	bcd_d10
	movwf	bcd_temp

getbcd_loop1
	sublw	D'99'
	btfsc	STATUS, C
	goto	getbcd_loop2

	incf	bcd_d100, F
	movlw	D'100'
	subwf	bcd_temp, F
	movfw	bcd_temp
	goto	getbcd_loop1

getbcd_loop2
	movfw	bcd_temp
	sublw	D'9'
	btfsc	STATUS, C
	goto	getbcd_lt10

	incf	bcd_d10, F
	movlw	D'10'
	subwf	bcd_temp, F
	movfw	bcd_temp
	goto	getbcd_loop2

getbcd_lt10
	movfw	bcd_temp
	movwf	bcd_d1
	return

; 10us timer (not used)
;wait_10us
;	nop
;	nop
;	movlw	9
;	movwf	wait_cnt
;wait_10us_0
;	decfsz	wait_cnt, F	;4+3*9-1=30     
;	goto	wait_10us_0
;	return				;30+2=32


	ORG		0x700
; Font data
getfont
	andlw	0x0f
	addwf	PCL, F
			; DECBAGHF
	retlw	B'11100000'	; 0
	retlw	B'00000110'	; 1
	retlw	B'11011000'	; 2
	retlw	B'10111000'	; 3
	retlw	B'00000111'	; 4
	retlw	B'10101001'	; 5
	retlw	B'11110000'	; 6
	retlw	B'00011010'	; 7
	retlw	B'11111001'	; 8
	retlw	B'00011011'	; 9
	; Followings are not used, difficult to get non-numeric result
	retlw	B'00000000'	; a
	retlw	B'00000000'	; b
	retlw	B'00000000'	; c
	retlw	B'00000000'	; d
	retlw	B'00000000'	; e
	retlw	B'00000000'	; f


	END                       ; directive 'end of program'

