; SERVID - Serial video display using Ubicom SX microcontroller
; $Id: servid.asm,v 1.28 2001/01/04 23:52:32 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/
;---------------------------------------------------------------------------
; 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)
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
;---------------------------------------------------------------------------
; 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
vid_port equ portb ; D/A converter
vid_sync equ 0
vid_blank equ 77
vid_black equ 89
vid_white equ 255
trisb equ 0x00 ; all outputs
initb equ vid_sync
if ft_sx28
trisc equ 0x00 ; all outputs
initc equ 0x00 ; drive low
endif
;---------------------------------------------------------------------------
; 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
line_types equ 7
;---------------------------------------------------------------------------
; 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
; [1 .. line_types]
line_count: res 1 ; how many lines of this type to do
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
movlw line_types
movwf line_type
page line_count_tab
call line_count_tab
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"
; table of line type function pointers in reverse order
line_dispatch:
decf line_type,w ; 57 - change to zero-based
addwf pcl ; 58
goto black_video_line ; 61
goto active_video_line ; 61
goto black_video_line ; 61
goto vblank_line ; 61
goto equalization_line ; 61
goto vsync_line ; 61
goto equalization_line ; 61
; table of counts of line types in reverse order
line_count_tab:
decf line_type,w ; change to zero-based
addwf pcl
if ft_pal_video
retlw 86 ; black - PAL
else
retlw 61 ; black - NTSC
endif
retlw 120 ; active video
if ft_pal_video
retlw 86 ; black - PAL
else
retlw 61 ; black - NTSC
endif
retlw 11 ; vblank
retlw 3 ; post-equalizing
retlw 3 ; vsync
retlw 3 ; pre-equalizing
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