;; filename:	renard-20071209.asm
;; Copyright (c) 2006,2007 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.
;;
;;  --------------------------------------------------------------------------
;;  NOTE:
;;  This code is configured for regular use (the firmware generates 30 uS (at
;;  60 Hz) pulses to drive the SSRs, synchronized to the zero-crossing input
;;  pin).  Here are the DEFINE statements to change for PWM use (generates a
;;  variable width pulse sync'd to the zero-crossing signal) or DC use
;;  generates a variable-width pulse, not sync'd to anything).

;; PWM_build can be either 0 or 1
#define PWM_build 0
;; DC_build can be either 0 or 1 (it should be 0 if PWM_build is 0)
#define DC_build 0

 #define CLOCKRATE 18432000
 #define BAUDRATE 57600

;; CTR_LOCKOUT should be 1 for the DC build, can be a somewhat larger number
;; for non-PWM or non-DC builds.

 #define CTR_LOCKOUT 15

;; For positive_true outputs, remove the following define (normally, this
;; should be left in place).
 #define OUTPUT_NEGATIVE_TRUE
	
;;  --------------------------------------------------------------------------
;;
;;  13 May, 2006  PJS First version sent out for review, discussion (thought
;;  to be working version.
;;
;;  20 May, 2006  PJS Modifications for new protocol, shorter timer interval,
;;  some code optimization on ISR.  Fixed bug in the code for decoding ESC
;;  sequences.
;;
;;  19 June, 2006 PJS Modified uart access sequence (changed order of reading
;;  the RCSTA and RXREG registers, made sure to clear rx errors).
;;  Moved this history note to a slightly lower location in the file.
;;
;;  1 July 2006 PJS Fixed bug relating to pass-through function.
;;
;;  8 Sept 2006 PJS Tri-state RA5 (in case there is an oscillator connected,
;;  even though this particular code uses the internal oscillator).
;;
;;  23 Oct 2006 PJS fixed silly bug introduced in last version.  This bug would
;;  cause RA0-RA2 and RA3 to remain tri-stated, preventing these first four
;;  outputs from working.
;;
;;  07 Apr 2007 PJS backtracking from later version - parameterized baud rate,
;;  line frequency, etc (i.e. added #defines).
;;  07 Apr 2007 PJS changed from itnernal to external oscillator, and changed
;;  baudrate to 57600.
;;  08 Apr 2007 PJS folded in the lockout code (still set up for 60 hz only)
;;  08 Apr 2007 PJS added line-frequency detection code (hopefully more robust
;;  in the presence of noise than the last attempt).
;;
;;  11 Jul 2007 PJS folded in the PWM and DC-only code, using build-time
;;  defines.  Among other things, this involved re-arranging the code and
;;  renaming the ctr_lockout variable.
;;
;;  11 Aug 2007 PJS made changes to allow zero-crossing SSRs to turn on with
;;  brightness of 255 (basically this change turns the output on all of the
;;  time if the brightness is 255).
;;
;;  7 Sept 2007 PJS added option for positive-true (source) outputs.
;;
;;  29 Dec 2007 PJS fixed bug that left all lights on at power-up.
;;
;;  Hardware mappings
;;  PIN 13 (RA0) - triac driver 1 (output)
;;  PIN 12 (RA1) - triac driver 2 (output)
;;  PIN 11 (RA2) - triac driver 3 (output)
;;  PIN 4  (RA3) - zero-crossing (input only)
;;  PIN 3  (RA4) - triac driver 0 (output)
;;  PIN 10 (RC0) - triac driver 4 (output)
;;  PIN 9  (RC1) - triac driver 5 (output)
;;  PIN 8  (RC2) - triac driver 6 (output)
;;  PIN 7  (RC3) - triac driver 7 (output)
;;  PIN 6  (RC4/TX) - uart_out (output)
;;  PIN 5  (RC5/RX) - uart_in (input)
;;  PIN 2  (RA5) - clock input
;;
;;  Weak pullups are used on pin RA5 (currently a spare pin).
;;  Outputs are good to drive 15 mA opto-isolators.
;;
;;  This version of the code does not have any fast-path through the ISR, has
;;  good worst-case ISR path length (but mediocre average-case path length),
;;  with the main virtue of small size and simple, easier to understand code.
;;
;;  This code fits in less than 2K of program space, and takes advantage of
;;  that fact at various places to save a few bytes of code, and so it may
;;  not necessarily work as intended if the code size is increased.
;; 
;;  This code does not make use of automatic baud-rate detection, nor does
;;  it use timer1, the low-power, wake-up, analog comparator or A/D
;;  capabilities of the chip.
;;
;;  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"

;; To use the internal clock of the PIC, comment out this line, remove the
;; comments on the next line, and change the clock rate from 18432000 to
;; 8000000.

 __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

  ctr_ckt0, ctr_ckt1, ctr_ckt2, ctr_ckt3
  ctr_ckt4, ctr_ckt5, ctr_ckt6, ctr_ckt7

  slot_id_ckt0, slot_id_ckt1, slot_id_ckt2, slot_id_ckt3	
  slot_id_ckt4, slot_id_ckt5, slot_id_ckt6, slot_id_ckt7	

  rx_copycount
  out_map, disable_map, cur_mask

  rx_char, rx_state

  zc_delayline

  timer0_preset

  ctr_timeslot
	
 ENDC	

;; assigned numbers:

 #define DEFAULT_LINERATE 50
 #define MAX_LINERATE 70
	
;; derived numbers:

;; initializer for baudrate generator (based on formula from datasheet)
	
 #define BAUD_INIT (CLOCKRATE/BAUDRATE/16 - 1)

;; initializer for cell timer (replaces TIMER_MAGIC0/1).  This assumes 50 Hz,
;; but it will self-adjust after 1/2 AC cycle (better too slow than too fast).

 #define CELL_RATE ((2*DEFAULT_LINERATE) * 256)
 #define CELL_TMR_INIT (CLOCKRATE/4/CELL_RATE)

;; Number used to prevent overly large values from being stored in the
;; timer0_preset variable register.

 #define MIN_TMR1H ((CLOCKRATE/4)/MAX_LINERATE/256/2)
	 	
;; I/O bit definition

 IF (!(PWM_build && DC_build))
   #define ZC_BIT   3
   #define ZC       PORTA,(ZC_BIT)
   #define ZC_MASK  (1<<(ZC_BIT))
 ENDIF

 #define TEST_STROBE PORTA,0
;; ---------------------------------------------------------------------------

 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
	movwf	_status		; save f to _status
				; maybe save PCLATH register

	bcf	INTCON,T0IF	; reset the interrupt
	
	movf	timer0_preset,w
	addwf	TMR0,f

  IF (PWM_build && DC_build)
	incfsz  ctr_timeslot,f	; decrement slot time counter.
	goto	do_regular
  ELSE	
	btfss	ZC		; skip if not zero-crossing
	goto	do_regular
  ENDIF
;;
;; ISR code executed if ZC is detected (or counter expires, if DC build)
;;
do_zc:		
	movlw	CTR_LOCKOUT	; dead periods for dimmest possible values
	movwf	ctr_timeslot

turn_off_outputs:
	decf	ctr_timeslot,f	; restore ctr_timeslot in case of jump to here

;;
;; Initialize the ctr_ckt registers for upcoming half-cycle
;;

	movf	slot_id_ckt0,w
	movwf	ctr_ckt0
	
	movf	slot_id_ckt1,w
	movwf	ctr_ckt1
	
	movf	slot_id_ckt2,w
	movwf	ctr_ckt2
	
	movf	slot_id_ckt3,w
	movwf	ctr_ckt3
	
	movf	slot_id_ckt4,w
	movwf	ctr_ckt4
	
	movf	slot_id_ckt5,w
	movwf	ctr_ckt5
	
	movf	slot_id_ckt6,w
	movwf	ctr_ckt6
	
	movf	slot_id_ckt7,w
	movwf	ctr_ckt7

;; turn outputs off (positive if NEGATIVE_TRUE, ground if POSTIVE_TRUE)

  IF (!PWM_build)
    IFDEF OUTPUT_NEGATIVE_TRUE
	movlw	0xFF
    ELSE
	movlw	0
    ENDIF
	movwf	PORTC
	movwf	PORTA
  ELSE
	swapf	disable_map,w	; disable_map tells which outputs to turn off
	movwf	PORTC		; at the zero-crossing time
	
	rrf	disable_map,w
	andlw	0x7
	btfsc	STATUS,C
	iorlw	0x10
	movwf	PORTA
  ENDIF
	goto	isr_exit

;;
;; handle the normal case (next timeslot when no zero-crossing, no timeout).
;;
do_regular:

 IF (!(PWM_build && DC_build))
	incf	ctr_timeslot,f
	btfsc	STATUS,Z
	goto	turn_off_outputs; ctr_timeslot will be decremented after goto
 ENDIF

	clrf	out_map		; map of outputs
			
	incfsz	ctr_ckt0,f
	bsf	out_map,3	; turn off this SSR
	incfsz	ctr_ckt1,f
	bsf	out_map,0
	incfsz	ctr_ckt2,f
	bsf	out_map,1
	incfsz	ctr_ckt3,f
	bsf	out_map,2
	incfsz	ctr_ckt4,f
	bsf	out_map,4		;
	incfsz	ctr_ckt5,f
	bsf	out_map,5
	incfsz	ctr_ckt6,f
	bsf	out_map,6
	incfsz	ctr_ckt7,f
	bsf	out_map,7

 IF PWM_build
   IFDEF OUTPUT_NEGATIVE_TRUE
	swapf	out_map,w
	andwf	PORTC,f
	movf	out_map,w
	andlw	0x0F
	addlw	8
	andwf	PORTA,f
   ELSE
	swapf	out_map,w
	xorlw	0xFF
	iorwf   PORTC,f
	comf	out_map,w
	andlw	0x0F
	addlw	8
	iorwf	PORTA,f
   ENDIF
 ELSE
   IFDEF OUTPUT_NEGATIVE_TRUE
	swapf	out_map,w
	movwf	PORTC
	movf	out_map,w
   ELSE
	swapf	out_map,w
	xorlw	0xFF
	movwf	PORTC
	comf	out_map,w
   ENDIF
	andlw	0x0F
	addlw	8
	movwf	PORTA

 ENDIF
isr_exit:	

	movf	_status, w
	movwf	STATUS
	swapf	_w,f
	swapf	_w,w
	retfie

;;
;; End of Interrupt Routine, start of Foreground code.
;;

initialize:
;;
;; initialize some registers
;;
	clrf	slot_id_ckt0	; set all 'lamps' to lowest intensity
	clrf	slot_id_ckt1
	clrf	slot_id_ckt2
	clrf	slot_id_ckt3
	clrf	slot_id_ckt4
	clrf	slot_id_ckt5
	clrf	slot_id_ckt6
	clrf	slot_id_ckt7

	clrf	rx_state
	clrf	ctr_timeslot

	movlw	~CELL_TMR_INIT
	movwf	timer0_preset
	
;; configure the I/O ports

	bcf	STATUS,RP0	; select bank 0
	movlw	7
	movwf	CMCON0		; disable the comparators
	clrf	out_map
 IFDEF OUTPUT_NEGATIVE_TRUE
	movlw	0xFF
   ELSE
	movlw	0x00
   ENDIF
	movwf	disable_map	; value to turn off outputs at zc time

	movwf	PORTA
	movwf	PORTC

	bsf	STATUS,RP0

	movlw	0x74
	movwf	OSCCON ^ 0x80
	
	movlw	0x20		; bit 5 is the clock input bit...just in case
	movwf	TRISA ^ 0x80	; h/w ignores bit 3 of this register

	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

;; configure the UART

	clrf	BAUDCTL
	movlw	BAUD_INIT;
	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
	
;; Set up timer1 - simple 16-bit counter, used to count interval between zero-
;; crossings.
	movlw	1
	movwf	T1CON

;; Set up timer0 - generate periodic interrupt at 512 times powerline freq

	bsf	STATUS,RP0
	movlw	0x08		; assign prescaler to WDT (i.e. not used)
	movwf	OPTION_REG ^ 0x80; clear all bits -> timer mode, clk/2
	bcf	STATUS,RP0
	movf	timer0_preset,w
	movwf	TMR0
	movlw	(1 <<GIE) | (1<<T0IE)
	movwf	INTCON		; enable interrupts

;; Next two wait loops ensure that we are on the rising edge of ZC when we
;; first enter the mainloop, so that we don't put an overly small value in the
;; timer0_preset register.

  IF !(PWM_build && DC_build)	
wait_nzc:
	btfsc	ZC
	goto	wait_nzc
wait_zc:
	btfss	ZC
	goto	wait_zc

	clrf	zc_delayline
  ENDIF

mainloop:
  IF !(PWM_build && DC_build)	
	call	zc_filter
  ENDIF
	btfss	PIR1,RCIF	; skip if UART has a rx character to process
	goto	mainloop	; no character available

	movf    RCREG,w
	movwf	rx_char		; and save a working copy
	
	movlw	6		; Rx error mask (framing error, overrun error)
	andwf	RCSTA,w		; get (masked) rx status
	btfss	STATUS,Z
	goto	rx_error	; goto error routine
	movf	rx_char,w	; retrieve the character
	addlw	0x82		; test for sync character
	btfss	STATUS,Z	; skip if sync character
	goto	rx_not_sync	; jump to code for processing non-sync
;; 
;; process sync character
;;
	bcf	rx_state,3	; exit the discarding state
	bcf	rx_state,2	; start out just echoing data
	bsf	rx_state,1
rx_echo:	
	movf	rx_char,w	; echo most recent character to transmitter
	movwf	TXREG
	goto	mainloop

rx_error:
	bcf	RCSTA,CREN
	bsf	RCSTA,CREN
	bsf	rx_state,3	; start discarding
	goto	mainloop

;; 
;; process non-sync character
;;
;; rx_state:
;; xxxx1xxx -> discarding data (startup, just after rx error)
;; xxxx01xx -> echoing data (count was > 0 or after end of local data)
;; xxxx001x -> expecting command/address byte (just after SYNC)
;; xxxx0001 -> process character after an ESC character
;; xxxx0000 -> process short-form data character

rx_not_sync:
	addlw	1		; test for PAD character - discard if yes
	btfsc	STATUS,Z
	goto	mainloop
	
	btfsc	rx_state,3	; skip if not discard state
	goto	mainloop
	btfsc	rx_state,2	; skip if non-echo state
	goto	rx_echo		; pure echo
;;
;; handle non-echo cases
;;
	btfss	rx_state,1	; skip if first character after SYNC
	goto	decode_packet_body
decode_cmd_byte:		; first character after the SYNC
	bsf	rx_state,2	; just in case packet is not for us.
	btfss	rx_char,7
	goto	rx_echo		; long form packet, just echo it.

	decf	rx_char,f	; 
	btfsc	rx_char,7	; value 0x7F after dec?  If so, it's for us
	goto	rx_echo

	clrf	rx_state	; next state is normal body
	incf	rx_char,f

	movlw	1
	movwf	cur_mask
	clrf	rx_copycount
	
	goto	rx_echo		; get next character (and echo the '0x80').
;;
;; done initializing packet, now handle the data
;;
decode_packet_body:	

	btfss	rx_state,0	; skip if previous char was ESC
	goto	previous_not_ESC
	movlw	0x4E
	addwf	rx_char,f	; decode the ESC charactor
	goto	after_ESC_decoding
previous_not_ESC:	
	bsf	rx_state,0	; just in case current char is ESC
	movlw	0x7F
	xorwf	rx_char,w	; check for ESC
	btfsc	STATUS,Z	; skip if not ESC
	goto	mainloop	; go and discard the ESC
after_ESC_decoding:
	clrf	rx_state	; normal data state

	movf	rx_copycount,w
	addwf	rx_copycount,w  ; x 2
	addwf	rx_copycount,w	; x 3
	addwf	PCL,f

	movf	rx_char,w		; save 'our' data
	movwf	slot_id_ckt0
	goto	decode_continue
	movf	rx_char,w
	movwf	slot_id_ckt1
	goto	decode_continue
	movf	rx_char,w
	movwf	slot_id_ckt2
	goto	decode_continue
	movf	rx_char,w
	movwf	slot_id_ckt3
	goto	decode_continue
	movf	rx_char,w
	movwf	slot_id_ckt4
	goto	decode_continue
	movf	rx_char,w
	movwf	slot_id_ckt5
	goto	decode_continue
	movf	rx_char,w
	movwf	slot_id_ckt6
	goto	decode_continue
	movf	rx_char,w
	movwf	slot_id_ckt7
decode_continue:
 IF PWM_build
	addlw	1		; check for 255
	movf	disable_map,w	; update the mask of full-brightness channels
	iorwf	cur_mask,w
    IFDEF OUTPUT_NEGATIVE_TRUE
	btfsc	STATUS,C
	xorwf	cur_mask,w	; clr bit in cur_mask if channel brightness = 0
    ELSE
	btfss	STATUS,C	
	xorwf	cur_mask,w	; set bit in cur_mask if channel brightness = 0
    ENDIF
	movwf	disable_map

	addlw	0		; clear the carry
	rlf	cur_mask,f	; update mask for current channel
 ENDIF

	incf	rx_copycount,f	; count of bytes copied into memory
	btfsc	rx_copycount,3
	bsf	rx_state,2	; if yes, start the 'echo' rx_state
	goto	mainloop	; no echo !

 IF (!(PWM_build && DC_build))
zc_filter:
	addlw	0		; clear carry
	rlf	zc_delayline,f	;
	btfss	ZC
	return

	bsf	zc_delayline,0

	comf	zc_delayline,w
	btfsc	STATUS,Z
	goto	zc_filter_restart_timer	; jump if zc detected 8 times in row

	xorlw	0xF0

	btfss	STATUS,Z
	return
	
;; rising edge of zero-cross - 4 samples '0' followed by 4 samples '1'.
	movlw	MIN_TMR1H
	subwf	TMR1H,w		; TMR1H - MIN_TMR1H - no borrow if OK.
	comf	TMR1H,w

	btfsc	STATUS,Z
	goto	zc_filter_restart_timer
	btfss	STATUS,C	;
	goto	zc_filter_restart_timer

	addlw	3		; compensate for difference between 2's compl
				; and 1's complement inversion and for two lost
				; clocks when writing to TMR0
	
	movwf	timer0_preset
zc_filter_restart_timer:
	clrf	TMR1L
	clrf	TMR1H
	return
  ENDIF	
  END
