;;  Original Code Copyright (c) 2006 Phil Short
;;
;;  This program is provided free for you to use in any way that you wish,
;;  subject to the laws and regulations where you are using it.  Due diligence
;;  is strongly suggested before using this code.
;;
;;  The Author makes no warranty of any kind, express or implied, with regard
;;  to this program or the documentation contained in this document.  The
;;  Author shall not be liable in any event for incidental or consequential
;;  damages in connection with, or arising out of, the furnishing, performance
;;  or use of these programs.
;;
;;  5 Sept 2006 PJS First diagnostics test version, derived from the 0701a
;;  working version.
;;
;;  15 Aug 2007 PJS Modified to use external 18.432 MHz oscillator
;;
;;  17 Jan 2009 RL Added a LED (PIC output) test sub-routine
;;
;;  Hardware mappings
;;  PIN 3  (RA4) - triac driver 0 (output)
;;  PIN 13 (RA0) - triac driver 1 (output) - flashing heartbeat LED (sink)
;;  PIN 12 (RA1) - triac driver 2 (output)
;;  PIN 11 (RA2) - triac driver 3 (output)
;;  PIN 10 (RC0) - triac driver 4 (output) - flashing zero-cross detect LED
;;  PIN 9  (RC1) - triac driver 5 (output) - 'one-shot' rx_okay LED
;;  PIN 8  (RC2) - triac driver 6 (output) - 'one-shot' rx_frame_error LED
;;  PIN 7  (RC3) - triac driver 7 (output) - 'one-shot' rx_overrun LED
;;  PIN 2  (RA5) - clock input
;;  PIN 4  (RA3) - zero-crossing (input only)
;;  PIN 5  (RC5/RX) - uart_in (input)
;;  PIN 6  (RC4/TX) - uart_out (output)
;;
;;  Outputs are good to drive 15 mA opto-isolators.
;;
;;  The interrupt used by this program is timer0, which is intended to create
;;  a periodic (32 us) clock.  Since the timer is reset inside of the ISR, the
;;  foreground routine should disable interrupts for as short a time as
;;  possible (or not at all, if this can be accomplished).

 LIST R=DEC
 INCLUDE "p16f688.inc"

 __CONFIG _FCMEN_OFF & _IESO_OFF & _BOD_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _EC_OSC
;; __CONFIG _FCMEN_OFF & _IESO_OFF & _BOD_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTOSCIO
	
 CBLOCK 0x020
;; variables used by the interrupt service routine (ISR)
  _w, _status

  state_bitmap
  porta_image
  portc_image

  isr_counter
  isr_counter_msb
  hz_counter

  rx_counter
  rx_frame_err_counter
  rx_overrun_counter

  LED_Dwn_Cnt
  Delay_Cnt
  Dwn_Cnt
  Dwn1_Cnt
	
 ENDC	

;; units in Hz for the following rate constants
#define CLOCKRATE 18432000
#define BAUDRATE 57600
#define TIMERRATE 30720
	
#define TIMER_CONSTANT (CLOCKRATE /(4 * TIMERRATE))

#define BAUDRATE_CONSTANT (CLOCKRATE / (16 * BAUDRATE) - 1)

#define STATE_BITMAP_REG state_bitmap
#define TIMER_BIT 7

#define ZC_STATE_REG state_bitmap

;; approximate value for 60 hertz
#define TIMECONSTANT_1SEC_60HZ 240

;; port A LED
#define HEARTBEAT_LED_BIT 1
#define ZC_INPUT_BIT 3
	
;; port C LEDs

#define HZ_LED_BIT 0x01
#define RX_LED_BIT 0xFD
#define RX_FRAME_ERR_LED_BIT 0xFB
#define RX_OVERRUN_LED_BIT 0xF7
	
#define LED_Cnt 3     				; Number on times to run LED test
#define Delay_Max 75 				; Number of times to run delay loop
#define Max_Dwn 100  				; Outer Delay loop count
#define Max_Dwn1 150 				; Inner Delay loop count


 org 0
	nop
	goto initialize

 org 4
;; interrupt routine
;; Just the timer interrupt for now...

Int
	movwf	_w						; save 'w'
	movf	STATUS, w				; src, dst
	clrf	STATUS					; unnecessary, as nothing changes the status
									; bits RP0, RP1, IRP
	movwf	_status					; save f to _status
									; maybe save PCLATH register

	bcf	INTCON,T0IF					; reset the interrupt
	
	movlw	TIMER_CONSTANT			; 
	subwf	TMR0,f					; subtraction is atomic, hopefully

	bsf	STATE_BITMAP_REG, TIMER_BIT

	movf	_status, w				; fairly standard interrupt epilogue code
	movwf	STATUS
	swapf	_w,f
	swapf	_w,w
	retfie

initialize:
	clrf	STATUS
	
	clrf	state_bitmap

	clrf	isr_counter
	clrf	isr_counter_msb
	clrf	hz_counter

	clrf	rx_counter
	clrf	rx_frame_err_counter
	clrf	rx_overrun_counter
	
;; configure the I/O ports

	bcf	STATUS,RP0					; select bank 0
	movlw	7
	movwf	CMCON0					; disable the comparators

	clrf	porta_image				; should turn some of the LEDs off
	clrf	portc_image				; and the rest of the LEDS
	clrf	PORTA
	clrf	PORTC

	bsf	STATUS,RP0

	movlw	0x74
	movwf	OSCCON ^ 0x80
	
	movlw	0x20
	movwf	TRISA ^ 0x80			; bit 5 is the clock input bit
	movlw	0xF0					; upper four bits are tri-stated for uart op.
	movwf	TRISC ^ 0x80			; 
	clrf	ANSEL ^ 0x80
	bcf	OPTION_REG ^ 0x80,7			; global weak-pullup enable
	movlw	0x20	    			; turn on weak pullup for unused input (RA5)
	movwf	WPUA ^ 0x80
	bcf	STATUS,RP0
	movlw b'000011111'
	movwf porta_image				; values to turn off LEDs on PORTA
	movwf portc_image				; values to turn off LEDs on PORTC
	call LED_Test_Sub				; Call to LED Test Sub-routine

;; configure the UART

	clrf	BAUDCTL
	movlw	BAUDRATE_CONSTANT	
	movwf	SPBRG
	clrf	SPBRGH
		
	movlw	0x24					; enable 8-bit transmitter in async mode
									; (x16 baud divisor)
	movwf	TXSTA
	movlw	0x90					; enable serial port with continuous 8-bit rx
	movwf	RCSTA
	
;; setup timer - desired action is an interrupt every 60 instructions

	bsf	STATUS,RP0
	movlw	0x08					; assign prescaler to WDT (i.e. not used)
	movwf	OPTION_REG ^ 0x80		; timer mode
	bcf	STATUS,RP0
	movlw	(~TIMER_CONSTANT)
	movwf	TMR0
	movlw	(1 <<GIE) | (1<<T0IE)
	movwf	INTCON
;;
;; Main event loop
;;
mainloop:
	movf	porta_image,w
	movwf	PORTA
	movf	portc_image,w
	movwf	PORTC
	
	btfss	STATE_BITMAP_REG,TIMER_BIT	; interrupt occurred?
	goto	test_zc

	bcf	STATE_BITMAP_REG,TIMER_BIT
	
;; 30 KHz interrupt just occured
	incf	isr_counter,f
	btfss	STATUS,Z
	goto	test_zc
;; 30 Khz interrupt now divided down to ~120 Hz
	movlw	HEARTBEAT_LED_BIT
	incf	isr_counter_msb,f
	incf	isr_counter_msb,f 		; now divided down to ~ 1Hz
	btfss	STATUS,Z				; 
	goto	not_heartbeat_z
	xorwf	porta_image,f			; toggle the heartbeat LED
	movlw	16						; make LED on/off time 1s (instead of 1.18 s)
	addwf	isr_counter_msb,f
	
not_heartbeat_z:	
;; tentatively clear the three LEDs related to UART operations
	
	movlw	~(RX_LED_BIT & RX_FRAME_ERR_LED_BIT & RX_OVERRUN_LED_BIT)
	iorwf	portc_image,f			; turn three LEDs off
	
;; check if the rx LED should actually be on

	movf	rx_counter,f
	btfsc	STATUS,Z
	goto	not_rx_LED
	movlw	RX_LED_BIT
	andwf	portc_image,f
	decf	rx_counter,f
	decf	rx_counter,f
	
not_rx_LED:
	movf	rx_frame_err_counter,f
	btfsc	STATUS,Z
	goto	not_rx_frame_err_LED
	movlw	RX_FRAME_ERR_LED_BIT
	andwf	portc_image,f
	decf	rx_frame_err_counter,f
	decf	rx_frame_err_counter,f
	
not_rx_frame_err_LED:
	movf	rx_overrun_counter,f
	btfsc	STATUS,Z
	goto	not_rx_overrun_LED
	movlw	RX_OVERRUN_LED_BIT
	andwf	portc_image,f
	decf	rx_overrun_counter,f
	decf	rx_overrun_counter,f
	
not_rx_overrun_LED:
;;
;; End of code that is executed when timer interrupt was received
;;
;; Now, check the hardware zero-crossing bit
;;

test_zc:
	btfss	PORTA,ZC_INPUT_BIT
	goto	zc_off
;; zero-crossing detected
	btfsc	ZC_STATE_REG,2			; already handled it?
	goto	test_uart_rx			; jump if yes
	incf	ZC_STATE_REG,f			; otherwise do a little bit of filtering
	btfss	ZC_STATE_REG,2			; just did a 3 -> 4 transition?
	goto	test_uart_rx
	
	movlw	HZ_LED_BIT				; increment the hz counter
	incf	hz_counter,f	
	incf	hz_counter,f
	btfss	STATUS,Z
	goto	test_uart_rx
	
	xorwf	portc_image,f			; toggle the LED if necessary
	movlw	16
	addwf	hz_counter,f
	goto	test_uart_rx
	
zc_off:
	movlw	0xF8
	andwf	ZC_STATE_REG,f			; zc off -> reset the debounce filter
;;
;; done handling possible zero-crossing event, now look at uart events
;;

test_uart_rx:
	
	btfss	PIR1,RCIF				; skip if UART has a rx character to process
	goto	mainloop				; no character available

	btfss	RCSTA,2					; test for framing error
	goto	no_frame_err

	movlw	TIMECONSTANT_1SEC_60HZ
	movwf	rx_frame_err_counter 	; mark occurance of error - (re)starts LED
	movf	RCREG,w
	goto	mainloop

no_frame_err:						; 
	movf    RCREG,w					; read and discard the received character

	movlw	TIMECONSTANT_1SEC_60HZ
	
	btfsc	RCSTA,1					; now check for overrun error
	goto	overrun_err				;

	movwf	rx_counter				; no error - (re)start the rx LED
	goto	mainloop

overrun_err:						; should never occur, but handle it anyway
	movwf	rx_overrun_counter		; (re)start the rx overrun LED
	bcf	RCSTA,CREN
	bsf	RCSTA,CREN					; restart the uart
	goto	mainloop

;; New test routine to verify all LEDs are functional

LED_Test_Sub:
	movlw LED_Cnt
	movwf LED_Dwn_Cnt
LED_Test:
	movlw b'00000000'   
	movwf PORTA         			; LED 0-3 On  
	movlw b'00000000'   
	movwf PORTC        				; LED 4-7 On
	call Delay_Loop
	call Delay_Loop
	movlw b'00011111'   
	movwf PORTA        				; LED 0-3 Off
	movwf PORTC        				; LED 4-7 Off
	call Delay_Loop
	call Delay_Loop
	movlw b'00001111'   			; LED 0 ON  
	movwf PORTA
	call Delay_Loop
	movlw b'00011110'   			; LED 1 ON  
	movwf PORTA
	call Delay_Loop
	movlw b'00011101'   			; LED 2 ON  
	movwf PORTA
	call Delay_Loop
	movlw b'00011011'   			; LED 3 ON  
	movwf PORTA
	call Delay_Loop
	movlw b'00010111'   
	movwf PORTA         			; LED 0-3 Off
	movlw b'00001110'   			; LED 4 ON  
	movwf PORTC
	call Delay_Loop
	movlw b'00001101'   			; LED 5 ON  
	movwf PORTC
	call Delay_Loop
	movlw b'00001011'   			; LED 6 ON  
	movwf PORTC	
	call Delay_Loop
	movlw b'00000111'   			; LED 7 ON  
	movwf PORTC
	call Delay_Loop
	movlw b'00001111'   
	movwf PORTC         			; LED 4-7 Off
	
	decfsz LED_Dwn_Cnt,f			; Decement counter 
	goto LED_Test
	return

Delay_Loop:
	movlw Delay_Max
	movwf Delay_Cnt
Reset_Cnt:
	movlw Max_Dwn
	movwf Dwn_Cnt
D_Cnt:
	movlw Max_Dwn1
	movwf Dwn1_Cnt
D1_Cnt:
	decfsz Dwn1_Cnt,f
	goto D1_Cnt
	decfsz Dwn_Cnt,f
	goto D_Cnt
	decfsz Delay_Cnt,f
	goto Reset_Cnt
	return


  END
