;
; lcd_video7.asm:	09/12/08	Jeff Keyzer		http://mightyohm.com
;
;			Generates an RGB Video Signal (noninterlaced) using PIC16F628 @ 20MHz
;			CSYNC->RA0, RED->RB0, GREEN->RB1, BLUE->RB2
;			External resistors needed to drop RGB voltages to 0.7Vpp
;			horizontal sync is 4us for every 64us line
;			vertical sync is 1 full line out of every 262 lines (once per field)
;			CSYNC is composite sync, carries both sync signals (inverse TTL logic)
;
;
;			v1 - display one row (12 pixels)  broken implementation of image table
;			v2 - image table fixed, code needs to be optimized
;			v3 - added multiple rows of pixels
;			v4 - added 2nd image and routine to alternate between them every FIELDSPERIMG fields
;			v5 - added more images and routine to rotate between them
;			v6 - changed image selection routine to alternate between two images every FIELDSPERIMG
;					fields, and change images every IMGREPEAT cycles
;			v7 - cleaned up code, added more comments and better variable names
;
;			Note: PC clock is xtal/4 so for 20MHz clock that gives 5 instr/us (or 0.2us/instr)
;

;	Includes
	LIST R=DEC                                                                                                                          
	ifdef __16F628
	INCLUDE "p16f628.inc"
	endif

;  Special Function Registers
	ifdef __16F628
	__CONFIG _CP_OFF & _DATA_CP_OFF & _WDT_OFF & _HS_OSC & _PWRTE_ON & _LVP_OFF & _MCLRE_ON & _BODEN_ON
	endif

; Variables
	CBLOCK 0x070	; start of general purpose registers (16 bytes)
					; 0x070 is special because it can be accessed from any bank
i, linecount, blankcnt, Dlay, pxline_cnt, imageoffset, tableindex:2, array_offset, fieldctr, imagecnt, alienstate, flag
	ENDC

Image	EQU	0x020	; Here is where we store the currently displayed image
					; 80 bytes of data memory in bank 0

; Macros
wait1us		MACRO	; wait 1us (5 instructions at 20MHz)
	goto	$+1		; 0.4us (2 instr)
	goto	$+1		; 0.4us	(2 instr)
	nop				; 0.2us	(1 instr)
			ENDM

dnop		MACRO	; equivalent of two nop's
	goto	$+1		; only one instruction, does nothing, takes 2 instr cycles
			ENDM
			
blankline	MACRO	; Blank line, has HSYNC but no video data
	bcf		PORTA, CSYNC		; Start of sync pulse
	dnop
	dnop						; delay=1us
	wait1us						; 2us
	wait1us						; 3us
	wait1us						; 4us
	bsf		PORTA, CSYNC		; sync pulse end, now wait 60us (295 instr)
	movlw	0x3B				; load delay into w
	call	Delay				; 
			ENDM
			
; Definess
#define		CSYNC		0			; PORTA bit 0
#define		RED			0			; PORTB bit 0
#define		GREEN		1			; PORTB	bit 1
#define		BLUE		2			; PORTB bit 2

#define		VISLINES	0xF3-3		; Visible lines (243 - 3) throw away 3 lines so 240/8 is an integer
#define		BLANKLINES	0x11		; Blanking interval (17 lines)
#define		PIXELS		0x0C		; Pixels per line	(8)
#define		PXLINES		0x1E		; Lines per pixel	(30)

#define		FIELDSPERIMG	0x1F	; Fields per image, controls speed of animation
#define		IMGREPEAT		0x09	; Number of times to repeat an image before moving to the next one
#define		STATES			0x03	; Number of states for the alien selection state machine

; Set program origin at base of program memory
	org		0x00

Init	; Set up hardware and initialize variables, etc.
	clrf	PORTA					; Clear PORTA (set all outputs low)
	clrf	PORTB					; Clear PORTB
	movlw	0x07					; CM[2:0] = 111 (disabled)
	movwf	CMCON					; Disable comparator so we can use RA0 as an output
	
	; While in bank1, (REGISTER^0x80) gets rid of annoying warning message about banks in MPLAB
	bsf		STATUS, RP0				; Change to bank 1
	bcf		(OPTION_REG^0x80), 7	; Enable PORTB pullups
	bcf		(TRISA^0x80), CSYNC		; Set CSYNC to output
	movlw	0xF8					; Set RB0-RB2 to output (RGB)
	movwf	(TRISB^0x80)
	bcf		STATUS, RP0				; Back to bank 0

; Initialize variables
	movlw	VISLINES
	movwf	linecount				; start linecounter at top of visible image

	movlw	PIXELS/2				; Pixels are read two at a time (one nibble per pixel)
	movwf	i						; Counter for reading image table into memory
	
	movlw	PXLINES					; # of lines each pixel lasts for
	movwf	pxline_cnt				; counter to keep track of those lines
	
	clrf	imageoffset				; imageoffset allows us to select which image to load into memory
									; and is relative to the base address of the first image lookup table
									; Only 8 bits - limits how far we can jump in the LUT
									
	movlw	FIELDSPERIMG
	movwf	fieldctr				; Set field counter, determines interval between images 
	
	movlw	IMGREPEAT
	movwf	imagecnt				; Set image counter, how many frames should each alien be displayed
	
	clrf	flag					; Flag to display frame 1 or 2 of each alien animation
	clrf	alienstate				; Number of alien to display, 0-2

; Begin main program loop
; Starting here, every instruction matters if we expect to maintain video timings across field
vsync	; Vertical Sync - Hold CSYNC low for a full line (64us)
	bcf		PORTA, CSYNC
	movlw	0x3D					; 62us delay + 2us at end for instructions = 64us
	call	Delay					; Delay takes value in 'w' and delays 5*(w-1)-10 instructions
	
	; Get ready to load image to display into memory
	movlw	Image					; load address of image array
	movwf	FSR						; set pointer to image array

	movf	imageoffset, w			; Preload table offset for desired image 
	movwf	tableindex
	clrf	tableindex + 1			; At present imageoffset is only 1 byte wide
	
	clrf	array_offset			; offset into image array in data memory

	movlw	BLANKLINES				; initialize blanking interval counter
	movwf	blankcnt
	dnop
	
blankint	; Blanking interval (17 lines), has HSYNC but no video data
	bcf		PORTA, CSYNC			; Start of sync pulse
	dnop
	dnop							; delay=1us
	wait1us							; 2us
	wait1us							; 3us
	wait1us							; 4us
	bsf		PORTA, CSYNC			; end of HSYNC
	
	movlw	0x1B					; any unused instruction cycles get taken care of here
	call	Delay
	dnop
	dnop
	nop
tableread	; Reads a line from ImageTable, stores into memory starting at addr 'Image'
	movlw	PIXELS/2				; we read 2 pixels at a time from the table
	movwf	i						; counter for table read
tableloop
	call	ImageTable				; table lookup routine
	movwf	INDF					; store result in data memory so we can access it quickly later
	incf	FSR, f					; move to next location in data memory
	incf	tableindex, f			; move to next location in table
	btfsc	STATUS, Z				; if tableindex=0 after incf we need to increment the high byte
		incf	tableindex + 1, f
	xorlw	0xFF					; xor=0 only if table returned 0xFF (signals end of image)
	btfsc	STATUS, Z
		goto 	table_exit			; we're done, stop (scroll way down to see the code for this)
	nop
	decfsz	i, f					; when i=0 we have read a full line of pixels
		goto	tableloop
	nop
	
blanking_reentry	; if table read 0xFF we jump away to kill time and come back here
	decfsz	blankcnt, f	
		goto	blankint			; write another blank line
	clrf	PCLATH					; 1 spare instruction on loop exit

hsync_loop
	bcf		PORTA, CSYNC			; Start of sync pulse
	dnop
	dnop							; delay=1us
	wait1us							; 2us
	wait1us							; 3us
	wait1us							; 4us
	bsf		PORTA, CSYNC			; sync pulse end, we have 8us to do stuff before visible part of line
	movlw	0x05					; chew up any leftover instruction cycles here
	call	Delay

	movlw	PIXELS/2				; each byte contains two pixels
	movwf	i						; counter for pixel pairs on each line
	dnop
	dnop
	
	movlw	Image					; starting address of image data
	addwf	array_offset, w			; offset into array for desired line
	movwf	FSR						; set pointer to image	
	swapf	INDF, w					; swap nibbles so we display high nibble first

visible
	movwf	PORTB					; BEGIN VISIBLE LINE
	movf	INDF, w					; move unswapped byte to w so we can show other pixel
	incf	FSR, f					; get ready for next pair of pixels
	dnop
	wait1us							; if we wanted higher resolution we'd get rid of these waits
	wait1us							; but then we'd run out of data memory pretty fast
	wait1us
	movwf	PORTB					; display other pixel in the pair (low nibble)
	wait1us
	wait1us
	wait1us
	swapf	INDF, w					; get ready for first pixel on next line
	decfsz	i, f
		goto	visible
	wait1us
	dnop
	
	; The next 16 lines have to do with the fact that we need a pixel to last more than 1 line in the picture
	; there is probably a better way to do this with fewer instructions.
	movlw	PIXELS/2				; preload array offset
	decfsz	pxline_cnt, f			; if counter=0 it's time to increment array offset for next line
		goto $+2              	
	addwf	array_offset, f			
	
	clrf	PORTB					; END VISIBLE LINE
									; We have 2us from here to beginning of scanline	
	dnop
	movlw	PXLINES					; if the pxline counter hits zero we need to reset it
	movf	pxline_cnt, f			; wish I could find a way to do this in less instructions
	btfsc	STATUS, Z
		movwf	pxline_cnt			; if counter=0, reset it
	
	decfsz	linecount, f			; check if we have more visible lines to display
		goto	hsync_loop			; we're not done with the field, go back and do another line
	nop

	blankline	; 243/8 has remainder 3, so we need 3 blank lines here + final blank before VSYNC
	blankline
	blankline
	
; Blank line, has HSYNC but no video data, a little short to allow time for loop return
	bcf		PORTA, CSYNC			; Start of sync pulse
	dnop
	dnop							; delay=1us
	wait1us							; 2us
	wait1us							; 3us
	wait1us							; 4us
	bsf		PORTA, CSYNC			; HSYNC pulse end 
	movlw	0x34					; load delay into w
	call	Delay	
	movlw	VISLINES
	movwf	linecount
	nop
	
	decfsz	fieldctr, f	; test is fieldctr=0, if so it's time to change images
		goto $+2              	
	goto chooseimage

	wait1us							; We didn't choose a new image so we have a bunch of leftover instructions to kill
	wait1us
	wait1us
	wait1us
	wait1us
	dnop
	
	goto 	vsync					; END OF FIELD, time to vsync and start over again
	
; END OF MAIN PROGRAM

; Subroutines
chooseimage	; State machine and some counters determine what should be displayed in a given field
			; This code only gets executed every FIELDSPERIMG fields (slowly compared to the main program loop)
			
	decfsz	imagecnt, f				; We change the displayed alien when imagecnt hits zero
		goto	$+2
	incf	alienstate, f
	
	movlw	STATES					; If present state is higher than number of valid states then reset it
	xorwf	alienstate, w			; if this returns zero it's time for a reset
	btfsc	STATUS, Z
		clrf	alienstate
	
	movlw	IMGREPEAT				; The image counter keeps track of how long a particular alien should be displayed
	movf	imagecnt, f
	btfsc	STATUS, Z				; if it's zero, reset it
		movwf	imagecnt
	
;	movlw	FIELDSPERIMG
;	movf	fieldctr, f
;	btfsc	STATUS, Z
;		movwf	fieldctr			; if fieldctr=0, reset it

	movlw	FIELDSPERIMG			; we got here, so it must be time to reset the field counter 
	movwf	fieldctr
	dnop
	nop
	
	movf	alienstate, w			; alien selection state machine
	addwf	PCL, f
	goto	Alien1
	goto	Alien2
	goto	Alien3
	
Alien1
	movlw	Image1-Image1			; compute table offset for 1st image
	movwf	imageoffset				; preload imageoffset with first image
	movlw	Image2-Image1			; computer table offset for 2nd image
	btfsc	flag, 0					; if we showed image 1 last time
		movwf	imageoffset			; load image 2 offset instead
	comf	flag, f					; invert the flag so next time we show the opposite image
	
	goto vsync						; we're done with the field, start the next field with a new image
	
Alien2
	movlw	Image3-Image1			; see Alien1 for comments
	movwf	imageoffset
	movlw	Image4-Image1
	btfsc	flag, 0
		movwf	imageoffset	
	comf	flag, f
	
	goto vsync

Alien3
	movlw	Image5-Image1			; see Alien1 for comments
	movwf	imageoffset
	movlw	Image6-Image1
	btfsc	flag, 0
		movwf	imageoffset	
	comf	flag, f
	
	goto vsync

table_exit	; exit routine for table if 0xFF was read (signals table end)
	decf	FSR, f					; this keeps us from overwriting extra memory
	decf	tableindex, f
	
	movlw	0x18					; kill some cycles so each blank line always takes the same time
	call	Delay
	goto	blanking_reentry		; go back to finish blanking interval

Delay	; Delays 5*(w-1)+10 cycles including prev inst, function call, and movlw in main loop
	movwf	Dlay	; (1)
	nop
Back
	decfsz 	Dlay, f	; (1 or 2) normal loop takes 5 clocks to get back here
		goto Bounce	; (2)
	return			; (2) END OF FUNCTION
Bounce
	goto Back		; (2) we kill 4 clocks by jumping and returning
	

; IMAGE DATA
; Store image to display at base of 2nd block of program memory 
	org	0x0400
	
ImageTable	;Image stored as two pixels per byte, in reverse order (low nibble of each byte comes first)
	movlw	HIGH Image1
	addwf	tableindex + 1, w
	movwf	PCLATH
	movlw	Image1 & 0x0FF
	addwf	tableindex, w
	btfsc	STATUS, C
		incf	PCLATH, f
	movwf	PCL 	; this modifies PCL so takes 2 instructions!!

Image1
; Begin Alien1 Closed Image
	retlw	0x00
	retlw	0x00
	retlw	0x11
	retlw	0x11
	retlw	0x00
	retlw	0x00
	
	retlw	0x01
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x10
	
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	
	retlw	0x11
	retlw	0x10
	retlw	0x01
	retlw	0x10
	retlw	0x01
	retlw	0x11
	
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	
	retlw	0x00
	retlw	0x11
	retlw	0x10
	retlw	0x01
	retlw	0x11
	retlw	0x00
	
	retlw	0x01
	retlw	0x10
	retlw	0x01
	retlw	0x10
	retlw	0x01
	retlw	0x10
	
	retlw	0x00
	retlw	0x11
	retlw	0x00
	retlw	0x00
	retlw	0x11
	retlw	0x00
	retlw	0xFF	; end of image
Image2
; Begin Alien1 Open Image
	retlw	0x00
	retlw	0x00
	retlw	0x11
	retlw	0x11
	retlw	0x00
	retlw	0x00
	
	retlw	0x01
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x10
	
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11

	retlw	0x11
	retlw	0x10
	retlw	0x01
	retlw	0x10
	retlw	0x01
	retlw	0x11

	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11
	retlw	0x11

	retlw	0x00
	retlw	0x01
	retlw	0x10
	retlw	0x01
	retlw	0x10
	retlw	0x00

	retlw	0x00
	retlw	0x11
	retlw	0x01
	retlw	0x10
	retlw	0x11
	retlw	0x00

	retlw	0x11
	retlw	0x00
	retlw	0x00
	retlw	0x00
	retlw	0x00
	retlw	0x11
	retlw	0xFF	; Signals end of image
Image3	
; Begin Alien2 Closed Image
	retlw	0x00
	retlw	0x20
	retlw	0x00
	retlw	0x00
	retlw	0x20
	retlw	0x00
	
	retlw	0x00
	retlw	0x02
	retlw	0x00
	retlw	0x02
	retlw	0x00
	retlw	0x00
	
	retlw	0x00
	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x20
	retlw	0x00

	retlw	0x02
	retlw	0x20
	retlw	0x22
	retlw	0x20
	retlw	0x22
	retlw	0x00

	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x20

	retlw	0x20
	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x20
	retlw	0x20

	retlw	0x20
	retlw	0x20
	retlw	0x00
	retlw	0x00
	retlw	0x20
	retlw	0x20

	retlw	0x00
	retlw	0x02
	retlw	0x20
	retlw	0x22
	retlw	0x00
	retlw	0x00
	retlw	0xFF	; Signals end of image
Image4
; Begin Alien2 Open Image
	retlw	0x00
	retlw	0x20
	retlw	0x00
	retlw	0x00
	retlw	0x20
	retlw	0x00
	
	retlw	0x20
	retlw	0x02
	retlw	0x00
	retlw	0x02
	retlw	0x00
	retlw	0x20
	
	retlw	0x20
	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x20
	retlw	0x20

	retlw	0x22
	retlw	0x20
	retlw	0x22
	retlw	0x20
	retlw	0x22
	retlw	0x20

	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x20

	retlw	0x02
	retlw	0x22
	retlw	0x22
	retlw	0x22
	retlw	0x20
	retlw	0x00

	retlw	0x00
	retlw	0x20
	retlw	0x00
	retlw	0x00
	retlw	0x20
	retlw	0x00

	retlw	0x02
	retlw	0x00
	retlw	0x00
	retlw	0x00
	retlw	0x02
	retlw	0x00
	retlw	0xFF	; Signals end of image
Image5
; Begin Alien3 Closed Image
	retlw	0x00
	retlw	0x00
	retlw	0x04
	retlw	0x40
	retlw	0x00
	retlw	0x00
	
	retlw	0x00
	retlw	0x00
	retlw	0x44
	retlw	0x44
	retlw	0x00
	retlw	0x00
	
	retlw	0x00
	retlw	0x04
	retlw	0x44
	retlw	0x44
	retlw	0x40
	retlw	0x00

	retlw	0x00
	retlw	0x44
	retlw	0x04
	retlw	0x40
	retlw	0x44
	retlw	0x00

	retlw	0x00
	retlw	0x44
	retlw	0x44
	retlw	0x44
	retlw	0x44
	retlw	0x00

	retlw	0x00
	retlw	0x00
	retlw	0x40
	retlw	0x04
	retlw	0x00
	retlw	0x00

	retlw	0x00
	retlw	0x04
	retlw	0x04
	retlw	0x40
	retlw	0x40
	retlw	0x00

	retlw	0x00
	retlw	0x40
	retlw	0x40
	retlw	0x04
	retlw	0x04
	retlw	0x00
	retlw	0xFF	; Signals end of image
Image6
; Begin Alien3 Open Image
	retlw	0x00
	retlw	0x00
	retlw	0x04
	retlw	0x40
	retlw	0x00
	retlw	0x00
	
	retlw	0x00
	retlw	0x00
	retlw	0x44
	retlw	0x44
	retlw	0x00
	retlw	0x00
	
	retlw	0x00
	retlw	0x04
	retlw	0x44
	retlw	0x44
	retlw	0x40
	retlw	0x00

	retlw	0x00
	retlw	0x44
	retlw	0x04
	retlw	0x40
	retlw	0x44
	retlw	0x00

	retlw	0x00
	retlw	0x44
	retlw	0x44
	retlw	0x44
	retlw	0x44
	retlw	0x00

	retlw	0x00
	retlw	0x04
	retlw	0x04
	retlw	0x40
	retlw	0x40
	retlw	0x00

	retlw	0x00
	retlw	0x40
	retlw	0x00
	retlw	0x00
	retlw	0x04
	retlw	0x00

	retlw	0x00
	retlw	0x04
	retlw	0x00
	retlw	0x00
	retlw	0x40
	retlw	0x00
	retlw	0xFF	; Signals end of image
	
	end

