; 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