; 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