; SERVID - Serial video display using Ubicom SX microcontroller
; $Id: servid.asm,v 1.34 2001/01/31 07:25:31 eric Exp $
;
; Copyright 2000, 2001 Eric Smith <eric@brouhaha.com>
;
; Home page:
;    http://www.brouhaha.com/ubicom/servid/
;
; This program is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License version 2 as published
; by the Free Software Foundation.  Note that permission is not granted
; to redistribute this program under the terms of any other version of the
; General Public License.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program; if not, write to the Free Software
; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;
; NOTE:  it is sometimes claimed that compliance with the GPL is
; awkward for commercial interests.  Licenses for non-GPL use of this
; program may be negotiated with the author.
;
; This program is written to be assembled with the GPASM assembler,
; version 0.8.14 or newer:
;     http://gpasm.sourceforge.net/


; NOTE: there are references in this code to PAL and NTSC.  Technically
; those are color standards.  In most cases the references to PAL and NTSC
; are really intended to refer to 625/50 and 525/59.94 scanning, or (in
; non-interlaced mode) 312/25 and 262/29.97 scanning.


;---------------------------------------------------------------------------
; feature test switches
;---------------------------------------------------------------------------

ft_sx28		equ	0	; 0 for SX18/SX20, 1 for SX28

ft_pal_video	equ	0	; 0 for NTSC 525/60, 1 for PAL 625/50
				; (approximate timing only)
				; (not well tested)

ft_interlace	equ	1	; 1 for interlaced video

ft_color	equ	0	; 1 for color burst (NTSC only)

ft_serial_input	equ	1	; 1 for normal serial input,
				; 0 to omit (when replaced with user
				;     application code)

ft_ser_noninv	equ	1	; 0 for "normal" TTL-level serial,
				;     mark = low, space = high
				; 1 for non-inverted serial (the
				; crude resistor-only method)
				;     mark = high, space = low

ft_splash	equ	1	; 1 for splash screen


;---------------------------------------------------------------------------
; processor definitions and assembler settings
;---------------------------------------------------------------------------

	if	ft_sx28
	processor SX28
	else
	processor SX18
	endif

	radix	dec

	errorlevel -305		; don't want default destination warning
	errorlevel -224		; don't want deprecated instruction warnings,
				; since on an SX or PIC16C5X there's no other
				; way to set the TRIS or OPTION registers

	include	"sxdefs.inc"

device	equ	pins28+pages4+banks8+oschs+optionx+stackx+bor40+turbo


; for instruction destination argument
f	equ     1
w	equ     0

int_off	equ	0xc3		; RTCC internal clock, prescale by 16,
				; RTCC interrupt off, WDT disabled

int_on	equ	0x83		; RTCC internal clock, prescale by 16,
				; RTCC interrupt on, WDT disabled



;---------------------------------------------------------------------------
; other includes
;---------------------------------------------------------------------------

	include "ascii.inc"


;---------------------------------------------------------------------------
; video definitions
;---------------------------------------------------------------------------

; Display size in characters.  Note that simply changing these definitions
; won't have the desired effect.

rows	equ	4
columns	equ	20


; osc = 42.9545 MHz = 12 * color burst
; tCYC = 23.2804 ns
; theoretical total width = 12 * 227.5 = 2730 cycles = 2 * 3 * 5 * 91 = 15 * 182
;
; The RTCC prescaler can only be set for powers of two, and we need the
; count to be a little under 256, so we use a prescaler of 16 and a divisor
; of 171, for an actual scan line width of 2736 cycles (63.7 us).

h				equ	2736	; 63.7 us
hsync_pulse_width		equ	201	; 4.7 us
equalization_pulse_width	equ	98	; 2.3 us
serration_pulse_width		equ	201	; 4.7 us
vsync_pulse_width		equ	(h/2)-serration_pulse_width
front_porch_width		equ	64	; 1.5 us
back_porch_width		equ	193	; 4.5 us

; safe area = 40 us = 1718 cycles

; 20 chars wide * (5+2) = 139 pixels wide, 12.4 cycles per pixel
; ("rounded" up to 13)
;
; for 4:3 aspect ratio, display should be 90 pixels tall, so make
; a pixel be 3 scan lines.

scan_lines_per_vpixel	equ	3
vpixels_per_char	equ	10
chars_per_row		equ	20


	if	ft_pal_video
total_active_lines	equ	287
	else
total_active_lines	equ	242
	endif

active_video_lines	equ	rows*vpixels_per_char*scan_lines_per_vpixel
top_border	equ	(total_active_lines-active_video_lines)/2
bottom_border	equ	total_active_lines-(top_border+active_video_lines)


;---------------------------------------------------------------------------
; I/O port definitions
;---------------------------------------------------------------------------

rxd_bit		equ	0
pzt_bit		equ	1
txd_bit		equ	2	; not used
mode_button_bit	equ	3	; not used

#define rxd porta,rxd_bit
#define pzt porta,pzt_bit
#define txd porta,txd_bit
#define mode_button porta,mod_button_bit

trisa	equ	0x01		; RxD is our only input
inita	equ	0x00

trisb	equ	0x00		; all outputs
initb	equ	vid_sync

	if	ft_sx28
trisc	equ	0x00		;  all outputs
initc	equ	0x00		;  drive low
	endif


;---------------------------------------------------------------------------
; composite video definitions
;---------------------------------------------------------------------------

vid_port   equ     portb	; D/A converter

; DAC 0 = ground (sync tip), 255 = 1.25V into 75 ohm load
; one DAC step = 1.25/255 V = 4.902 mV  
; there are 140 IRE units to 1.0V, so an IRE unit is 7.143 mV = 1.457 DAC steps

vid_sync	equ	0	; -40 IRE
vid_blank	equ	58	; 58.29 = 0 IRE
vid_black	equ	69	; 69.21 = 7.5 IRE
vid_white	equ	204	; 204.00 = 100 IRE
vid_max_chroma	equ	249	; 249.17 = 131 IRE

burst_amplitude	equ	58	; 58.29 = 40 IREs


;---------------------------------------------------------------------------
; bell definitions
;---------------------------------------------------------------------------

; The bell tone is nominally around 500 Hz for 200 ms (100 cycles).
; This works out to a period of 32 scan lines.

bell_half_period	equ	16	; lines
bell_duration		equ	200	; half-periods	


;---------------------------------------------------------------------------
; serial definitions
;---------------------------------------------------------------------------

; While serial line idle, sample every scan line.  Once start bit is
; detected, delay 6 scan lines, then sample every 13.  This results
; in a 1208 bps rate, 0.6% fast.

lines_per_serial_sample	equ	13


skip_on_ser_rx_mark	macro
	if	ft_ser_noninv
	btfss	rxd
	else
	btfsc	rxd
	endif
	endm

skip_on_ser_rx_space	macro
	if	ft_ser_noninv
	btfsc	rxd
	else
	btfss	rxd
	endif
	endm


;---------------------------------------------------------------------------
; memory utilization
;---------------------------------------------------------------------------
	 
rambase equ     08h             ; start of RAM

rombase equ     0000h           ; beginning of program
romsize	equ	0800h
chargen	equ	romsize-384
resetvec equ	romsize-1	; reset vector
intvec	equ	0000h		; interrupt vector

main_page	equ	0000h
int_page	equ	0200h


;---------------------------------------------------------------------------
; shared variables
;---------------------------------------------------------------------------
	 
	org     rambase         ; start of RAM

g_field_count:	res	1	; field down-counter

g_mtemp:	res	1	; global temp for main

; "DelM" uses "DelMCnt" in the interupt. Do NOT use DelM or DelMCnt in main!
DelMCnt:	res	1	; counter used for cycle delays
Five:		res	1
Fifteen		res	1


;---------------------------------------------------------------------------
; variables for main
;---------------------------------------------------------------------------
	 
	org	010h		

main_vars:

temp:		res	3

char:		res	1	; character being processed
escape_state:	res	1	; 0 = normal
				; 1 = ESC seen, waiting for 2nd char
				; 2 = ESC-Y seen, waiting for <col>
				; 3 = ESC-Y <col> seen, waiting for <row>
esc_Y_col:	res	1

; cursor
cursor_col:	res	1
cursor_row:	res	1
cursor_loc:	res	1

; for scrolling
src_addr	equ	temp
dest_addr	equ	temp+1
move_count	equ	temp+2


;---------------------------------------------------------------------------
; variables for interrupt
;---------------------------------------------------------------------------
	
	org	030h

int_vars:

line_type:	res	1	; type of scan line we're working on
				;    2 * [0 .. line_types-1]
line_count:	res	1	; how many lines of this type to do

		if	ft_color
burst_phase:	res	1	; LSB used for burst phase
int_temp:	res	1	; general use in interrupt
		endif


line_start:	res	1	; start buffer loc of currently displayed line
char_ptr:	res	1	; pointer to currently displayed character
chargen_ptr:	res	2	; pointer into character generator
inverse_flag:	res	1	; bit 7 indicates current char inverse
vpix_cnt:	res	1	; vertical pixel counter
scanline_cnt:	res	1	; vertical scan line counter (per pixel) 
char_cnt:	res	1
pixels:		res	1	; pixels of current char

; bell
bell_half_cyc:	res	1	; bell half-cycle in lines
bell_line_cnt:	res	1	; bell half-cycle down-counter
bell_dur_cnt:	res	1	; bell duration

;---------------------------------------------------------------------------
; variables for serial receive
;---------------------------------------------------------------------------

	if	ft_serial_input

	org	050h

ser_vars:

ser_rx_state:	res	1
ser_rx_byte:	res	1
ser_rx_samp_cnt:	res	1
ser_rx_bit_cnt:	res	1

ser_rx_char:	res	1
ser_rx_flag:	res	1

	endif


;---------------------------------------------------------------------------
; video buffer
;---------------------------------------------------------------------------

; NOTE: subtract offset of 20h (space) before storing characters into
; video buffer
	
video_buffer	equ	070h	; 80 characters, uses last five banks
				; *must* start on a bank boundary

; reserve RAM, skipping over banks as needed
res_bank_ram	macro	count
	local	c
c	set	count
	while	c>0
	res	1
	if	($ & 010h)==0
	org	$+010h
	endif
c	set	c-1
	endw
	endm

	org	video_buffer
line_0:		res_bank_ram	columns
line_1:		res_bank_ram	columns
line_2:		res_bank_ram	columns-1
line_2_end:	res_bank_ram	1
line_3:		res_bank_ram	columns-1
line_3_end:	res_bank_ram	1

	org	rombase

	page	interrupt	; 0
	goto	interrupt	; 1


escape_state_table:
	movf	escape_state,w
	addwf	pcl
	goto	esc_not_seen
	goto	esc_seen
	goto	esc_Y_col_seen
	goto	esc_Y_row_seen


control_char_table:
	movf	char,w
	addwf	pcl
	goto	null		; 00 - NUL - null - don't do anything
	goto	null		; 01 -
	goto	null		; 02 -
	goto	null		; 03 -
	goto	null		; 04 -
	goto	null		; 05 -
	goto	null		; 06 -
	goto	bell		; 07 - BEL - bell
	goto	backspace	; 08 - BS - backspace
	goto	null		; 09 -
	goto	line_feed	; 0a - LF - line feed
	goto	null		; 0b -
	goto	form_feed	; 0c - FF - form feed - clear screen
	goto	carriage_return	; 0d - CR - carriage return
	goto	null		; 0e
	goto	null		; 0f
	goto	null		; 10
	goto	null		; 11
	goto	null		; 12
	goto	null		; 13
	goto	null		; 14
	goto	null		; 15
	goto	null		; 16
	goto	null		; 17
	goto	null		; 18
	goto	null		; 19
	goto	null		; 1a
	goto	escape		; 1b - ESC - escape
	goto	null		; 1c
	goto	null		; 1d
	goto	null		; 1e
	goto	null		; 1f

esc_char_table:
	addwf	pcl
	goto	bad_escape	; 40 - @
	goto	cursor_up	; 41 - A - cursor up
	goto	cursor_down	; 42 - B - cursor down
	goto	cursor_left	; 43 - C - cursor left
	goto	cursor_right	; 44 - D - cursor right
	goto	bad_escape	; 45 - E
	goto	bad_escape	; 46 - F
	goto	bad_escape	; 47 - G
	goto	home_cursor	; 48 - H - cursor home
	goto	rev_line_feed	; 49 - I - reverse line feed (can scroll)
	goto	clear_eop	; 4A - J - clear to end of screen
	goto	clear_eol	; 4B - K - clear to end of line
	goto	bad_escape	; 4C - L
	goto	bad_escape	; 4D - M
	goto	bad_escape	; 4E - N
	goto	bad_escape	; 4F - O
	goto	bad_escape	; 50 - P
	goto	bad_escape	; 51 - Q
	goto	bad_escape	; 52 - R
	goto	bad_escape	; 53 - S
	goto	bad_escape	; 54 - T
	goto	bad_escape	; 55 - U
	goto	bad_escape	; 56 - V
	goto	bad_escape	; 57 - W
	goto	bad_escape	; 58 - X
	goto	esc_Y		; 59 - Y - cursor positioning
	goto	bad_escape	; 5A - Z
	goto	bad_escape	; 5B - [
	goto	bad_escape	; 5C - \
	goto	bad_escape	; 5D - ]
	goto	bad_escape	; 5E - ^
	goto	bad_escape	; 5F - _


show_cursor:
	movf	cursor_loc,w
	movwf	fsr
	bsf	indf,7
	bank	main_vars
	return


hide_cursor:
	movf	cursor_loc,w
	movwf	fsr
	bcf	indf,7
	bank	main_vars
	return


; delay until either the number of fields specified in W have been
; displayed (zero flag set), or a serial character is received
; (zero flag clear)

delay_fields:
	bank	ser_vars
	movwf	g_field_count

df_loop:
	if	ft_serial_input

	movf	ser_rx_flag	; check serial receive flag
	btfss	status,zf	; character received?
	goto	df_return	; yes, return to caller

	endif

	movf	g_field_count	; has field count decremented to zero?
	btfss	status,zf
	goto	df_loop		; no, keep looping

df_return:
	bank	main_vars
	return


home_cursor:
	clrf	cursor_row

carriage_return:
	clrf	cursor_col

compute_cursor_loc:
	movf	cursor_row,w	; cursor_loc = 20 * cursor_row
	movwf	cursor_loc
	bcf	status,cf
	rlf	cursor_loc
	rlf	cursor_loc
	swapf	cursor_row,w
	addwf	cursor_loc

	movf	cursor_col,w	; cursor_loc += cursor_col
	addwf	cursor_loc

	movf	cursor_loc,w	; shift high nibble left one bit
	andlw	0f0h
	addwf	cursor_loc

	movlw	video_buffer	; add in base address
	addwf	cursor_loc

null:	return


; output a character from W to the display
output_char:
	andlw	07fh		; strip MSB (parity?) and save
	movwf	char
	goto	escape_state_table	; process character


esc_not_seen:
	movf	char,w
	andlw	060h		; is it a control character?
	btfsc	status,zf
	goto	control_char_table	; yes, process and return

	movf	char,w		; is it a DEL
	xorlw	asc_del
	btfsc	status,zf
	return			; yes, do nothing

	movf	char,w
; fall into printable_char

printable_char:
	movwf	g_mtemp		; save character

	movlw	-' '		; remove offset
	addwf	g_mtemp
	
	movf	cursor_loc,w	;  store character
	movwf	fsr
	movf	g_mtemp,w
	movwf	indf
	bank	main_vars
; fall into cursor_advance

cursor_advance:
	incf	cursor_col
	movf	cursor_col,w
	xorlw	columns
	btfss	status,zf
	goto	compute_cursor_loc

crlf:	clrf	cursor_col
line_feed:
	incf	cursor_row
	movf	cursor_row,w
	xorlw	rows
	btfss	status,zf
	goto	compute_cursor_loc
	decf	cursor_row	; restore
	call	compute_cursor_loc

scroll_up:
	movlw	line_1
	movwf	src_addr
	movlw	line_0
	movwf	dest_addr
	movlw	(rows-1)*columns
	movwf	move_count
	call	block_move_up

	movlw	line_3		; clear freed space
	movwf	temp+1
	movlw	columns
	movwf	temp
	goto	clear_chars


block_move_up:
	movf	src_addr,w
	movwf	fsr
	movf	indf,w
	movwf	g_mtemp
	bank	main_vars
	incf	src_addr
	bsf	src_addr,4

	movf	dest_addr,w
	movwf	fsr
	movf	g_mtemp,w
	movwf	indf
	bank	main_vars
	incf	dest_addr
	bsf	dest_addr,4

	decfsz	move_count
	goto	block_move_up

	return


backspace:
	decf	cursor_col
	btfss	cursor_col,7
	goto	compute_cursor_loc
	movlw	columns-1
	movwf	cursor_col

rev_line_feed:
	decf	cursor_row
	btfss	cursor_row,7
	goto	compute_cursor_loc
	incf	cursor_row	; restore
	call	compute_cursor_loc

scroll_down:
	movlw	line_2_end
	movwf	src_addr
	movlw	line_3_end
	movwf	dest_addr
	movlw	(rows-1)*columns
	movwf	move_count
	call	block_move_down

	movlw	line_0		; clear freed space
	movwf	temp+1
	movlw	columns
	movwf	temp
	goto	clear_chars

block_move_down:
	movf	src_addr,w
	movwf	fsr
	movf	indf,w
	movwf	g_mtemp
	bank	main_vars
	decf	src_addr
	btfsc	src_addr,4
	goto	bmd_1
	movlw	010h
	subwf	src_addr

bmd_1:	movf	dest_addr,w
	movwf	fsr
	movf	g_mtemp,w
	movwf	indf
	bank	main_vars
	decf	dest_addr
	btfsc	dest_addr,4
	goto	bmd_2
	movlw	010h
	subwf	dest_addr

bmd_2:	decfsz	move_count
	goto	block_move_down

	return


clear_eol:
	movlw	columns		; compute number of chars to clear:
	movwf	temp		; temp := columns - cursor_col
	movf	cursor_col,w
	subwf	temp

	movf	cursor_loc,w
	movwf	temp+1

; clear temp chars starting at loc temp+1
clear_chars:
	movlw	' '-020h
	movwf	g_mtemp

; fill temp chars starting at loc temp+1 to value temp+2
fill_chars:
	movf	temp+1,w
	movwf	fsr
	movf	g_mtemp,w
	movwf	indf
	bank	main_vars

	incf	temp+1
	bsf	temp+1,4
	decfsz	temp
	goto	fill_chars
	return


form_feed:
	call	home_cursor

clear_eop:
	call	clear_eol	; clear to end of current line
	movlw	rows-1		; compute additional rows to clear:
	movwf	temp		; temp := (rows - 1) - cursor_row
	movf	cursor_row,w
	subwf	temp
	btfsc	status,zf	; any rows to clear?
	return			; no

	bcf	status,cf	; multiply temp by 20 to get char count
	rlf	temp
	bcf	status,cf
	rlf	temp
	movf	temp,w
	bcf	status,cf
	rlf	temp
	bcf	status,cf
	rlf	temp
	addwf	temp

	goto	fill_chars


cursor_up:
	decf	cursor_row
	movlw	rows-1
	btfsc	cursor_row,7
	movwf	cursor_row
	goto	compute_cursor_loc

cursor_down:
	incf	cursor_row
	btfsc	cursor_row,2	; hard-coded for 4 rows
	clrf	cursor_row
	goto	compute_cursor_loc

cursor_left:
	decf	cursor_col
	movlw	columns-1
	btfsc	cursor_col,7
	movwf	cursor_col
	goto	compute_cursor_loc

cursor_right:
	incf	cursor_col
	movf	cursor_col,w
	xorlw	columns
	btfsc	status,zf
	clrf	cursor_row
	goto	compute_cursor_loc


esc_Y:
	movlw	2
	movwf	escape_state
bad_escape:
	return

esc_Y_col_seen:
	movlw	' '
	subwf	char,w
	movwf	esc_Y_col
	incf	escape_state
	return

escape:
	incf	escape_state
	return

esc_Y_row_seen:
	movlw	(256-' ')-rows	; range check the row (still has ' ' offset)
	addwf	char,w
	btfsc	status,cf
	goto	bad_row

	movlw	' '		; move cursor to specified column
	subwf	char,w
	movwf	cursor_row
bad_row

	movlw	256-columns	; range check the column
	addwf	esc_Y_col,w
	btfsc	status,cf
	goto	bad_col
	
	movf	esc_Y_col,w	; move cursor to specified column
	movwf	cursor_col
bad_col:

	clrf	escape_state
	goto	compute_cursor_loc

	
esc_seen:
	clrf	escape_state	; assume only two-char sequence
	movlw	'@'
	subwf	char,w
	movwf	temp
	andlw	060h		; check for range 40-5F
	btfss	status,zf
	goto	bad_escape

	movf	temp,w
	goto	esc_char_table

	
bell:	bank	int_vars	; start a bell
	movlw	bell_half_period
	movwf	bell_half_cyc
	movwf	bell_line_cnt
	movlw	bell_duration
	movwf	bell_dur_cnt
	bank	main_vars
	return


reset:	mode	0fh		; paranoia

	movlw	int_off
	option

	bank	main_vars

	movlw	inita
	movwf	porta
	movlw	trisa
	tris	porta

	movlw	initb
	movwf	portb
	movlw	trisb
	tris	portb

	if	ft_sx28
	movlw	initc
	movwf	portc
	movlw	trisc
	tris	portc
	endif

	clrf	escape_state
	call	form_feed

	bank	int_vars

	movlw	5		; set up for DelM macro
	movwf	Five
	movlw	15
	movwf	Fifteen

	clrf	line_type

	incf	line_type,w	; get initial line count
	page	line_dispatch
	call	line_dispatch
	page	$
	movwf	line_count

	if	ft_serial_input

	bank	ser_vars

	clrf	ser_rx_state
	movlw	1
	movwf	ser_rx_samp_cnt
	clrf	ser_rx_flag

	endif

	bank	main_vars

	movlw	int_on
	option

	if	ft_splash
	page	splash
	call	splash
	endif

;	call	home_cursor

main_loop:
	call	show_cursor
	movlw	30
	call	delay_fields

	if	ft_serial_input
	btfss	status,zf
	goto	got_char
	endif
	
	call	hide_cursor
	movlw	30
	call	delay_fields

	if	ft_serial_input
	btfss	status,zf
	goto	got_char
	endif

	goto	main_loop


	if	ft_serial_input
got_char:
	call	hide_cursor	; hide cursor during character processing

	bank	ser_vars	; get character and clear rx flag
	movf	ser_rx_char,w
	clrf	ser_rx_flag
	bank	main_vars

	call	output_char
	goto	main_loop
	endif


;---------------------------------------------------------------------------
; interrupt handler
;---------------------------------------------------------------------------
	 
	org	0200h

	include	"delm.inc"


scanln	macro	count,function
	goto	function
	retlw	count
	endm


; table of line type function pointers and counts
; when called for function, takes cycles 58-63

line_dispatch:
	addwf	pcl			; 58

	if	ft_pal_video

							; PAL lines
	scanln	2,equalization_line			; 624-625
	scanln	2,vsync_line				; 1-2
	scanln	1,vsync_eq_line				; 3
	scanln	2,equalization_line			; 4-5
	scanln	17,vblank_line				; 6-22
	scanln	1,vblank_black_line			; 23
	scanln	top_border,black_video_line		; 24-106
	scanln	active_video_lines,active_video_line	; 107-226
	scanln	bottom_border-1,black_video_line	; 227-309
	
	if	ft_interlace
	scanln	1,black_video_line			; 310
	scanln	2,equalization_line			; 311-312	
	scanln	1,eq_vsync_line				; 313
	scanln	2,vsync_line				; 314-315
	scanln	2,equalization_line			; 316-317	
	scanln	1,eq_vblank_line			; 318
	scanln	17,vblank_line				; 319-335
	scanln	top_border,black_video_line		; 336-418
	scanln	active_video_lines,active_video_line	; 419-538
	scanln	bottom_border,black_video_line		; 539-622
	endif

	scanln	1,black_eq_line				; 310 or 623

	else

;							; NTSC lines
	scanln	3,equalization_line			; 1-3
	scanln	3,vsync_line				; 4-6
	scanln	3,equalization_line			; 7-9
	scanln	11,vblank_line				; 10-20
	scanln	top_border,black_video_line		; 21-81
	scanln	active_video_lines,active_video_line	; 82-201
	scanln	bottom_border,black_video_line		; 202-262

	if	ft_interlace
	scanln	1,black_eq_line				; 263
	scanln	2,equalization_line			; 264-265
	scanln	1,eq_vsync_line				; 266
	scanln	2,vsync_line				; 267-268
	scanln	1,vsync_eq_line				; 269
	scanln	2,equalization_line			; 270-271
	scanln	1,eq_vblank_line			; 272
	scanln	10,vblank_line				; 273-282
	scanln	1,vblank_black_line			; 283
	scanln	top_border,black_video_line		; 284-344
	scanln	active_video_lines,active_video_line	; 345-464
	scanln	bottom_border,black_video_line		; 465-525
	endif

	endif
	

line_types	equ	(($-line_dispatch)-1)/2


	if	ft_serial_input
; serial input state machine dispatch
serial_state_table:
	movf	ser_rx_state,w	; 12
	addwf	pcl		; 13-15
	goto	ser_idle	; 16-18
	goto	ser_data_bit	; 16-18
	goto	ser_stop_bit	; 16-18
	endif
	

bell_48:
	bcf	pzt		; 48
	delm	1		; 49
bell_50:
	delm	1		; 50
	goto	bell_done	; 51-53


interrupt:
	movlw	vid_blank	; 4 start front porch
	movwf	vid_port	; 5

	if	ft_serial_input

	bank	ser_vars	; 6
	decfsz	ser_rx_samp_cnt	; 7
	goto	skip_serial	; 8-10

	call	serial_state_table	; 9-11
	goto	serial_done	; 39-41

skip_serial:
	delm	31		; 11-41
serial_done:

	else

	delm	36		; 6-41

	endif

	bank	int_vars	; 42

	movf	bell_dur_cnt	; 43 - bell active?
	btfsc	status,zf	; 44
	goto	bell_48		; 45-47 - no

	decfsz	bell_line_cnt	; 46 - time to toggle PZT?
	goto	bell_50		; 47-49 - no

	movlw	1<<pzt_bit	; 48 - toggle PZT
	xorwf	porta		; 49

	decf	bell_dur_cnt	; 50 - decrement duration

	movf	bell_half_cyc,w	; 51 - reinit line count
	movwf	bell_line_cnt	; 52

	delm	1		; 53
bell_done:

	movf	line_type,w	; 54
	call	line_dispatch	; 55-57

	decfsz	line_count	; any more lines of the current type?
	goto	interrupt_done	; no, done

	incf	line_type	; advance to next line type
	incf	line_type
	movf	line_type,w	; end of field?
	xorlw	line_types*2
	btfss	status,zf
	goto	get_line_count	; no

	clrf	line_type	; yes, start new field

	movf	g_field_count	; decrement field counter
	btfss	status,zf
	decf	g_field_count

get_line_count:
	incf	line_type,w	; new line type, how many lines?
	call	line_dispatch
	movwf	line_count

interrupt_done:	
	movlw	256-171		; all done with this scan line
	retiw


; at some point within the frame before the first active video line,
; call this subroutine to initialize the pointers
display_frame_setup:
	movlw	video_buffer
	movwf	line_start

	clrf	vpix_cnt

	movlw	scan_lines_per_vpixel
	movwf	scanline_cnt

	return


	if	ft_color

burst_l	equ	vid_blank-(burst_amplitude/2)
burst_h	equ	vid_blank+(burst_amplitude/2)
burst_x	equ	burst_l^burst_h

; burst starts at 228 cycles from horizontal reference point,
; which is 292 cycles from our start of back porch.

color_burst:
	delm	14		; 270-283

; $$$ actually, don't toggle burst phase, because our current
; line timing is an integral multiple of the color carrier
	movlw	0		; 284 - toggle burst phase
	xorwf	burst_phase	; 285

	movlw	18		; 286 - 18 half-cycles of burst
	movwf	int_temp	; 287

	movlw	burst_h		; 288 - assume leading edge high
	btfsc	burst_phase,0	; 289
	movlw	burst_l		; 290
	
burst_loop:
	xorlw	burst_x		; 291		399
	movwf	vid_port	; 292		400
	decfsz	int_temp	; 293		401
	goto	burst_loop	; 294-296	402

	delm	2		; 403
	movlw	vid_blank	; 405
	movwf	vid_port	; 406

	delm	48		; 407
	return			; 455-457

	else

color_burst:
	delm	185		; 270-454
	return			; 455-457

	endif


equalization_line:
	movlw	vid_sync	; 64 - start equalizing pulse
	movwf	vid_port
	delm	equalization_pulse_width-2
	
	movlw	vid_blank	; end equalizing pulse
	movwf	vid_port
	delm	((h/2)-equalization_pulse_width)-2

eq_second_half:
	movlw	vid_sync	; start equalizing pulse
	movwf	vid_port
	delm	equalization_pulse_width-2
	
	movlw	vid_blank	; end equalizing pulse
	movwf	vid_port
	return


vsync_line:
	movlw	vid_sync	; 64 - start vsync pulse
	movwf	vid_port
	delm	vsync_pulse_width-2
	
	movlw	vid_blank	; end vsync pulse - start serration
	movwf	vid_port
	delm	serration_pulse_width-2

vsync_second_half:
	movlw	vid_sync	; start vsync pulse
	movwf	vid_port
	delm	vsync_pulse_width-2
	
	movlw	vid_blank	; end vsync pulse - start serration
	movwf	vid_port
	return


vblank_line:
	movlw	vid_sync	; 64 - start hsync pulse
	movwf	vid_port	; 65
	delm	hsync_pulse_width-2	; 66-264
	
	movlw	vid_blank	; 265 - end hsync pulse
	movwf	vid_port	; 266
	call	color_burst	; 267-269
	return


black_video_line:	
	movlw	vid_sync	; 64 - start hsync pulse
	movwf	vid_port	; 65
	delm	hsync_pulse_width-2	; 66-264
	
	movlw	vid_blank	; 265 - end hsync pulse, start back porch
	movwf	vid_port	; 266

	call	color_burst	; 267-269

	movlw	vid_black	; 458 - end back porch, start active video
	movwf	vid_port	; 459

	goto	display_frame_setup	; $$$ not the best place for this?
;	return
	

	if	ft_interlace|ft_pal_video
; NTSC line 263, PAL line 623 (interlaced), PAL line 310 (non-interlaced)
black_eq_line:
	movlw	vid_sync	; 64 - start hsync pulse
	movwf	vid_port	; 65
	delm	hsync_pulse_width-2	; 66-264
	
	movlw	vid_blank	; 265 - end hsync pulse, start back porch
	movwf	vid_port	; 266

	call	color_burst	; 267-269

	movlw	vid_black	; 458 - end back porch, start active video
	movwf	vid_port	; 459
	delm	((h/2)-(hsync_pulse_width+back_porch_width))-5
	goto	eq_second_half
	endif


	if	ft_interlace
; NTSC line 266, PAL line 313
eq_vsync_line:
	movlw	vid_sync	; 64 - start equalizing pulse
	movwf	vid_port
	delm	equalization_pulse_width-2
	
	movlw	vid_blank	; end equalizing pulse
	movwf	vid_port
	delm	((h/2)-equalization_pulse_width)-5
	goto	vsync_second_half
	endif


	if	ft_interlace|ft_pal_video
; NTSC line 269, PAL line 3
vsync_eq_line:
	movlw	vid_sync	; 64 - start vsync pulse
	movwf	vid_port
	delm	vsync_pulse_width-2
	
	movlw	vid_blank	; end vsync pulse - start serration
	movwf	vid_port
	delm	serration_pulse_width-5
	goto	eq_second_half
	endif


	if	ft_interlace
; NTSC line 272 - like an equalization, but a full line with only one pulse
; PAL line 318
eq_vblank_line:
	movlw	vid_sync	; 64 - start equalizing pulse
	movwf	vid_port
	delm	equalization_pulse_width-2
	
	movlw	vid_blank	; end equalizing pulse
	movwf	vid_port
	return
	endif


	if	ft_interlace|ft_pal_video
; NTSC line 283, PAL line 23
vblank_black_line:
	movlw	vid_sync	; 64 - start hsync pulse
	movwf	vid_port	; 65
	delm	hsync_pulse_width-2	; 66-264
	
	movlw	vid_blank	; 265 - end hsync pulse
	movwf	vid_port	; 266

	call	color_burst	; 267-269

	delm	((h/2)+front_porch_width)-458	; 458-1431

	movlw	vid_black	; 1432 - start active video
	movwf	vid_port
	return
	endif


active_video_line:	
	movlw	vid_sync	; 64 - start hsync pulse
	movwf	vid_port	; 65
	delm	hsync_pulse_width-2	; 66-264
	
	movlw	vid_blank	; 265 - end hsync pulse, start back porch
	movwf	vid_port	; 266

	call	color_burst	; 267-269
	
	movlw	vid_black	; 458 - end back porch, start active video
	movwf	vid_port	; 459

	btfsc	vpix_cnt,3	; vpixel >= 8?
	goto	active_video_line_done

	delm	100

	movf	line_start,w
	movwf	char_ptr

	movlw	chars_per_row+1
	movwf	char_cnt

; leading dummy character is always blank, allows us to fill
; the pixel pipeline
	clrf	pixels

character:
; pixel 0 

	movlw	vid_black	; 0
	btfsc	pixels,0	; 1
	movlw	vid_white	; 2
	movwf	vid_port	; 3

	movf	char_ptr,w	; 4 - get next character
	movwf	fsr		; 5
	movf	indf,w		; 6
	bank	int_vars	; 7

	movwf	inverse_flag	; 8
	andlw	07fh		; 9
	movwf	chargen_ptr	; 10

	decf	char_cnt,w	; 11 - increment buffer pointer
	btfss	status,zf	; 12 -   unless we're at end of line
	incf	char_ptr	; 13 -   (due to pipeline, we pass through
	bsf	char_ptr,4	; 14 -   here columns+1 times)

; pixel 1

	movlw	vid_black	; 0
	btfsc	pixels,1	; 1
	movlw	vid_white	; 2
	movwf	vid_port	; 3

	movlw	(chargen/4)&0ffh ; 4 - add low part of chargen base
	addwf	chargen_ptr	; 5

	movlw	(chargen/4)>>8	; 6 - add high part of chargen base
	movwf	chargen_ptr+1	; 7
	btfsc	status,cf	; 8
	incf	chargen_ptr+1	; 9

	bcf	status,cf	; 10 - rotate high bit of vpix_cnt into
	btfsc	vpix_cnt,2	; 11 - table address
	bsf	status,cf	; 12
	rlf	chargen_ptr	; 13
	rlf	chargen_ptr+1	; 14

; pixel 2

	movlw	vid_black	; 0
	btfsc	pixels,2	; 1
	movlw	vid_white	; 2
	movwf	vid_port	; 3

	bcf	status,cf	; 4 - rotate next bit of vpix_cnt into
	btfsc	vpix_cnt,1	; 5 - table address
	bsf	status,cf	; 6
	rlf	chargen_ptr	; 7
	rlf	chargen_ptr+1	; 8

	delm	6		; 9-14

; pixel 3

	movlw	vid_black	; 0
	btfsc	pixels,3	; 1
	movlw	vid_white	; 2
	movwf	vid_port	; 3

	DelM	11		; 4-14

; pixel 4

	movlw	vid_black	; 0
	btfsc	pixels,4	; 1
	movlw	vid_white	; 2
	movwf	vid_port	; 3

	movf	chargen_ptr+1,w	; 4
	movwm			; 5
	movf	chargen_ptr,w	; 6
	iread			; 7-10
	movwf	pixels		; 11
	movmw			; 12
	mode	0fh		; 13
	movwf	chargen_ptr	; 14 - now use chargen_ptr as a temp

; intercharacter space

	btfsc	vpix_cnt,0	; 0 - get left four pixels into bits 0..3
	swapf	pixels		; 1
	movlw	vid_black	; 2
	movwf	vid_port	; 3
	bcf	pixels,4	; 4

	btfsc	vpix_cnt,0	; 5 - get rightmost pixel into bit 4
	rrf	chargen_ptr	; 6
	btfsc	chargen_ptr,0	; 7
	bsf	pixels,4	; 8

	movlw	01fh		; 9 - invert if needed
	btfsc	inverse_flag,7	; 10
	xorwf	pixels		; 11

; $$$ add more inter-character spacing here?

	decfsz	char_cnt	; 12
	goto	character	; 13-15

active_video_line_done:

	decfsz	scanline_cnt	; more scan lines for this pixel row?
	return
	movlw	scan_lines_per_vpixel
	movwf	scanline_cnt

	incf	vpix_cnt	; more pixels for this character row?
	movf	vpix_cnt,w
	xorlw	vpixels_per_char
	btfss	status,zf
	return

	clrf	vpix_cnt
	movf	char_ptr,w	; advance buffer pointer to next character row
	movwf	line_start

	return


;---------------------------------------------------------------------------
; serial receive routine
;---------------------------------------------------------------------------

	if	ft_serial_input

ser_idle:
	movlw	1		; 19 - sample every line
	movwf	ser_rx_samp_cnt	; 20

	skip_on_ser_rx_mark	; 21 - start bit detected?
	goto	ser_ret_25	; 22-24 - no, return

	movlw	lines_per_serial_sample * 3 / 2	; 23
	movwf	ser_rx_samp_cnt	; 24 - skip start bit and sample first data bit
				; in middle of bit time

	movlw	8		; 25 - set up to receive 8 chars
	movwf	ser_rx_bit_cnt	; 26

	clrf	ser_rx_byte	; 27 - not needed for 8-bit chars

	incf	ser_rx_state	; 28
	incf	ser_rx_flag	; 29 - signal main

	movlw	(lines_per_serial_sample * 11)/8 ; 30 - set up for stop bit
	movwf	ser_rx_samp_cnt	; 31

	incf	ser_rx_state	; 32 - advance to next state
	goto	ser_ret_36	; 33-35


ser_stop_bit:
	movlw	1		; 19 - sample every line
	movwf	ser_rx_samp_cnt	; 20

	skip_on_ser_rx_mark	; 21 - line idle?
	clrf	ser_rx_state	; 22 - yes, back to idle

ser_ret_23:	delm	2	; 23-24
ser_ret_25:	delm	4	; 25-28
ser_ret_29:	delm	3	; 29-31
ser_ret_32:	delm	4	; 32-35
ser_ret_36:	return		; 36-38

	endif


;---------------------------------------------------------------------------
; splash screen
;---------------------------------------------------------------------------

	if	ft_splash

	org	400h

splash_table:
	addwf	pcl
	dt	asc_bel
;		 01234567890123456789
	dt	"SERVID 0.2 Copyright"
	dt	"2001 Eric Smith and", asc_cr, asc_lf
	dt	"Richard Ottosen", asc_cr, asc_lf
	dt	"(SXLIST challenge) "
	dt	0


splash:
	clrf	temp+2
splash_loop:
	movf	temp+2,w
	call	splash_table
	xorlw	0
	btfsc	status,zf
	retp
	page	output_char
	call	output_char		; $$$ change to end with retp?
	page	$
	incf	temp+2
	goto	splash_loop

	endif


;---------------------------------------------------------------------------
; character generator macros
;---------------------------------------------------------------------------

	org	chargen

cg_row1	macro	pixels
	local	char
	local	col
char	set	pixels
r1_bits	set	0
col	set	0
	while	col<5
r1_bits	set	(r1_bits*2)+(char%10)
char	set	char/10
col	set	col+1
	endw
	endm

cg_row2	macro	pixels
	local	char
	local	col
char	set	pixels
r2_bits	set	0
col	set	0
	while	col<5
r2_bits	set	(r2_bits*2)+(char%10)
char	set	char/10
col	set	col+1
	endw

	dw	((r1_bits&010h)<<4)+(r1_bits&0fh)+((r2_bits&010h)<<5)+((r2_bits&0fh)<<4)
	endm

	include	"charset.inc"

	org	resetvec
	goto	reset

	end

See: