; Display board firmware, to allow the JW-002 display board to be
; used with the PJRC MP3 Player:   http://www.pjrc.com/tech/mp3/
; The JW-002 display board may be purchased from PJRC.COM, with
; this firmware installed and an adaptor board with 12 pushbuttons
; and two cables to allow it to connect directly to the PJRC MP3
; player circuit board's 4-pin connector.

; The JW-002 display board may also be purchased directly from
; Earth Computer Tech, at:  http://www.flat-panel.com/jw002.htm
; Earth will offer quantity discounts, so for applications other
; than use with the PJRC MP3 player, Earth may be a better choice
; for purchasing the display.  They provide the JW-002 without
; the extra parts needed for use with the PJRC MP3 player board.

; This JW-002 firmware source code has been placed in the public domain.
; This code is provided in the hope that it will be useful, but
; it is provided without any warranty; without even the implied
; warranty of merchantability or fitness for a particular purpose. 


;TODO: handle pushbutton "ghosts" (eg if you press #2, #3 and #4
;  at same time, #10 appears as a ghost).


	;configurable (but hard-coded) settings

	.equ    location, 0x0000	;stand-alone build
	;.equ    location, 0x8000	;to run under PAULMON2 (see below)

	;.equ	baud_const, 255		;57600 baud
	.equ	baud_const, 253		;19200 baud
	;.equ	baud_const, 250		;9600 baud


; ******************************************************************



	;internal memory allocation

	.equ	reg0, 0
	.equ	reg1, 1
	.equ	reg2, 2
	.equ	reg3, 3
	.equ	reg4, 4
	.equ	reg5, 5
	.equ	reg6, 6
	.equ	reg7, 7
	.equ	rx_buf_head, 0x08	;1 byte
	.equ	rx_buf_tail, 0x09	;1 byte
	.equ	tx_buf_head, 0x0A	;1 byte
	.equ	tx_buf_tail, 0x0B	;1 byte
	.equ	x_pos, 0x0C		;1 byte, current x char position
	.equ	y_pos, 0x0D		;1 byte, current y char position
	.equ	font, 0x0E		;1 byte, current font to use
	.equ	cmd_len, 0x0F		;1 byte, # of chars revc
	.equ	cmd_buf, 0x10		;16 byte command buffer
	.equ	hscroll_en, 0x20	;1 byte
	.equ	mode_bits, 0x21		;3 bytes
	.equ	vscroll_offset, 0x24	;1 byte, current y scroll offset
	.equ	pb_scan_state, 0x25	;1 byte
	.equ	char_buf, 0x26		;5 bytes, buffer of current char
	.equ	pb_state, 0x2b		;12 bytes, pushbutton state
	.equ	pb_config, 0x37		;12 bytes, config for each button
	.equ	pb_event_head, 0x43	;1 byte
	.equ	pb_event_tail, 0x44	;1 byte
	.equ	lcd_on_timer, 0x45	;1 byte
	.equ	redraw_addr, 0x46	;2 bytes


	.equ	hscroll_speed, 0x49	;1 byte
	.equ	hscroll_t0_count, 0x4a	;1 byte
	.equ	hscroll_fill_row, 0x4b  ;1 byte
	.equ	hscroll_font0, 0x4c	;1 byte
	.equ	hscroll_font1, 0x4d	;1 byte
	.equ	hscroll_font2, 0x4e	;1 byte
	.equ	hscroll_font3, 0x4f	;1 byte
	.equ	hscroll_font4, 0x50	;1 byte
	.equ	hscroll_font5, 0x51	;1 byte
	.equ	hscroll_font6, 0x52	;1 byte
	.equ	hscroll_font7, 0x53	;1 byte
	.equ	hscroll_end0, 0x54	;1 byte
	.equ	hscroll_end1, 0x55	;1 byte
	.equ	hscroll_end2, 0x56	;1 byte
	.equ	hscroll_end3, 0x57	;1 byte
	.equ	hscroll_end4, 0x58	;1 byte
	.equ	hscroll_end5, 0x59	;1 byte
	.equ	hscroll_end6, 0x5a	;1 byte
	.equ	hscroll_end7, 0x5b	;1 byte
	.equ	hscroll_pos0, 0x5c	;1 byte
	.equ	hscroll_pos1, 0x5d	;1 byte
	.equ	hscroll_pos2, 0x5e	;1 byte
	.equ	hscroll_pos3, 0x5f	;1 byte
	.equ	hscroll_pos4, 0x60	;1 byte
	.equ	hscroll_pos5, 0x61	;1 byte
	.equ	hscroll_pos6, 0x62	;1 byte
	.equ	hscroll_pos7, 0x63	;1 byte
	.equ	stack, 0x64		;28 byte stack

	;bit mapped memory allocation (4 bytes)
	;hscroll_en* must start at 0
	.equ	hscroll_en0, 0
	.equ	hscroll_en1, 1
	.equ	hscroll_en2, 2
	.equ	hscroll_en3, 3
	.equ	hscroll_en4, 4
	.equ	hscroll_en5, 5
	.equ	hscroll_en6, 6
	.equ	hscroll_en7, 7
	.equ	wrap_mode, 8
	.equ	vscroll_mode, 9
	.equ	ignore_input_mode, 10
	.equ	cin_serviced, 11
	.equ	tx_intr_expected, 12
	.equ	update_mode, 13
	.equ	hscroll_fill_en, 14
	.equ	hscroll_redraw, 15
	.equ	turn_lcd_on, 16
	.equ	clocked_serial_mode, 17	; indicates clocked serial mode

	;external data memory
	.equ	rx_buf_page, 0		;256 byte rx buffer, 0000 to 00FF
	.equ	tx_buf_page, 1		;256 byte tx buffer, 0100 to 01FF
	.equ	pb_strings_page, 2	;512 bytes, strings, 0200 to 03FF
	.equ    display_cache, 4        ;5 columns * 24 characters * 8 rows =
					;960 bytes, display cache 0400 to 07FF
					;redraw routine assumes 1k boundary alignment
	.equ	hscroll_buffer, 8	;8x256 byte bufferx for horizontal scrolling rows
	.equ	pb_events_page, 0x10	;256 byte event buffer, 0800 to 08FF
	.equ	fonts_start_page, 0x11	;28416 bytes for fonts, 1100 to 7FFF
					;this allows 22 fonts to be stored
	.equ	num_rom_fonts, 4	;4 fonts included in ROM


	;memory mapped interface to the LCD display controller chips
	.equ	left_cmd_wr, 0xFF00
	.equ	left_cmd_rd, 0xFF01
	.equ	left_data_wr, 0xFF02
	.equ	left_data_rd, 0xFF03
	.equ	right_cmd_wr, 0xFF04
	.equ	right_cmd_rd, 0xFF05
	.equ	right_data_wr, 0xFF06
	.equ	right_data_rd, 0xFF07

	;connections to the 8051 port pins
        .equ    pb_xmit_1, 0xB2         ;p3.2
        .equ    pb_xmit_2, 0xB3         ;p3.3
        .equ    pb_xmit_3, 0x97         ;p1.7
        .equ    pb_recv_1, 0x93         ;p1.3
        .equ    pb_recv_2, 0x92         ;p1.2
        .equ    pb_recv_3, 0x94         ;p1.4
        .equ    pb_recv_4, 0x90         ;p1.0
	.equ	led_pin, 0x91		;clear to turn on LED
	.equ	watchdog_pin, 0xB5	;toggle to clear watchdog timer
	.equ	bank_pin1, 0x95		;ROM memory bank selects
	.equ	bank_pin2, 0x96

	; internal memory for clocked serial mode
	; NB: these overlay internal memory used for push buttons
	;     which are not supported in clocked serial mode
	.equ 	serial_bit_cnt, pb_scan_state
	.equ 	serial_acc, pb_state

	; port pins for clocked serial mode (these overload the pins used for pushbuttons)
        .equ    clk_ser_req, 0x90	;p1.0 - hold low during startup
        .equ    serial_data, 0x92	;p1.2 - incoming data
        .equ    serial_clk, 0xB2	;p3.2 - bit clock (negative edge)
        .equ    serial_data_rx, 0xB3	;p3.3 - acknowledge
	
;pushbutton config
;
; autorepeat delay: no autorepeat, 400 ms, 650 ms, 900 ms
; autorepeat speed: 3/sec, 5/sec, 8/sec, 12/sec
; xmit press event
; xmit depress event



;***********************************************************************
;**                                                                   **
;**                          Main Program                             **
;**                                                                   **
;***********************************************************************

	.org	location
	ljmp	begin

	.org	location+3
	ljmp	extern0_isr

	.org	location+11
	ljmp	timer0_isr

	.org	location+35
	ljmp	uart_intr

.org    location+256
.db     0xA5,0xE5,0xE0,0xA5     ;signiture bytes
.db     35,255,0,0              ;id
.db     0,0,0,0                 ;reserved
.db     0,0,0,0                 ;reserved
.db     0,0,0,0                 ;reserved
.db     0,0,0,0                 ;reserved
.db     0,0,0,0                 ;user defined
.db     255,255,255,255         ;length and checksum (255=unused)
.db     "JW-002 LCD Firmware",0
.org    location+256+64         ;executable code begins here


; To allow for program development with PAULMON2, the JW-002 display
; board needs several modifications.
;
; 1: Program PAULMON2 (pm21_8.obj) into the flash chip.
;
; 2: Disable the watchdog timer: short C1.
;
; 3: Disconnect the flash chip's CE pin from ground and connect it to
;    A15.  The wide trace that runs under the upper left corner of the 
;    PLCC socket is ground and only connects to CE, so this is the
;    trace to cut.  A15 may be found on a via 0.25 inches away from
;    the corner of this trace near the PLCC socket.
;
; 4: Connect PSEN and RD to an AND gate.  The two unused NAND gates
;    in IC2 may be used, if the pins are lifted away from the board.
;
; 5: Disconnect IC7's CE (pin 22) and connect it to the AND gate output.
;
; With these changes, this code may be built at 8000 and downloaded to
; the board.  Memory from 0000 to 7FFF will be both code memory (the
; flash chip with PAULMON2) and data memory (32k SRAM, IC8), and the
; firmware will use the SRAM starting at 0000 for fonts.  The code
; allows 32 fonts, but care should be taken not to use more than the
; first 22 fonts, as the 23rd font will begin to overwrite the program
; at 8000.


begin:
	mov	sp, #stack
	mov	ie, #0
	mov	psw, #0
	lcall	retinow
	lcall	retinow
	setb	update_mode
	lcall	init_display

	lcall	move_cursor_home
	mov	font, #0
	mov	cmd_len, #0
	setb	wrap_mode
	setb	vscroll_mode
	clr	ignore_input_mode
	clr	hscroll_fill_en
	clr	hscroll_redraw
	clr	clocked_serial_mode
	mov	hscroll_fill_row, #0
	mov	hscroll_en, #0
	mov	hscroll_font0, #0
	mov	hscroll_font1, #0
	mov	hscroll_font2, #0
	mov	hscroll_font3, #0
	mov	hscroll_font4, #0
	mov	hscroll_font5, #0
	mov	hscroll_font6, #0
	mov	hscroll_font7, #0
	mov	hscroll_speed, #0x06
	mov	hscroll_pos0, #0
	mov	hscroll_pos1, #0
	mov	hscroll_pos2, #0
	mov	hscroll_pos3, #0
	mov	hscroll_pos4, #0
	mov	hscroll_pos5, #0
	mov	hscroll_pos6, #0
	mov	hscroll_pos7, #0
	mov	hscroll_end0, #0
	mov	hscroll_end1, #0
	mov	hscroll_end2, #0
	mov	hscroll_end3, #0
	mov	hscroll_end4, #0
	mov	hscroll_end5, #0
	mov	hscroll_end6, #0
	mov	hscroll_end7, #0
	mov	hscroll_t0_count, #0

	lcall	init_uart_intr
	lcall	timer0_setup

	lcall	copy_fonts_to_sram

	mov	a, #location >> 8
	jz	main_loop
	mov	dptr, #mesg_abort
	lcall	pstr


main_loop:
	jnb	turn_lcd_on, main_event
	lcall	display_on
main_event:
	lcall	get_event
	jz	main_cont
	lcall	print_event
main_cont:
	jb	hscroll_redraw, main_hscroll_redraw
	lcall	cin
	jnc	main_got_char
	sjmp	main_loop
	
main_got_char:
	;lcall	cout			;uncomment for echo of input characters
	mov	r2, a
main1:	mov	a, cmd_len		;check if continuing multi-byte cmd
	jz	main2
	;if cmd_len > 0, this byte is to be treated at part of
	;a multi-byte command, and not regular input
	mov	a, #cmd_buf
	add	a, cmd_len
	mov	r0, a
	mov	a, r2
	mov	@r0, a
	inc	cmd_len
	acall	exec_cmd
	ajmp	main_loop
main2:	mov	a, r2			;check for 0x5C command
	cjne	a, #0x5C, main3
	;the '\' character, 0x5C, begin a command
	mov	r0, #cmd_buf
	mov	a, r2
	mov	@r0, a
	mov	cmd_len, #1
	;all '\' commands are at least two characters, so don't call exec_cmd
	ajmp	main_loop
main3:	mov	a, r2			;check for control character
	add	a, #224
	jc	main4
	mov	r0, #cmd_buf
	mov	a, r2
	mov	@r0, a
	mov	cmd_len, #1
	acall	exec_cmd
	ajmp	main_loop
main4:	jb	ignore_input_mode, main_loop	;check ignore input mode
	mov	a, r2
	lcall	lookup_char
	lcall	cache_char
	ajmp	main_loop


main_hscroll_redraw:
	mov	dph, #(hscroll_buffer - 1)
main_hscroll_redraw_next_row:
	inc	dph
	mov	a, dph
	cjne	a, #(hscroll_buffer + 8), main_hscroll_redraw_start
main_hscroll_redraw_exit:
	clr	hscroll_redraw
	ajmp	main_loop
main_hscroll_redraw_start:
	anl	a, #00000111b
	acall	check_hscroll_en
	jnc	main_hscroll_redraw_next_row
	mov	a, #0			;save params
	mov	c, wrap_mode
	rrc	a	
	mov	c, vscroll_mode
	rrc	a
	orl	a, x_pos
	mov	r2, a
	mov	r4, y_pos
	mov	r3, font
	mov	x_pos, #0		;init params
	mov	a, dph			
	anl	a, #00000111b
	mov	y_pos, a
	add	a, #hscroll_pos0	;get first char location
	mov	r0, a
	mov	dpl, @r0
	clr	c
	subb	a, #(hscroll_pos0 - hscroll_font0)
	mov	r0, a
	mov	font, @r0		;get font for this line
	add	a, #(hscroll_end0 - hscroll_font0)
	mov	r0, a			;point to end
	clr	wrap_mode
	clr	vscroll_mode
	mov	r1, #0
main_hscroll_redraw_next:
	movx	a, @dptr
	mov	r5, dph
	mov	r6, dpl
	lcall	lookup_char
	lcall	cache_char_only
	mov	dph, r5
	mov	dpl, r6
	inc	dpl
	inc	r1
	cjne	r1, #24, main_hscroll_redraw_cont
	sjmp	main_hscroll_redraw_complete
main_hscroll_redraw_cont:
	mov	a, dpl			;check for buffer wrap
	clr	c
	subb	a, @r0
	jnz	main_hscroll_redraw_next
	mov	dpl, #0
	sjmp	main_hscroll_redraw_next
main_hscroll_redraw_complete:
	mov	a, dph
	anl	a, #00000111b
	mov	r7, a
	mov	r5, dph
	lcall	redraw_row
	mov	dph, r5
	mov	a, r2		;restore params
	anl	a, #00011111b
	mov	x_pos, a
	mov	y_pos, r4
	mov	font, r3
	mov	a, r2
	rlc	a
	mov	vscroll_mode, c
	rlc	a
	mov	wrap_mode, c
	mov	a, dph
	anl	a, #00000111b
	add	a, #hscroll_pos0
	mov	r1, a
	inc	@r1
	mov	a, @r1
	clr	c
	subb	a, @r0
	jnz	main_hscroll_redraw_cont1
	mov	@r1, #0
main_hscroll_redraw_cont1:
	ljmp	main_hscroll_redraw_next_row



; checks whether the row specified in a is enabled for horizontal
; scrolling - result in c

check_hscroll_en:
	push	acc
	clr	c
	jnz	check_hscroll_en1
	mov	c, hscroll_en0
	sjmp	check_hscroll_done
check_hscroll_en1:
	dec	a
	jnz	check_hscroll_en2
	mov	c, hscroll_en1
	sjmp	check_hscroll_done
check_hscroll_en2:
	dec	a
	jnz	check_hscroll_en3
	mov	c, hscroll_en2
	sjmp	check_hscroll_done
check_hscroll_en3:
	dec	a
	jnz	check_hscroll_en4
	mov	c, hscroll_en3
	sjmp	check_hscroll_done
check_hscroll_en4:
	dec	a
	jnz	check_hscroll_en5
	mov	c, hscroll_en4
	sjmp	check_hscroll_done
check_hscroll_en5:
	dec	a
	jnz	check_hscroll_en6
	mov	c, hscroll_en5
	sjmp	check_hscroll_done
check_hscroll_en6:
	dec	a
	jnz	check_hscroll_en7
	mov	c, hscroll_en6
	sjmp	check_hscroll_done
check_hscroll_en7:
	dec	a
	jnz	check_hscroll_done
	mov	c, hscroll_en7
check_hscroll_done:
	pop	acc
	ret

;***********************************************************************
;**                                                                   **
;**                     Handle Control Characters                     **
;**                                                                   **
;***********************************************************************



exec_cmd:

	mov	r0, #cmd_buf
	mov	a, @r0
	cjne	a, #0x5C, exec_ctrl
	ajmp	exec_5c_cmd

exec_ctrl:

	;ESC Sequence
escape:	cjne	a, #27, ctrl_char
	ajmp	exec_esc

ctrl_char:
	jnb	ignore_input_mode, ctrl_a
	mov	cmd_len, #0
	ret

	;CTRL-A: Cursor Home
ctrl_a: cjne	a, #1, ctrl_h
	lcall	display_on
	lcall	move_cursor_home
	mov	cmd_len, #0
	ret

	;CTRL-H: Backspace
ctrl_h:	cjne	a, #8, ctrl_i
	mov	a, #1
	lcall	move_cursor_left
	mov	cmd_len, #0
	ret

	;CTRL-I: Tab
ctrl_i:	cjne	a, #9, ctrl_j
	mov	a, x_pos
	anl	a, #00000011b
	cpl	a
	inc	a
	add	a, #4
	lcall	move_cursor_right
	mov	cmd_len, #0
	ret

	;CTRL-J: Line Feed
ctrl_j:	cjne	a, #10, ctrl_k
	mov	a, #1
	lcall	move_cursor_down
	mov	cmd_len, #0
	ret

	;CTRL-K: Up one line
ctrl_k: cjne	a, #11, ctrl_l
	mov	a, #1
	lcall	move_cursor_up
	mov	cmd_len, #0
	ret

	;CTRL-L: Form Feed (clear display)
ctrl_l:	cjne	a, #12, ctrl_m
	lcall	init_display
	lcall	move_cursor_home
	mov	cmd_len, #0
	ret

	;CTRL-M: Carriage Return
ctrl_m:	cjne	a, #13, ctrl_u
	lcall	cursor_to_start_of_line
	mov	cmd_len, #0
	ret

	;CTRL-U: Forward
ctrl_u:	cjne	a, #21, crtl_unknown
	mov	a, #1
	lcall	move_cursor_right
	mov	cmd_len, #0
	ret

crtl_unknown:
	;just ignore unknown control characters
	mov	cmd_len, #0
	ret



;***********************************************************************
;**                                                                   **
;**                       Handle Escape Sequences                     **
;**                                                                   **
;***********************************************************************


	;0x1B 0x5B 0x41, move up one line
	;0x1B 0x5B 0x42, move down one line
	;0x1B 0x5B 0x43, move right one line
	;0x1B 0x5B 0x44, move left one line
	;0x1B 0x5B 0x48, cursor home
	;0x1B 0x5B 0x30 0x3B 0x37 0x6B, begin inverse text
	;0x1B 0x5B 0x30 0x6B, begin normal text
	;0x1B 0x5B 0x32 0x4A, clear screen

exec_esc:
	mov	a, cmd_len
	add	a, #254
	jc	exec_esc_1
	ret				;if len < 2, wait for more

exec_esc_1:
	mov	a, cmd_len
	add	a, #240
	jnc	exec_esc_2		;check if len >= 16
	mov	cmd_len, #0		;if len > 16, ignore input
	ret

exec_esc_2:
	mov	r0, #cmd_buf+1
	mov	a, @r0
	cjne	a, #0x5B, exec_esc_esc
	ajmp	exec_esc_5B

exec_esc_esc:
	cjne	a, #0x1B, exec_esc_unknown
	mov	a, #location >> 8
	jnz	exec_esc_abort
	dec	cmd_len			;ignore the 2nd ESC
	ret
exec_esc_abort:
	ljmp	0			;return to PAULMON2 on two ESCs


exec_esc_unknown:
	;just ignore unknown escape sequences
	mov	cmd_len, #0
	ret

exec_esc_5B:
	;TODO: this parsing really ought to allow numeric and ';'
	;characters and conclude that we have a complete escape
	;sequence when we get a alpha character A-Z or a-z.
	mov	a, cmd_len
	add	a, #253
	jc	exec_esc_5B_2
	ret				;if len < 3, wait for more

exec_esc_5B_2:
	mov	r0, #cmd_buf+2
	mov	a, @r0

exec_esc_5B_41:
	cjne	a, #0x41, exec_esc_5B_42
	jb	ignore_input_mode, exec_esc_unknown
	mov	a, #1
	lcall	move_cursor_up
	mov	cmd_len, #0
	ret

exec_esc_5B_42:
	cjne	a, #0x42, exec_esc_5B_43
	jb	ignore_input_mode, exec_esc_unknown
	mov	a, #1
	lcall	move_cursor_down
	mov	cmd_len, #0
	ret

exec_esc_5B_43:
	cjne	a, #0x43, exec_esc_5B_44
	jb	ignore_input_mode, exec_esc_unknown
	mov	a, #1
	lcall	move_cursor_right
	mov	cmd_len, #0
	ret

exec_esc_5B_44:
	cjne	a, #0x44, exec_esc_5B_unknown
	jb	ignore_input_mode, exec_esc_unknown
	mov	a, #1
	lcall	move_cursor_left
	mov	cmd_len, #0
	ret

exec_esc_5B_unknown:
	;just ignore unknown escape sequences
	mov	cmd_len, #0
	ret





;***********************************************************************
;**                                                                   **
;**                   Handle Custom Display Commands                  **
;**                                                                   **
;***********************************************************************


exec_5c_cmd:
	mov	a, cmd_len
	add	a, #254
	jc	exec_5c_cmd_1
	ret				;if len < 2, wait for more

exec_5c_cmd_1:
	mov	a, cmd_len
	add	a, #240
	jnc	exec_5c_cmd_2		;check if len >= 16
	mov	cmd_len, #0		;if len > 16, ignore input
	ret


	;cmd 0x5C <0x20-0x3F>, display bytes 0 to 31
exec_5c_cmd_2:
	mov	r0, #cmd_buf+1
	mov	a, @r0
	anl	a, #11100000b
	cjne	a, #00100000b, exec_5c_cmd_3
	;if we get here, the cmd is to display bytes 0 to 31
	mov	a, @r0
	add	a, #224
exec_display_char:
	lcall	lookup_char
	lcall	cache_char
	mov	cmd_len, #0
	ret


	;cmd 0x5C <0x60-0x7F>, display bytes 128 to 159
exec_5c_cmd_3:
	cjne	a, #01100000b, exec_5c_5c
	;if we get here, the cmd is to display bytes 128 to 159
	mov	a, @r0
	add	a, #32
	sjmp	exec_display_char


	;cmd 0x5C 0x5C, display '\'
exec_5c_5c:
	mov	a, @r0
	cjne	a, #0x5C, exec_5c_5f
	sjmp	exec_display_char


	;cmd 0x5C 0x5C, display byte 127
exec_5c_5f:
	cjne	a, #0x5F, exec_5c_5d
	mov	a, #127
	sjmp	exec_display_char


	;cmd 0x5C 0x5D, begin ignoring input bytes (but not commands)
exec_5c_5d:
	cjne	a, #0x5D, exec_5c_5b
	setb	ignore_input_mode
	mov	cmd_len, #0
	ret


	;cmd 0x5C 0x5B, don't ignore input bytes
exec_5c_5b:
	cjne	a, #0x5B, exec_5c_40
	clr	ignore_input_mode
	mov	cmd_len, #0
	ret


	;cmd 0x5C 0x40 <font+32> <opcode>, clear display or show font summary
exec_5c_40:
	cjne	a, #0x40, exec_5c_41
	mov	a, cmd_len
	cjne	a, #4, exec_5c_40_end
	mov	r0, #cmd_buf+2
	mov	a, @r0
	acall	byte2font
	jc	exec_5c_40_done
	mov	font, a
	mov	r0, #cmd_buf+3
	mov	a, @r0
exec_5c_40_30:
	cjne	a, #0x30, exec_5c_40_31
	lcall	init_display
	sjmp	exec_5c_40_done
exec_5c_40_31:
	cjne	a, #0x31, exec_5c_40_32
	mov	r6, #0
	mov	r0, #cmd_buf+4
	mov	@r0, #msg_f0 & 255
	inc	r0
	mov	@r0, #msg_f0 >> 8
	sjmp	exec_5c_40_font_preview
exec_5c_40_32:
	cjne	a, #0x32, exec_5c_40_done
	mov	r6, #128
	mov	r0, #cmd_buf+4
	mov	@r0, #msg_f128 & 255
	inc	r0
	mov	@r0, #msg_f128 >> 8
exec_5c_40_font_preview:		;draw font preview page
	lcall	test_font
	mov	a, font
	add	a, #'0'
	mov	r6, a
	push	font
	mov	font, #0
	lcall	move_cursor_home
	mov	dptr, #msg_font
	lcall	lcd_pstr
	mov	a, r6
	lcall	lookup_char
	lcall	cache_char
	mov	x_pos, #0
	mov	y_pos, #2
	mov	r0, #cmd_buf+4
	mov	dpl, @r0
	inc	r0
	mov	dph, @r0
	lcall	lcd_pstr
	mov	x_pos, #0
	mov	y_pos, #3
	lcall	lcd_pstr
	pop	font
exec_5c_40_done:
	lcall	move_cursor_home
	mov	cmd_len, #0
exec_5c_40_end:
	ret


	;cmd 0x5C 0x41 <mode>, set modes (wrap and vscroll)
exec_5c_41:
	cjne	a, #0x41, exec_5c_42
	mov	a, cmd_len
	cjne	a, #3, exec_5c_41_end
	mov	r0, #cmd_buf+2
	mov	a, @r0
	anl	a, #11110000b
	cjne	a, #0x30, exec_5c_41_done
	mov	a, @r0
	rrc	a
	mov	wrap_mode, c
	rrc	a
	mov	vscroll_mode, c
exec_5c_41_done:
	mov	cmd_len, #0
exec_5c_41_end:
	ret


	;cmd 0x5C 0x42 <column+32> <row+32>, set cursor position
exec_5c_42:
	cjne	a, #0x42, exec_5c_43
	mov	a, cmd_len
	cjne	a, #4, exec_5c_42_end
	mov	r0, #cmd_buf+2
	mov	a, @r0
	add	a, #224
	mov	x_pos, a
	mov	r0, #cmd_buf+3
	mov	a, @r0
	add	a, #224
	mov	y_pos, a
exec_5c_42_done:
	mov	cmd_len, #0
exec_5c_42_end:
	ret


	;cmd 0x5C 0x43 <font+32>, set font
exec_5c_43:
	cjne	a, #0x43, exec_5c_44
	mov	a, cmd_len
	cjne	a, #3, exec_5c_43_end
	mov	r0, #cmd_buf+2
	mov	a, @r0
	acall	byte2font
	jc	exec_5c_43_done
	mov	font, a
exec_5c_43_done:
	mov	cmd_len, #0
exec_5c_43_end:
	ret




	;define a custom character
	;cmd 0x5C 0x44 <char (2 bytes hex)> <font+32> <bitmap (8 bytes)>
	;
	;  example: replace "R" (R is ascii code 0x52) in font 0
	;  0x5C 0x44 0x35 0x32 0x20 0x3E 0x39 0x39 0x39 0x3E 0x3A 0x39 0x39
	;  as an ascii string: "\D52 >999>:99"
	;  
	; ****   00111110  0x3E
	; **  *  00111001  0x39
	; **  *  00111001  0x39
	; **  *  00111001  0x39
	; ****   00111110  0x3E
	; ** *   00111010  0x3A
	; **  *  00111001  0x39
	; **  *  00111001  0x39
exec_5c_44:
	cjne	a, #0x44, exec_5c_45
	mov	a, cmd_len
	cjne	a, #13, exec_5c_44_end
	mov	r0, #cmd_buf+2
	mov	a, @r0
	acall	asc2hex
	jc	exec_5c_44_done
	swap	a
	mov	r1, a
	mov	r0, #cmd_buf+3
	mov	a, @r0
	acall	asc2hex
	jc	exec_5c_44_done
	orl	a, r1
	mov	r1, a			;r1 is the character to write
	mov	r0, #cmd_buf+4
	mov	a, @r0
	acall	byte2font
	jc	exec_5c_44_done
	mov	r2, a			;r2 is the font number of change
	mov	r0, #cmd_buf+5
exec_5c_44_loop1:
	mov	a, @r0			;make sure all bitmap bytes are legal
	anl	a, #11100000b
	cjne	a, #00100000b, exec_5c_44_done
	inc	r0
	cjne	r0, #cmd_buf+13, exec_5c_44_loop1
	mov	a, r2
	mov	b, #5
	mul	ab			;compute font's memory position
	add	a, #fonts_start_page
	mov	r7, a
	mov	a, r1
	mov	b, #5
	mul	ab			;compute offset within font
	mov	dpl, a
	mov	a, b
	add	a, r7
	mov	dph, a			;dptr points to character to update
	mov	r3, #0			;r3 counts font x position (0 to 4)
exec_5c_44_loop2:
	mov	a, r3
	acall	exec_5c_44_bitmask
	mov	r4, a			;r4 holds bitmask for this x position
	mov	r0, #cmd_buf+5
	mov	r5, #0			;r5 will become 8 bit vertical pattern
exec_5c_44_loop3:
	mov	a, @r0
	anl	a, r4
	setb	c
	jnz	exec_5c_44_loop3b
	clr	c
exec_5c_44_loop3b:
	mov	a, r5
	rrc	a
	mov	r5, a
	inc	r0
	cjne	r0, #cmd_buf+13, exec_5c_44_loop3
	mov	a, r5
	movx	@dptr, a		;write to the in-memory font
	inc	dptr
	inc	r3
	cjne	r3, #5, exec_5c_44_loop2
exec_5c_44_done:
	mov	cmd_len, #0
exec_5c_44_end:
	ret
exec_5c_44_bitmask:
	inc	a
	movc	a, @a+pc
	ret
	.db	16, 8, 4, 2, 1


exec_5c_45:
	cjne	a, #0x45, exec_5c_46
 	mov	a, #0xFF
 	lcall	cout
 	mov	a, #0xC3
 	lcall	cout
	mov	dptr, #mesg_version
	lcall	pstr
	mov	a, #'1'
	lcall	cout
	mov	cmd_len, #0
	ret


exec_5c_46:
	cjne	a, #0x46, exec_5c_47
	mov	a, cmd_len
	cjne	a, #3, exec_5c_46_end
	mov	r0, #cmd_buf+2
	mov	a, @r0
	add	a, #224
	jnc	exec_5c_46_done
exec_5c_46_left:
	add	a, #224
	jc	exec_5c_46_right
	add	a, #32
	lcall	move_cursor_left
	sjmp	exec_5c_46_done
exec_5c_46_right:
	add	a, #224
	jc	exec_5c_46_down
	add	a, #32
	lcall	move_cursor_right
	sjmp	exec_5c_46_done
exec_5c_46_down:
	add	a, #240
	jc	exec_5c_46_up
	add	a, #16
	lcall	move_cursor_down
	sjmp	exec_5c_46_done
exec_5c_46_up:
	add	a, #240
	jc	exec_5c_46_up
	add	a, #16
	lcall	move_cursor_up
	sjmp	exec_5c_46_done
exec_5c_46_done:
	mov	cmd_len, #0
exec_5c_46_end:
	ret


	;cmd 0x5C 0x47 <button+48> <config+32>, set button config
exec_5c_47:
	cjne	a, #0x47, exec_5c_48
	mov	a, cmd_len
	cjne	a, #4, exec_5c_47_end
	mov	r0, #cmd_buf+2
	mov	a, @r0
	add	a, #256 - 48
	add	a, #256 - 12
	jc	exec_5c_46_done
	add	a, #12 + pb_config
	mov	r1, a
	inc	r0
	mov	a, @r0
	add	a, #256 - 32
	mov	@r1, a
exec_5c_47_done:
	mov	cmd_len, #0
exec_5c_47_end:
	ret


	;cmd 0x5C 0x48 <button> <string, 12 bytes>
	;button 48 - 59 = string for button down
	;button 64 - 75 = string for button up
	;button 80 - 91 = string for button repeat
exec_5c_48:
	cjne	a, #0x48, exec_5c_49
	mov	a, cmd_len
	add	a, #241
	jnc	exec_5c_48_end
exec_5c_48_ok:
	mov	r0, #cmd_buf+2
	mov	a, @r0
	add	a, #256 - 48 + 0x10
	lcall	event2addr
	jc	exec_5c_48_done
	mov	r0, #cmd_buf+14
exec_5c_48_loop1:
	mov	a, @r0
	cjne	a, #' ', exec_5c_48_copy
	clr	a
	mov	@r0, a
	dec	r0
	cjne	r0, #cmd_buf+2, exec_5c_48_loop1
exec_5c_48_copy:
	mov	r0, #cmd_buf+3
exec_5c_48_loop2:
	mov	a, @r0
	inc	r0
	movx	@dptr, a
	inc	dptr
	cjne	r0, #cmd_buf+14, exec_5c_48_loop2
	clr	a
	movx	@dptr, a
exec_5c_48_done:
	mov	cmd_len, #0
exec_5c_48_end:
	ret


	;cmd 0x5c 0x49 <param> 
	;redraw all (param = '8') or a row ('0' <= param <= '7')
	;       of the screen from the cache
	;turn display updates on '9' or off ':'

exec_5c_49:
	cjne	a, #'I', exec_5c_4a
	mov	a, cmd_len
	cjne	a, #3, exec_5c_49_end
	mov	a, cmd_buf+2
	anl	a, #11110000b
	cjne	a, #0x30, exec_5c_49_done
	mov	a, cmd_buf+2
	jnb	acc.3, exec_5c_49_partial
	cjne	a, #'8', exec_5c_49_toggle
	lcall	redraw
	sjmp	exec_5c_49_done
exec_5c_49_toggle:
	rrc	a
	mov	update_mode, c
	sjmp	exec_5c_49_done
exec_5c_49_partial:
	anl	a, #00000111
	mov	r7, a
	lcall	redraw_row
exec_5c_49_done:
	mov	cmd_len, #0
exec_5c_49_end:
	ret



	;cmd 0x5c 0x4a <'0'|'1'|'2'> <'0'|'1'> write
	;cmd 0x5c 0x4a <'4'|'5'|'6'> <'0'>     read
	;set/read LED/bank_select state
exec_5c_4a:
	cjne	a, #0x4a, exec_5c_4b
	mov	a, cmd_len
	cjne	a, #4, exec_5c_4a_end
	mov	a, cmd_buf+3
	rrc	a
	cpl	c
	mov	a, cmd_buf+2
	jb	acc.2, exec_5c_4a_read
	anl	a, #3
	jnz	exec_5c_4a_notled
	mov	led_pin, c
	sjmp	exec_5c_4a_done
exec_5c_4a_notled:
	dec	a
	jnz	exec_5c_4a_notbank1
	mov	bank_pin1, c
	sjmp	exec_5c_4a_done
exec_5c_4a_notbank1:
	dec	a
	jnz	exec_5c_4a_done
	mov	bank_pin2, c
	sjmp	exec_5c_4a_done
exec_5c_4a_read:
	mov	dptr, #mesg_io_read
	lcall	pstr
	mov	a, cmd_buf+2
	anl	a, #11111011b
	lcall	cout
	anl	a, #00000011b
	jnz	exec_5c_4a_notrled
	setb	led_pin
	mov	c, led_pin
	sjmp	exec_5c_4a_read_done
exec_5c_4a_notrled:
	dec	a
	jnz	exec_5c_4a_notrbank1
	setb	bank_pin1
	mov	c, bank_pin1
	sjmp	exec_5c_4a_read_done
exec_5c_4a_notrbank1:
	dec	a
	jnz	exec_5c_4a_done
	setb	bank_pin2
	mov	c, bank_pin2
	sjmp	exec_5c_4a_read_done
exec_5c_4a_read_done:
	mov	a, #'1'		; report as active low
	jnc	exec_5c_4a_read_output
	mov	a, #'0'
exec_5c_4a_read_output:
	lcall	cout
exec_5c_4a_done:
	mov	cmd_len, #0
exec_5c_4a_end:
	ret

	;cmd 0x5c 'K'
	;control horizontal scrolling display element
	; 0x5c 'K' '0' <'0' - '7'> '0'     - set speed
	; 0x5c 'K' '1' <row+32> <font+32> - set font for row
	; 0x5c 'K' '2' <row+32> <'0'|'1'> - disable/enable row
	; 0x5c 'K' '3' <row+32> <'0'|'1'> - enable/disable buffer fill for row
exec_5c_4b:
	cjne	a, #'K', _exec_5c_4c
	mov	a, cmd_len
	cjne	a, #5, exec_5c_4a_end
	mov	a, cmd_buf+2
	cjne	a, #'0', exec_5c_4b_notspeed
	mov	a, cmd_buf+3
	anl	a, #11111000b
	cjne	a, #0x30, _exec_5c_4b_done
	mov	a, cmd_buf+3
	anl	a, #00000111b
	inc	a
	rl	a
	mov	hscroll_speed, a
	mov	hscroll_t0_count, #0
	sjmp	_exec_5c_4b_done
exec_5c_4b_notspeed:
	cjne	a, #'1', exec_5c_4b_notfont
	mov	a, cmd_buf+3
	anl	a, #11111000b
	cjne	a, #0x20, _exec_5c_4b_done
	mov	a, cmd_buf+3			;get row
	anl	a, #00000111b
	add	a, #hscroll_font0
	mov	r0, a
	mov	a, cmd_buf+4			;get font
	anl	a, #11100000b
	cjne	a, #0x20, exec_5c_4b_done
	mov	a, cmd_buf+4
	anl	a, #00011111b
	mov	@r0, a
_exec_5c_4b_done:
	sjmp	exec_5c_4b_done
_exec_5c_4c:
	sjmp	exec_5c_4c
exec_5c_4b_notfont:
	cjne	a, #'2', exec_5c_4b_notendis
	mov	a, cmd_buf+3
	anl	a, #11111000b
	cjne	a, #0x20, exec_5c_4b_done
	mov	a, cmd_buf+3			;get row
	anl	a, #00000111b
	mov	r0, a
	add	a, #hscroll_end0
	mov	r1, a
	inc	r0				;convert index to mask
	mov	a, #0
	setb	c
exec_5c_4b_font_loop:
	rlc	a
	djnz	r0, exec_5c_4b_font_loop
	mov	r0, a
	mov	a, cmd_buf+4			;get setting
	anl	a, #11111110b
	cjne	a, #0x30, exec_5c_4b_done
	clr	hscroll_fill_en			;disable fill
	mov	a, cmd_buf+4
	cjne	a, #'1', exec_5c_4b_disable
	mov	a, @r1
	jz	exec_5c_4b_done			;ignore enable if buffer empty
	mov	a, r0
	orl	hscroll_en, a
	sjmp	exec_5c_4b_done
exec_5c_4b_disable:
	mov	a, r0
	cpl	a
	anl	hscroll_en, a
	sjmp	exec_5c_4b_done
exec_5c_4b_notendis:
	cjne	a, #'3', exec_5c_4b_notfill
	mov	a, cmd_buf+3
	anl	a, #11111000b
	cjne	a, #0x20, exec_5c_4b_done
	mov	a, cmd_buf+3			;get row
	anl	a, #00000111b
	mov	hscroll_fill_row, a
	mov	a, cmd_buf+4
	cjne	a, #'1', exec_5c_4b_endfill
	setb	hscroll_fill_en
	mov	a, hscroll_fill_row
	add	a, #hscroll_end0
	mov	r0, a
	mov	@r0, #0
	add	a, #(hscroll_pos0 - hscroll_end0)
	mov	r0, a
	mov	@r0, #0
	sjmp	exec_5c_4b_done
exec_5c_4b_endfill:
	clr	hscroll_fill_en
	sjmp	exec_5c_4b_done
exec_5c_4b_notfill:
exec_5c_4b_done:
	mov	cmd_len, #0
exec_5c_4b_end:
	ret

	;cmd 0x5c 0x4c <param> 
	;clear a row ('0' <= param <= '7') of the display
exec_5c_4c:
	cjne	a, #'L', exec_5c_4d
	mov	a, cmd_len
	cjne	a, #3, exec_5c_4c_end
	mov	a, cmd_buf+2
	anl	a, #11111000b
	cjne	a, #0x20, exec_5c_4c_done
	mov	a, cmd_buf+2
	anl	a, #00000111b
	lcall	clear_row
exec_5c_4c_done:
	mov	cmd_len, #0
exec_5c_4c_end:
	ret

exec_5c_4d:

	;available for future (legacy code will ignore), 2 data bytes
exec_5c_53:
	cjne	a, #0x53, exec_5c_54
	mov	a, cmd_len
	cjne	a, #4, exec_5c_53_end
	mov	cmd_len, #0
exec_5c_53_end:
	ret

	;available for future (legacy code will ignore), 2 data bytes
exec_5c_54:
	cjne	a, #0x54, exec_5c_55
	mov	a, cmd_len
	cjne	a, #4, exec_5c_54_end
	mov	cmd_len, #0
exec_5c_54_end:
	ret

	;available for future (legacy code will ignore), 2 data bytes
exec_5c_55:
	cjne	a, #0x55, exec_5c_56
	mov	a, cmd_len
	cjne	a, #4, exec_5c_55_end
	mov	cmd_len, #0
exec_5c_55_end:
	ret

	;available for future (legacy code will ignore), 2 data bytes
exec_5c_56:
	cjne	a, #0x56, exec_5c_57
	mov	a, cmd_len
	cjne	a, #4, exec_5c_56_end
	mov	cmd_len, #0
exec_5c_56_end:
	ret

	;available for future (legacy code will ignore), 2 data bytes
exec_5c_57:
	cjne	a, #0x57, exec_5c_58
	mov	a, cmd_len
	cjne	a, #4, exec_5c_57_end
	mov	cmd_len, #0
exec_5c_57_end:
	ret

	;available for future (legacy code will ignore), 8 data bytes
exec_5c_58:
	cjne	a, #0x58, exec_5c_59
	mov	a, cmd_len
	cjne	a, #10, exec_5c_58_end
	mov	cmd_len, #0
exec_5c_58_end:
	ret

	;available for future (legacy code will ignore), 8 data bytes
exec_5c_59:
	cjne	a, #0x59, exec_5c_5a
	mov	a, cmd_len
	cjne	a, #10, exec_5c_59_end
	mov	cmd_len, #0
exec_5c_59_end:
	ret

	;available for future (legacy code will ignore), 8 data bytes
exec_5c_5a:
	cjne	a, #0x5A, exec_5c_unknown
	mov	a, cmd_len
	cjne	a, #10, exec_5c_5a_end
	mov	cmd_len, #0
exec_5c_5a_end:
	ret



exec_5c_unknown:
	lcall	phex
	mov	a, #'*'
	lcall	cout
	mov	cmd_len, #0
	ret





;***********************************************************************
;**                                                                   **
;**                           Misc Functions                          **
;**                                                                   **
;***********************************************************************




	;convert recv'd byte in Acc to a font #  (by subtracting 32)
	;C=0 if legal, C=1 if illegal
byte2font:
	add	a, #224
	jnc	b2f_nope
	add	a, #240
	jc	b2f_nope
	add	a, #16
	clr	c
	ret
b2f_nope:
	clr	a
	setb	c
	ret



        ;carry set if invalid input
asc2hex:
        add     a, #208
        jnc     hex_not
        add     a, #246
        jc      hex_maybe
        add     a, #10
        clr     c
        ret
hex_maybe:
        add     a, #249
        jnc     hex_not
        add     a, #250
        jc      hex_not
        add     a, #16
        clr     c
        ret
hex_not:setb    c
        ret


lcd_pstr:
	clr	a
	movc	a, @a+dptr
	inc	dptr
	jz	lcd_pstr_end
	push	dph
	push	dpl
	lcall	lookup_char
	lcall	cache_char
	pop	dpl
	pop	dph
	sjmp	lcd_pstr
lcd_pstr_end:
	ret




;***********************************************************************
;**                                                                   **
;**                         Cursor Movement                           **
;**                                                                   **
;***********************************************************************


move_cursor_home:
	mov	x_pos, #0
	mov	y_pos, #0
	ret


cursor_to_start_of_line:
	mov	x_pos, #0
	ret


	;move the cursor right by Acc positions
move_cursor_right:
	mov	r7, a			;keep num positions in r7
	add	a, x_pos
	add	a, #232
	jc	mov_rt_2
	add	a, #24
	mov	x_pos, a		;easy case, no wrap around
	ret
mov_rt_2:  ;the x movement puts the cursor off screen
	jb	wrap_mode, mov_rt_3
	mov	x_pos, #23		;right edge when wrap mode off
	ret
mov_rt_3:
	mov	a, #1
	lcall	move_cursor_down
	mov	a, #24
	clr	c
	subb	a, x_pos
	mov	r0, a
	mov	a, r7
	clr	c
	subb	a, r0
	mov	x_pos, #0
	sjmp	move_cursor_right


	;move the cursor left by Acc positions
move_cursor_left:
	mov	r7, a			;keep num positions in r7
	mov	a, x_pos
	clr	c
	subb	a, r7
	jc	mov_lf_2
	mov	x_pos, a		;easy case, no wrap around
	ret
mov_lf_2:  ;the x movement puts the cursor off screen
	jb	wrap_mode, mov_lf_3
	mov	x_pos, #0		;left edge when wrap mode off
	ret
mov_lf_3:
	mov	a, #1
	lcall	move_cursor_up
	mov	a, r7
	setb	c
	subb	a, x_pos
	mov	x_pos, #23
	sjmp	move_cursor_left


	;move the cursor down by Acc positions
move_cursor_down:
	mov	r6, a			;keep num positions in r6
	add	a, y_pos
	add	a, #248
	jc	mov_dn_2
	add	a, #8
	mov	y_pos, a		;easy case, didn't hit the bottom
	ret
mov_dn_2:
	jb	vscroll_mode, mov_dn_sch
	jb	wrap_mode, mov_dn_wrap
	mov	y_pos, #7		;bottom edge if no wrap or vertical scroll
	ret
mov_dn_sch:
	mov	a, #7
	clr	c
	subb	a, y_pos
	mov	r0, a
	mov	a, r6
	clr	c
	subb	a, r0
	mov	r6, a
	mov	y_pos, #7
	lcall	vscroll_normal
	dec	r6
	mov	a, r6
	jnz	move_cursor_down
	ret
mov_dn_wrap:
	mov	a, #8
	clr	c
	subb	a, y_pos
	mov	r0, a
	mov	a, r6
	clr	c
	subb	a, r0
	mov	y_pos, #0
	sjmp	move_cursor_down


	;move the cursor up by Acc positions
move_cursor_up:
	mov	r6, a			;keep num positions in r6
	mov	a, y_pos
	clr	c
	subb	a, r6
	jc	mov_up_2
	mov	y_pos, a		;easy case, didn't hit the top
	ret
mov_up_2:
	jb	vscroll_mode, mov_up_sch
	jb	wrap_mode, mov_up_wrap
	mov	y_pos, #0		;top edge if no wrap or scroll
	ret
mov_up_sch:
	mov	a, r6
	clr	c
	subb	a, y_pos
	mov	r6, a
	mov	y_pos, #0
	lcall	vscroll_reverse
	dec	r6
	mov	a, r6
	jnz	move_cursor_up
	ret
mov_up_wrap:
	mov	a, r6
	setb	c
	subb	a, y_pos
	mov	y_pos, #7
	sjmp	move_cursor_up


;**************************************************************************
;**                                                                      **
;**             LCD Display Controller (KS0108B) Routines                **
;**                                                                      **
;** Refer to                                                             **
;** http://www.usa.samsungsemi.com/products/prodspec/graflcd/ks0108b.pdf **
;**                                                                      **
;**************************************************************************
;
; Display is rotated 90 degrees WRT controller - our x is display column Y, our y, display row X
;
; Characters are formed as an 8x5 cell - Y column position is determined by 5 * x_pos
;                                      - X row position is determined using vertical scroll

draw_char:
	mov	a, x_pos
	add	a, #244 		; x < 12 then left side
	jnc	draw_char_left
	add	a, #244 		; 12 <= x < 24 then right side
	jnc	draw_char_right
	ret


draw_char_left:
        lcall	left_busy
	mov	dptr, #left_cmd_wr
	mov	a, x_pos		;find Y column address of character x_pos
	mov	b, #5
	mul	ab
	anl	a, #00111111b
	orl	a, #0x40		;set display Y address (display is rotated 90 degress)
	movx	@dptr, a

        lcall	left_busy
	mov	dptr, #left_cmd_wr
	mov	a, vscroll_offset	;find X row address for character y_pos
	swap	a
	rl	a
	add	a, y_pos
	anl	a, #00000111b
	orl	a, #0xB8		;set display X address (display is rotated 90 degrees)
	movx	@dptr, a

	;write 5 vertical slices of character
        lcall	left_busy			
	mov	dptr, #left_data_wr
	mov	a, char_buf+0
	movx	@dptr, a
	
        lcall	left_busy
	mov	dptr, #left_data_wr
	mov	a, char_buf+1
	movx	@dptr, a

        lcall	left_busy
	mov	dptr, #left_data_wr
	mov	a, char_buf+2
	movx	@dptr, a

        lcall	left_busy
	mov	dptr, #left_data_wr
	mov	a, char_buf+3
	movx	@dptr, a

        lcall	left_busy
	mov	dptr, #left_data_wr
	mov	a, char_buf+4
	movx	@dptr, a

	ret


draw_char_right:
        lcall   right_busy
	mov	dptr, #right_cmd_wr
	mov	a, x_pos		;find Y column address of character x_pos
	add	a, #244
	mov	b, #5
	mul	ab
	anl	a, #00111111b
	orl	a, #0x40		;set display Y address (display is rotated 90 degress)
	movx	@dptr, a

        lcall   right_busy
	mov	dptr, #right_cmd_wr
	mov	a, vscroll_offset	;find X row address for character y_pos
	swap	a
	rl	a
	add	a, y_pos
	anl	a, #00000111b
	orl	a, #0xB8		;set display X address (display is rotated 90 degrees)
	movx	@dptr, a

	;write 5 vertical slices of character
        lcall   right_busy
	mov	dptr, #right_data_wr
	mov	a, char_buf+0
	movx	@dptr, a
	
        lcall   right_busy
	mov	dptr, #right_data_wr
	mov	a, char_buf+1
	movx	@dptr, a

        lcall   right_busy
	mov	dptr, #right_data_wr
	mov	a, char_buf+2
	movx	@dptr, a

        lcall   right_busy
	mov	dptr, #right_data_wr
	mov	a, char_buf+3
	movx	@dptr, a

        lcall   right_busy
	mov	dptr, #right_data_wr
	mov	a, char_buf+4
	movx	@dptr, a

	ret


left_busy:
	mov	dptr, #left_cmd_rd
lbusy2i:movx	a, @dptr
	jb	acc.7, lbusy2i
	ret


right_busy:
	mov	dptr, #right_cmd_rd
rbusy2i:movx	a, @dptr
	jb	acc.7, rbusy2i
	ret

;
; clear the row specified by a
;
clear_row:
	mov	dph, #display_cache
	mov	r7, a
	swap	a
	clr	c
	rlc	a
	rlc	a
	mov	dpl, a
	mov	b, a
	mov	a, #0
	mov	r6, #60
	jnc	clear_row_next1
	inc	dph
clear_row_next1:
	movx	@dptr, a
	inc	dpl
	djnz	r6, clear_row_next1
	mov	r6, #60
	orl	dph, #2			
	mov	dpl, b
clear_row_next2:
	movx	@dptr, a
	inc	dpl
	djnz	r6, clear_row_next2
	jnb	update_mode, clear_row_done
	acall	redraw_row
clear_row_done:
	ret



redraw:
	mov	r7, #7			;row counter start at 7
redraw_next_row:
	;lcall	watchdog	; should not be needed, since T0 ISR does it
	lcall	redraw_row
	dec	r7
	cjne	r7, #0xff, redraw_next_row
	ret


	;redraw the row r7
redraw_row:
	lcall	right_busy
	mov	dptr, #right_cmd_wr
	mov	a, #0x40		;right start at first column
	movx	@dptr, a

	lcall	left_busy
	mov	dptr, #left_cmd_wr
	mov	a, #0x40		;left start at first column
	movx	@dptr, a

	lcall	right_busy
	mov	dptr, #right_cmd_wr
	mov	a, r7			;right move to current row
	orl	a, #0xb8
	movx	@dptr, a

	lcall	left_busy
	mov	dptr, #left_cmd_wr
	mov	a, r7			;left move to current row
	orl	a, #0xb8
	movx	@dptr, a

	mov	redraw_addr, #display_cache
	mov	a, r7
	swap	a
	rlc	a
	rlc	a
	anl	a, #11000000b
	mov	redraw_addr+1, a
	jnc	redraw_next_column
	inc	redraw_addr		;ripple into MSB

redraw_next_column:
	mov	dph, redraw_addr
	mov	dpl, redraw_addr+1
	orl	dph, #2			;point to right side cache
	movx	a, @dptr
	mov	b, a
	lcall	right_busy
	mov	a, b
	mov	dptr, #right_data_wr
	movx	@dptr, a
	mov	dph, redraw_addr	;point to left side cache
	mov	dpl, redraw_addr+1
	movx	a, @dptr
	inc	dptr
	mov	redraw_addr, dph
	mov	redraw_addr+1, dpl
	mov	b, a
	lcall	left_busy
	mov	a, b
	mov	dptr, #left_data_wr
	movx	@dptr, a
	mov	a, redraw_addr+1
	anl	a, #0x3f		;60 columns per row
	cjne	a, #60, redraw_next_column
	ret

vscroll_normal:
	mov	a, vscroll_offset
	add	a, #8
	anl	a, #0x3F
	mov	vscroll_offset, a
	lcall	update_vscroll_offset
	mov	r0, #7			;r0 is line # to clear
	sjmp	vscroll_clear_line

vscroll_reverse:
	mov	a, vscroll_offset
	add	a, #248
	anl	a, #0x3F
	mov	vscroll_offset, a
	lcall	update_vscroll_offset
	mov	r0, #0			;r0 is line # to clear

vscroll_clear_line:
        lcall   left_busy
	mov	dptr, #left_cmd_wr
	mov	a, #0x40
	movx	@dptr, a
        lcall   right_busy
	mov	dptr, #right_cmd_wr
	mov	a, #0x40
	movx	@dptr, a
        lcall   left_busy
	mov	dptr, #left_cmd_wr
	mov	a, vscroll_offset
	swap	a
	rl	a
	add	a, r0
	anl	a, #00000111b
	orl	a, #0xB8
	movx	@dptr, a
        lcall   right_busy
	mov	dptr, #right_cmd_wr
	mov	a, vscroll_offset
	swap	a
	rl	a
	add	a, r0
	anl	a, #00000111b
	orl	a, #0xB8
	movx	@dptr, a
	mov	r3, #64			;r3 counts horizontal position
vscroll_loop:
	lcall   left_busy
	mov	dptr, #left_data_wr
	clr	a
	movx	@dptr, a		;clear left side
	lcall	right_busy
	mov	dptr, #right_data_wr
	clr	a
	movx	@dptr, a		;clear right side
	djnz	r3, vscroll_loop
	ret


update_vscroll_offset:
	lcall	left_busy
	mov	dptr, #left_cmd_wr
	mov	a, vscroll_offset
	anl	a, #0x3F
	orl	a, #0xC0
	movx	@dptr, a		;set top of display at y=0
	lcall	right_busy
	mov	dptr, #right_cmd_wr
	mov	a, vscroll_offset
	anl	a, #0x3F
	orl	a, #0xC0
	movx	@dptr, a		;set top of display at y=0
	ret



init_display:
	mov	hscroll_en, #0
	mov	vscroll_offset, #0
	lcall	update_vscroll_offset
	lcall	clear_cached_display
	jb	update_mode, init1	;if update is enabled then update display
	ret
init1:
	mov	r2, #0			;r2 counts line number
init2:	lcall	left_busy
	mov	dptr, #left_cmd_wr
	mov	a, #0x40
	movx	@dptr, a
	lcall	right_busy
	mov	dptr, #right_cmd_wr
	mov	a, #0x40
	movx	@dptr, a
	lcall	left_busy
	mov	dptr, #left_cmd_wr
	mov	a, r2
	orl	a, #0xB8
	movx	@dptr, a
	lcall	right_busy
	mov	dptr, #right_cmd_wr
	mov	a, r2
	orl	a, #0xB8
	movx	@dptr, a
	mov	r3, #64			;r3 counts horizontal position
init3:	lcall   left_busy
	mov	dptr, #left_data_wr
	clr	a
	movx	@dptr, a		;clear left side
	lcall	right_busy
	mov	dptr, #right_data_wr
	clr	a
	movx	@dptr, a		;clear right side
	djnz	r3, init3
	inc	r2
	cjne	r2, #8, init2
	mov	lcd_on_timer, #0
display_on:
	lcall	left_busy
	mov	dptr, #left_cmd_wr
	mov	a, #0x3F
	movx	@dptr, a		;turn on display
	lcall	right_busy
	mov	dptr, #right_cmd_wr
	mov	a, #0x3F
	movx	@dptr, a		;turn on display
	clr	turn_lcd_on
        ret



;***********************************************************************
;**                                                                   **
;**                           Pushbuttons                             **
;**                                                                   **
;***********************************************************************


timer0_setup:
	clr	ea
	setb	pb_recv_1
	setb	pb_recv_2
	setb	pb_recv_3
	setb	pb_recv_4
	setb	pb_xmit_1
	setb	pb_xmit_2
	setb	pb_xmit_3
	mov	pb_scan_state, #0
	orl	tmod, #00000001b
	anl	tmod, #11110001b
	clr	tr0
	clr	tf0
	clr	a
	mov	pb_event_head, a
	mov	pb_event_tail, a
	mov	pb_state+0, a
	mov	pb_state+1, a
	mov	pb_state+2, a
	mov	pb_state+3, a
	mov	pb_state+4, a
	mov	pb_state+5, a
	mov	pb_state+6, a
	mov	pb_state+7, a
	mov	pb_state+8, a
	mov	pb_state+9, a
	mov	pb_state+10, a
	mov	pb_state+11, a
	mov	pb_config+0, a
	mov	pb_config+1, a
	mov	pb_config+2, a
	mov	pb_config+3, a
	mov	pb_config+4, a
	mov	pb_config+5, a
	mov	pb_config+6, a
	mov	pb_config+7, a
	mov	pb_config+8, a
	mov	pb_config+9, a
	mov	pb_config+10, a
	mov	pb_config+11, a

; check for clocked serial mode request
	jnb	clk_ser_req, activate_clocked_serial_mode

; normal asynchronous mode
	clr	pb_xmit_1
	clr	pt0
	sjmp	finish_timer0_setup

; prepare for clocked serial mode
activate_clocked_serial_mode:
	setb	clocked_serial_mode
	mov	serial_bit_cnt, #8
	setb	it0			; set for interrupt on falling edge of p3.2
	setb	ex0
	setb	px0			

finish_timer0_setup:
	setb	et0
	setb	ea
	setb	tr0
	lcall	copy_strings_to_sram
	ret

;--------------------------------------------------------------------
; This ISR will be called when a falling edge is detected on the
; serial_clock input.  It is assumed that the interfacing device is
; shifting out 8 bits of data, MSB first, that is valid for some period
; of time after the falling edge.  The measured latency between the
; triggering edge and completion of sampling the serial data (using
; the "acknowledge" pulse code below) ranges from 10us to 14us.  It
; is believed that a 20us minimum serial clock period would provide
; reliable operation.
extern0_isr:
	push	psw

	; sample the serial data line as early as possible
	mov	c, serial_data

; debugging code: uncomment to generate an "acknowledge" pulse
	setb	serial_data_rx
	nop
	nop
	clr	serial_data_rx
; end of debugging code

	push	acc
	push	reg0

	; shift the bit into our accumulator and decrement the bit counter
	mov	a, serial_acc
	rlc	a
	mov	serial_acc, a
	djnz	serial_bit_cnt, extern0_isr_done

	; a complete byte has been received, transfer to receive buffer
	mov     a, rx_buf_head
	inc     a
	cjne    a, rx_buf_tail, store_received_byte

        ;If we got here, the recv buffer is full, so we just
        ;discard this character.  Maybe there should be an overrun
        ;flag so the main program can know some data was lost...
	sjmp	ready_next_char

store_received_byte:
	mov     rx_buf_head, a  ;update rx_buf_head while it's still in Acc.
	push	dpl
	push	dph
	mov	dph, #rx_buf_page
	mov	dpl, a
	mov	a, serial_acc	;get the incoming data
	movx	@dptr, a	;and put it in the buffer
	pop	dph
	pop	dpl

ready_next_char:
	; prepare for next byte
	mov	serial_bit_cnt, #8

extern0_isr_done:
	pop	reg0
	pop	acc
	pop	psw
	reti

;--------------------------------------------------------------------


timer0_isr:
	push	psw
	push	acc
	push	reg0
	push	reg1
	clr	tr0
	mov	th0, #0xF4		;run this code at approx 300 Hz
	mov	tl0, #0x12
	setb	tr0
	jnb	cin_serviced, t0_lcd_on
	;clear the watchdog timer, but only if the main program has been
	;running "cin" (accepting user input) and of course this timer
	;interrupt routine is still working.
	lcall	watchdog
	clr	cin_serviced
t0_lcd_on:
	inc	lcd_on_timer
	mov	a, lcd_on_timer
	cjne	a, #12, pb_row_1
	mov	lcd_on_timer, #0
	setb	turn_lcd_on
t0_hscroll:
	mov	a, hscroll_en
	jz	pb_row_1
	inc	hscroll_t0_count
	mov	a, hscroll_t0_count
	clr	c
	subb	a, hscroll_speed
	jnz	pb_row_1
	mov	hscroll_t0_count, #0
	setb	hscroll_redraw
pb_row_1:
	jb	clocked_serial_mode, timer0_isr_done		; no buttons in clocked serial mode
	mov	a, pb_scan_state
	cjne	a, #0, pb_row_2
	mov	c, pb_recv_1
	mov	r0, #pb_state+10
	mov	r1, #pb_config+10
	lcall	pushbutton
	mov	c, pb_recv_2
	mov	r0, #pb_state+4
	mov	r1, #pb_config+4
	lcall	pushbutton
	mov	c, pb_recv_3
	mov	r0, #pb_state+1
	mov	r1, #pb_config+1
	lcall	pushbutton
	mov	c, pb_recv_4
	mov	r0, #pb_state+0
	mov	r1, #pb_config+0
	lcall	pushbutton
	mov	pb_scan_state, #1
	setb	pb_xmit_1
	clr	pb_xmit_2
	setb	pb_xmit_3
timer0_isr_done:
	pop	reg1
	pop	reg0
	pop	acc
	pop	psw
	reti
pb_row_2:
	cjne	a, #1, pb_row_3
	mov	c, pb_recv_1
	mov	r0, #pb_state+6
	mov	r1, #pb_config+6
	lcall	pushbutton
	mov	c, pb_recv_2
	mov	r0, #pb_state+5
	mov	r1, #pb_config+5
	lcall	pushbutton
	mov	c, pb_recv_3
	mov	r0, #pb_state+8
	mov	r1, #pb_config+8
	lcall	pushbutton
	mov	c, pb_recv_4
	mov	r0, #pb_state+9
	mov	r1, #pb_config+9
	lcall	pushbutton
	mov	pb_scan_state, #2
	setb	pb_xmit_1
	setb	pb_xmit_2
	clr	pb_xmit_3
	pop	reg1
	pop	reg0
	pop	acc
	pop	psw
	reti
pb_row_3:
	mov	c, pb_recv_1
	mov	r0, #pb_state+3
	mov	r1, #pb_config+3
	lcall	pushbutton
	mov	c, pb_recv_2
	mov	r0, #pb_state+2
	mov	r1, #pb_config+2
	lcall	pushbutton
	mov	c, pb_recv_3
	mov	r0, #pb_state+7
	mov	r1, #pb_config+7
	lcall	pushbutton
	mov	c, pb_recv_4
	mov	r0, #pb_state+11
	mov	r1, #pb_config+11
	lcall	pushbutton
	mov	pb_scan_state, #0
	clr	pb_xmit_1
	setb	pb_xmit_2
	setb	pb_xmit_3
	pop	reg1
	pop	reg0
	pop	acc
	pop	psw
	reti



	;Let's be really paranoid, and only reset the watchdog if some
	;sanity checks are valid.  If any of these checks fail, something
	;has done horribly wrong and we must reboot.
	;Checks: uart and timer1 confired, stack pointer is legal
watchdog:
	mov	a, th1
	cjne	a, #baud_const, reboot
	mov	a, tmod
	cjne	a, #0x21, reboot
	mov	a, tcon
	anl	a, #01010000b
	cjne	a, #0x50, reboot
	mov	a, scon
	anl	a, #11110000b
	cjne	a, #0x50, reboot
	mov	a, pcon
	jnb	acc.7, reboot
	mov	a, sp
	add	a, #256 - stack
	jnc	reboot
	cpl	watchdog_pin
	ret


reboot:
	ljmp	location
retinow:
	reti



	.equ	event_dn, 0x10
	.equ	event_up, 0x20
	.equ	event_ar, 0x30


	;C = pushbutton state
	;r0 = ptr to pb_state
	;r1 = ptr to pb_config
pushbutton:
	jc	pb_up
pb_dn:			;the button is being pressed right now!
	mov	a, @r0
	jz	pb_dn_wait
	dec	a
	jnz	pb_ar
	mov	@r0, #2
	mov	a, r0
	add	a, #(event_dn - pb_state) & 255
	sjmp	emit_pb_event
pb_dn_wait:
	mov	@r0, #1
	ret
pb_ar:			;the button has been down for a while
	inc	@r0
	mov	a, @r1
	anl	a, #3
	add	a, #2
	movc	a, @a+pc	;grab auto-repeat threshold
	sjmp	pb_ar2
	.db	120, 90, 65, 43
pb_ar2:	clr	c
	subb	a, @r0
	jnc	pb_end		;did we hit the auto-repeat threshold?
	mov	a, @r1
	rr	a
	rr	a
	anl	a, #7
	add	a, #2
	movc	a, @a+pc	;grab auto-repeat speed
	sjmp	pb_ar3
	.db	40, 34, 29, 23, 18, 15, 12, 8
pb_ar3:	cpl	a
	inc	a
	add	a, @r0
	mov	@r0, a
	mov	a, r0
	add	a, #(event_ar - pb_state) & 255
	sjmp	emit_pb_event
pb_up:			;the button is not presssed
	mov	a, @r0
	jz	pb_end
	dec	a
	jz	pb_up_ignore
	mov	@r0, #0
	mov	a, r0
	add	a, #(event_up - pb_state) & 255
	sjmp	emit_pb_event
pb_up_ignore:
	mov	@r0, #0
pb_end:
	ret



emit_pb_event:
	mov	r0, a
	mov	a, pb_event_head
	inc	a
	cjne	a, pb_event_tail, evt_ok
	ret			;event queue is full!  discard event
evt_ok:	mov	pb_event_head, a
	push	dpl
	mov	r1, dph
	mov	dph, #pb_events_page
	mov	dpl, a
	mov	a, r0
        movx	@dptr, a	;put event into the event queue
	mov	dph, r1
	pop	dpl
	ret



	;get the next event from the event queue, Acc=0 for none
get_event:
	clr     et0
        mov     a, pb_event_tail
        cjne    a, pb_event_head, getevt2	;check if buffer empty
        setb    et0
	clr	a
	ret					;no pending events
getevt2:inc     a
	mov     pb_event_tail, a
	mov	dpl, a
	mov	dph, #pb_events_page
	movx	a, @dptr			;read event from queue
	setb	et0
        ret


print_event:
	 ;push	acc
	 ;lcall	phex
	 ;mov	a, #'-'
	 ;lcall	cout
	 ;pop	acc
	acall	event2addr
	mov	r2, #14
	movx	a, @dptr
	jz	pr_evt_done		;don't print anything if null string
pr_evt_loop:
	movx	a, @dptr
	jz	pr_evt_end
	lcall	cout
	inc	dptr
	djnz	r2, pr_evt_loop
pr_evt_end:
	lcall	newline
pr_evt_done:
	ret



event2addr:
	mov	r2, a				;store the event in r2
	anl	a, #15
	add	a, #244
	jc	illegal_event_addr
	mov	a, r2
	swap	a
	anl	a, #15
	dec	a
	mov	r3, a
	add	a, #253
	jc	illegal_event_addr
	mov	a, r2
	anl	a, #15
	mov	b, #3
	mul	ab
	add	a, r3
	mov	b, #14				;strlen max of 14 bytes
	mul	ab
	mov	dpl, a
	mov	a, b
	add	a, #pb_strings_page
	mov	dph, a
	clr	c
	ret
illegal_event_addr:
	mov	dpl, #248
	mov	dph, #pb_strings_page+1
	setb	c
	ret




	


;***********************************************************************
;**                                                                   **
;**                       Serial Port Routines                        **
;**                                                                   **
;***********************************************************************


init_uart_intr:
	clr	ea
        orl     pcon, #10000000b	;set double baud rate
	anl	tmod, #00001111b	;clear all timer1 bits in tmod
	orl	tmod, #00100000b	;set timer1 as 8 bit auto reload
	clr	tr1			;make sure timer1 isn't running
	clr	tf1
        mov     a, #baud_const
        mov     th1, a			;set timer1 rate
        mov     tl1, a
	mov	scon, #01010000b	;config serial port (ri and ti cleared)
        mov     rx_buf_head, #0		;set up empty receive buffer
        mov     rx_buf_tail, #0
        mov     tx_buf_head, #0		;set up transmit buffer as empty
        mov     tx_buf_tail, #0
	clr	tx_intr_expected	;and no tx interrupt expected
	setb	tr1			;start timer1
        setb    es			;enable serial port interrupt
        setb    ea			;also turn on interrupts in general
        ret


;service interrupts generated by the uart... put a received character
;into the receive buffer or get a character from the transmit buffer
;and send it.

uart_intr:
        push    psw
        push    acc
        jb      ti, xmit

recv:   clr     ri
        mov     a, rx_buf_head
        inc     a
	cjne    a, rx_buf_tail, recv_ok         ;check for buffer full
        ;If we got here, the recv buffer is full, so we just
        ;discard this character.  Maybe there should be an overrun
        ;flag so the main program can know some data was lost...
        mov     a, sbuf
        pop     acc
        pop     psw
        reti
recv_ok:mov     rx_buf_head, a  ;update rx_buf_head while it's still in Acc.
	push	dpl
	push	dph
	mov	dph, #rx_buf_page
	mov	dpl, a
        mov	a, sbuf		;get the incoming data
        movx	@dptr, a	;and put it in the buffer
	pop	dph
	pop	dpl
        pop     acc
        pop     psw
        reti

xmit:   clr     ti
        mov     a, tx_buf_tail
        cjne    a, tx_buf_head, xmit_ok		;check for buffer empty
        ;If we got here, there is no data waiting to transmit.  The
        ;uart won't generate more interrupts, so the cout routine had
        ;better check set ti.  tx_buf_head is loaded with zero to that
        ;cout will know to set ti.
	clr	tx_intr_expected
        pop     acc
        pop     psw
        reti
xmit_ok:inc     a
	mov     tx_buf_tail, a  ;update tx_buf_tail while it's still in Acc.
	push	dpl
	push	dph
	mov	dph, #tx_buf_page
	mov	dpl, a
	movx	a, @dptr
	mov	sbuf, a
	pop	dph
	pop	dpl
	setb	tx_intr_expected
        pop     acc
        pop     psw
        reti


;Get a character from the receive buffer.  C=0 if char recv, C=1 if none

cin:	setb	cin_serviced
	clr     es
        mov     a, rx_buf_tail
        cjne    a, rx_buf_head, cin_ok	;check if buffer empty
        setb    es
	setb	c
	ret
cin_ok:	inc     a
	mov     rx_buf_tail, a
	push	dpl
	push	dph
	mov	dpl, a
	mov	dph, #rx_buf_page
	movx	a, @dptr
	pop	dph
	pop	dpl
	setb	es
	clr	c
        ret

;Put the character in Acc into the transmit buffer.  If the buffer is
;full, wait until these is a space to put it.

cout:
        push    psw
        xch     a, r0                           ;put char to send into r0
        push    acc                             ;keep r0 on stack
cout_wt:clr     es
	jb	tx_intr_expected, cout2		;check if not transmitting
        setb    ti              ;set ti in software since buffer is empty
	clr	a
        mov     tx_buf_head, a
        mov     tx_buf_tail, a
	setb	tx_intr_expected
cout2:	mov	a, tx_buf_head
	inc     a
	cjne    a, tx_buf_tail, cout4           ;check if buffer full
        setb    es
        nop                                     ;wait for space in buffer
        sjmp    cout_wt
cout4:  mov     tx_buf_head, a                  ;update tx_buf_head
	push	dph
	push	dpl
	mov	dph, #tx_buf_page
	mov	dpl, a
	xch	a, r0
	movx	@dptr, a
	pop	dpl
	pop	dph
        setb    es
        xch     a, r0                           ;restore r0 value and Acc
        pop     acc
        xch     a, r0
        pop     psw
        ret



; Highly code efficient resursive call phex contributed
; by Alexander B. Alexandrov <abalex@cbr.spb.ru>

phex:
phex8:
        acall   phex_b
phex_b: swap    a               ;SWAP A will be twice => A unchanged
phex1:  push    acc
        anl     a, #15
        add     a, #0x90        ; acc is 0x9X, where X is hex digit
        da      a               ; if A to F, C=1 and lower four bits are 0..5
        addc    a, #0x40
        da      a
        lcall   cout
        pop     acc
        ret


newline:push    acc             ;print one newline
        mov     a, #13
        lcall   cout
        mov     a, #10
        lcall   cout
        pop     acc
        ret

pstr:	clr	a
	movc	a, @a+dptr
	inc	dptr
	jz	pstr2
	lcall	cout
	sjmp	pstr
pstr2:	ret


;***********************************************************************
;**                                                                   **
;**                   Fonts and Font Manipulation                     **
;**                                                                   **
;***********************************************************************


	;use the character in Acc and "font" to lookup the 5 byte
	;data to draw a character, and copy it into "char_buf".
lookup_char:
	push	acc
	mov	b, #5
	mul	ab
	mov	dpl, a		;compute offset within font
	mov	dph, b
	mov	a, font
	mov	b, #5
	mul	ab		;compute font's location
	add	a, #fonts_start_page
	add	a, dph
	mov	dph, a		;dptr points to the character we need
	movx	a, @dptr
	mov	char_buf+0, a
	inc	dptr
	movx	a, @dptr
	mov	char_buf+1, a
	inc	dptr
	movx	a, @dptr
	mov	char_buf+2, a
	inc	dptr
	movx	a, @dptr
	mov	char_buf+3, a
	inc	dptr
	movx	a, @dptr
	mov	char_buf+4, a
	pop	acc
	ret



test_font:
	;r6 has the first character to draw
	push	mode_bits
	clr	vscroll_mode
	clr	wrap_mode
	lcall	init_display
	mov	r4, #0
tfont2:
	mov	r5, #16
	mov	x_pos, #8
	mov	y_pos, r4
tfont3:
	mov	a, r6
	push	acc
	lcall	lookup_char
	lcall	cache_char
	pop	acc
	mov	r6, a
	inc	r6
	djnz	r5, tfont3
	inc	r4
	cjne	r4, #8, tfont2
	pop	mode_bits
	ret

;**************************************************************************
;**                                                                      **
;**         Character Caching Routines                                    **
;**                                                                      **
;**************************************************************************

cache_char:
	jb	hscroll_fill_en, hscroll_fill
	mov	a, y_pos		;ignore updates to scrolling rows
	lcall	check_hscroll_en
	jnc	cache_char_ok
	ret
cache_char_ok:
	jnb	update_mode, cache_char_only
	lcall	draw_char
cache_char_only:
	mov	a, x_pos
	add	a, #244
	jnc	cache_char_left
	add	a, #244
	jnc	cache_char_right
	ret

cache_char_right:
	subb	a, #244
	mov	dph, #display_cache+2
	sjmp	cache_display_cont
cache_char_left:
	mov	dph, #display_cache
	mov	a, x_pos
cache_display_cont:
	mov	b, #5			;x_pos*5 to give column address
	mul	ab
	anl	a, #00111111b
	mov	dpl, a
	mov	a, vscroll_offset	;calculate row address
	swap	a
	rl	a
	add	a, y_pos
	anl	a, #00000111b
	swap	a			;mult row address * 64
	clr	c
	rlc	a
	clr	c
	rlc	a
	jnc	cache_char_noovr	;handle overflow into dph
	inc	dph
cache_char_noovr:
	orl	a, dpl
	mov	dpl, a

	;write 5 vertical slices of character
	mov	a, char_buf+0
	movx	@dptr, a
	inc	dptr	
	mov	a, char_buf+1
	movx	@dptr, a
	inc	dptr
	mov	a, char_buf+2
	movx	@dptr, a
	inc	dptr
	mov	a, char_buf+3
	movx	@dptr, a
	inc	dptr
	mov	a, char_buf+4
	movx	@dptr, a

	mov	a, #1
	lcall	move_cursor_right
	ret

hscroll_fill:
	mov	a, hscroll_fill_row
	orl	a, #hscroll_buffer
	mov	dph, a
	anl	a, #00000111b
	add	a, #hscroll_end0
	mov	r0, a
	mov	dpl, @r0
	mov	a, r2
	movx	@dptr, a
	inc	@r0
	ret


clear_cached_display:
	mov	dph, #display_cache
	mov	dpl, #0
	clr	a
clear_cached_display_loop:
	movx	@dptr, a
	inc	dptr
	mov	r0, dph
	cjne	r0, #display_cache+4, clear_cached_display_loop
	ret


copy_fonts_to_sram:
	mov	dptr, #fonts	;dptr = source address
	mov	r0, #0		;r0/r1 = destination address
	mov	r1, #fonts_start_page
cpy_fonts_loop1:
	mov	p2, r1
cpy_fonts_loop2:
	clr	a
	movc	a, @a+dptr
	inc	dptr
	movx	@r0, a
	inc	r0
	cjne	r0, #0, cpy_fonts_loop2
	inc	r1
	cjne	r1, #fonts_start_page + (num_rom_fonts * 5), cpy_fonts_loop1
	mov	p2, #255
	ret

copy_strings_to_sram:
	mov	dptr, #default_event_strings	;dptr = source address
	mov	r0, #0				;r0/r1 = destination address
	mov	r1, #pb_strings_page
cpy_str_loop1:
	mov	p2, r1
cpy_str_loop2:
	clr	a
	movc	a, @a+dptr
	inc	dptr
	movx	@r0, a
	inc	r0
	cjne	r0, #0, cpy_str_loop2
	inc	r1
	cjne	r1, #pb_strings_page + 2, cpy_str_loop1
	mov	p2, #255
	ret

	



msg_font:
	.db	"Font #",0
msg_f0:
	.db	"0 to",0
	.db	"127",0
msg_f128:
	.db	"128 to",0
	.db	"255",0


mesg_abort:
	.db	"Press ESC twice to return to PAULMON2",13,10,13,10,0

mesg_version:
	.db	"VERSION",0

mesg_io_read:
	.db	"IOREAD",0

default_event_strings:
	.db	"b0_down",0,0,0,0,0,0,0
	.db	"b0_up",0,0,0,0,0,0,0,0,0
	.db	"b0_repeat",0,0,0,0,0
	.db	"b1_down",0,0,0,0,0,0,0
	.db	"b1_up",0,0,0,0,0,0,0,0,0
	.db	"b1_repeat",0,0,0,0,0
	.db	"b2_down",0,0,0,0,0,0,0
	.db	"b2_up",0,0,0,0,0,0,0,0,0
	.db	"b2_repeat",0,0,0,0,0
	.db	"b3_down",0,0,0,0,0,0,0
	.db	"b3_up",0,0,0,0,0,0,0,0,0
	.db	"b3_repeat",0,0,0,0,0
	.db	"b4_down",0,0,0,0,0,0,0
	.db	"b4_up",0,0,0,0,0,0,0,0,0
	.db	"b4_repeat",0,0,0,0,0
	.db	"b5_down",0,0,0,0,0,0,0
	.db	"b5_up",0,0,0,0,0,0,0,0,0
	.db	"b5_repeat",0,0,0,0,0
	.db	"b6_down",0,0,0,0,0,0,0
	.db	"b6_up",0,0,0,0,0,0,0,0,0
	.db	"b6_repeat",0,0,0,0,0
	.db	"b7_down",0,0,0,0,0,0,0
	.db	"b7_up",0,0,0,0,0,0,0,0,0
	.db	"b7_repeat",0,0,0,0,0
	.db	"b8_down",0,0,0,0,0,0,0
	.db	"b8_up",0,0,0,0,0,0,0,0,0
	.db	"b8_repeat",0,0,0,0,0
	.db	"b9_down",0,0,0,0,0,0,0
	.db	"b9_up",0,0,0,0,0,0,0,0,0
	.db	"b9_repeat",0,0,0,0,0
	.db	"b10_down",0,0,0,0,0,0
	.db	"b10_up",0,0,0,0,0,0,0,0
	.db	"b10_repeat",0,0,0,0
	.db	"b11_down",0,0,0,0,0,0
	.db	"b11_up",0,0,0,0,0,0,0,0
	.db	"b11_repeat",0,0,0,0
	.db	"error",0,0,0



fonts:
	; this must be the last label of the file, because the build
	; process in the makefile will add the fonts after this point

