;
code_base:
;
init_entry:
	LJMP	init_driver
;
;	call vector 0x003H:	ALLOCATE_CELL
;
ac_entry:			; ALLOCATE CELL to be called from BASIC
	LJMP	cl_ac
;
;	call vector 0x006H:	TEST_QUEUE <TOS>
;
tq_entry:			; TEST QUEUE to be called from BASIC
	LJMP	cl_tq
;
;	call vector 0x009H:	PUT_QUEUE
;
pq_entry:			; PUT QUEUE to be called from BASIC
	LJMP	cl_pq
;
;	call vector 0x00CH:	GET_QUEUE
;
gq_entry:			; GET QUEUE to be called from BASIC
	LJMP	cl_gq
;
;	call vector 0x00FH:	the TASKER
;
tk_entry:
	LJMP	cl_tasker	; TASKER to be called from BASIC
;
; ---------------------------------------------------------------
;
	ORG	code_base+030H
;
; ---------------------------------------------------------------
;
cl_tq:
	anl	psw,#11100111B
	mov	a,#1
	lcall	030h		; pop queue number parameter
	mov	a,r1
	lcall	setq
	lcall	tstq		; check if queue contains data
	mov	r0,a
	mov	r2,#0
	mov	a,#154
	lcall	030h		; push result on argument stack
	ret
;
; ---------------------------------------------------------------
;
cl_gq:
	anl	psw,#11100111B
	mov	a,#1
	lcall	030h		; pop queue number
	mov	a,r1
	lcall	setq
	lcall	remqh
	mov	r0,dpl
	mov	r2,dph
	mov	a,#154
	lcall	030H		; push cell pointer
	ret
;
; ---------------------------------------------------------------
;
cl_ac:
	anl	psw,#11100111B
	lcall	alloc_cell
	mov	r0,dpl
	mov	r2,dph
	mov	a,#154
	lcall	030H		; push cell pointer
	ret	
;
; ---------------------------------------------------------------
;
	using	0
;    
cl_pq:
	anl	psw,#11100111B
	mov	a,#1
	lcall	030h		; pop queue number parameter
	mov	a,r1
	lcall	setq
	push	ar1
	push	ar3		; save QCB pointer
	mov	a,#1
	lcall	030h		; pop cell pointer
	mov	dpl,r1
	mov	dph,r3
	pop	ar3
	pop	ar1
	lcall	insqt		; insert cell in queue
	ret	
;
; ---------------------------------------------------------------
;
; FUNCTION:	LDIR
; INPUT:	R0/R2 - destination pointer
;		R1/R3 - source pointer
;		R4    - byte count 
; DESTORYS:	R1,R3,R4 of bank 0
; ACTION:	copy <R4> bytes from <R1/R3> to <R0/2>
;
	USING	0
ldir:
	push	ACC
	push	AR0
	push	AR2
ldirl:
	mov	dpl,r1
	mov	dph,r3
	movx	a,@dptr
	inc	dptr
	mov	r1,dpl
	mov	r3,dph
	mov	dpl,r0
	mov	dph,r2
	movx	@dptr,a
	inc	dptr
	mov	r0,dpl
	mov	r2,dph
	djnz	r4,ldirl
	pop	AR2
	pop	AR0
	pop	ACC
	ret
;
; ---------------------------------------------------------------
;
;  task control table
;  
;  
;  key/sensor assignment:
;  
;  0/0 = 040H ... Vorzimmer9 ... EssZiTür ... links
;  0/1 = 041H ... Vorzimmer9 ... EssZiTür ... rechts
;  0/2 = 042H ... Vorzimmer9 ... WCTür ...... links
;  0/3 = 043H ... Vorzimmer9 ... WCTür ...... rechts
;  0/4 = 044H ... Vorzimmer9 ... BadTür ..... links
;  0/5 = 045H ... Vorzimmer9 ... BadTür ..... rechts
;  0/6 = 046H ... Vorzimmer9 ... EingTür .... links
;  0/7 = 047H ... Vorzimmer9 ... EingTür .... rechts
;  
;  1/0 = 048H/008H ... BI-relay-return 0 
;  1/1 = 049H/009H ... BI-relay-return 1
;  1/2 = 04AH/00AH ... BI-relay-return 2
;  1/3 = 04BH/00BH ... BI-relay-return 3
;  1/4 = 04CH/00CH ... BI-relay-return 4
;  1/5 = 04DH/00DH ... BI-relay-return 5
;  1/6 = 04EH/00EH ... BI-relay-return 6
;  1/7 = 04FH/00FH ... BI-relay-return 7
;  
;  4/0 = 060H ... Esszimmer9 ... VorZiTür ... linksOben  
;  4/1 = 061H ... Esszimmer9 ... VorZiTür ... rechtsOben 
;  4/2 = 062H ... Esszimmer9 ... VorZiTür ... linksUnten 
;  4/3 = 063H ... Esszimmer9 ... VorZiTür ... rechtsUnten
;  5/0 = 068H ... Esszimmer9 ... ArbZiTür ... linksOben  
;  5/1 = 069H ... Esszimmer9 ... ArbZiTür ... rechtsOben 
;  5/2 = 06AH ... Esszimmer9 ... ArbZiTür ... linksUnten 
;  5/3 = 06BH ... Esszimmer9 ... ArbZiTür ... rechtsUnten
;  6/0 = 070H ... Esszimmer9 ... InBar ...... links
;  6/1 = 071H ... Esszimmer9 ... InBar ...... linksMitte
;  6/2 = 072H ... Esszimmer9 ... InBar ...... rechtsMitte
;  6/3 = 073H ... Esszimmer9 ... InBar ...... rechts
;  7/0 = 078H ... Esszimmer9 ... LoggiaTür .. linksOben  
;  7/1 = 079H ... Esszimmer9 ... LoggiaTür .. rechtsOben 
;  7/2 = 07AH ... Esszimmer9 ... LoggiaTür .. linksUnten 
;  7/3 = 07BH ... Esszimmer9 ... LoggiaTür .. rechtsUnten
;  
;  
;  relay/driver assignment
;  
;  MO-relay 00 +
;  MO-relay 01 ... BI-relay 00 ... Vorzimmer9 ... Spiegellampe
;  MO-relay 02 +
;  MO-relay 03 ... BI-relay 01 ... Bad9 ......... Waschtischlampe
;  MO-relay 04 +
;  MO-relay 05 ... BI-relay 02 ... Bad9 ......... Deckenlampe
;  MO-relay 06 +
;  MO-relay 07 ... BI-relay 03 ... Bad9 ......... Lüfter
;  MO-relay 08 +
;  MO-relay 09 ... BI-relay 04 ... Vorzimmer9 ... Deckenlampen
;  MO-relay 10 +
;  MO-relay 11 ... BI-relay 05 ... 
;  MO-relay 12 +
;  MO-relay 13 ... BI-relay 06 ... 
;  MO-relay 14 +
;  MO-relay 15 ... BI-relay 07 ... 
;  MO-relay 16 ................... Kartentisch
;  MO-relay 17 ................... Esstisch
;  MO-relay 18 ................... Bar
;  MO-relay 19 ................... Lichtsäule
;  MO-relay 20 +
;  MO-relay 21 ... BI-relay 10 ...
;  MO-relay 22 +
;  MO-relay 23 ... BI-relay 11 ...
;  MO-relay 24 +
;  MO-relay 25 ... BI-relay 12 ...
;  MO-relay 26 +
;  MO-relay 27 ... BI-relay 13 ...
;  MO-relay 28 +
;  MO-relay 29 ... BI-relay 14 ...
;  MO-relay 30 +
;  MO-relay 31 ... BI-relay 15 ...
;
;
def_task_table:
	DB	1,040H,3,0,  1,041H,3,0
	DB	1,042H,3,1,  1,043H,3,1
	DB	1,044H,3,4,  1,045H,3,5
	DB	1,046H,3,0,  1,047H,3,0
	DB	1,048H,9,0,  1,008H,10,0, 1,049H,9,1,  1,009H,10,1
	DB	1,060H,6,16, 1,061H,6,17, 1,062H,6,18, 1,063H,6,19
	DB	1,068H,6,16, 1,069H,6,17, 1,06AH,6,18, 1,06BH,6,19
	DB	1,070H,6,16, 1,071H,6,17, 1,072H,6,18, 1,073H,6,19
	DB	1,078H,6,16, 1,079H,6,17, 1,07AH,6,18, 1,07BH,6,19
	DB	0,0,0,0
;
; ---------------------------------------------------------------
;
	USING	0
;
cl_tasker:
ctskloop:
	jnb	RI,ctsktq
	mov	a,SBUF
	clr	RI
	clr	acc.7		; reset MSB
	lcall	ctsk_pget
	jz	ctskloop	;=end not requested
	mov	r2,#083H
	sjmp	ctskret2
ctskret:
	mov	r2,#0
ctskret2:
	mov	r0,a
	mov	a,#09AH
	lcall	030H	; push code onto ARG stack
	ret		; return to BASIC
ctsktq:
	mov	a,#0
	lcall	setq
	lcall	tstq	; event in queue 0 ?
	jz	ctskloop
	lcall	remqh	; get event cell
	jz	ctskloop
	inc	dptr
	inc	dptr
	inc	dptr
	inc	dptr	; point to CELL.USE
	push	dpl
	push	dph
	inc	dptr	; point to CELL.CODE
	mov	r1,dpl
	mov	r3,dph
	mov	r0,#LOW(task_evt)
	mov	r2,#HIGH(task_evt)
	mov	r4,#(cell_datalen+3)
	lcall	ldir	; copy data from cell
	pop	dph
	pop	dpl
	clr	a
	movx	@dptr,a	; mark cell as unused again
	lcall	ctsk_gact	; get action for event
	mov	r2,#082H
	jz	ctskret2
	lcall	ctsk_disp	; dispatch the action
	mov	r2,#081H
	jnz	ctskret2	; ACC<>0 => return to basic
	ljmp	ctskloop
;
; ---------------------------------------------------------------
;
ctsk_pget:
	ret	
;
; ---------------------------------------------------------------
;
	USING	0
;
ctsk_gact:
	mov	dptr,#task_evt
	movx	a,@dptr
	mov	r3,a
	inc	dptr
	movx	a,@dptr
	mov	r1,a
	mov	a,#098H
	lcall	030H
	mov	r5,#'>'
	mov	a,#080H
	lcall	030H
	mov	dptr,#task_evt
	movx	a,@dptr
	mov	r2,a		; event
	inc	dptr
	movx	a,@dptr
	mov	r0,a		; subevent
	mov	dptr,#task_tblptr
	movx	a,@dptr
	mov	r1,a
	inc	dptr
	movx	a,@dptr
	mov	dph,a
	mov	dpl,r1		; point to task control table
cgac_loop:
	movx	a,@dptr
	mov	r4,a		; AENT.EVT
	inc	dptr
	movx	a,@dptr
	mov	r5,a		; AEN.SEV
	inc	dptr
	movx	a,@dptr
	mov	r6,a		; AEN.ACT
	inc	dptr		; point to <AEN>.SAC
	movx	a,@dptr
	mov	r7,a		; AEN.SAC
	inc	dptr		; point to <AEN.NEXT>.EVT
	mov	a,r4
	jz	cgac_done	; AEN.EVT=0 marks end of table
	cjne	a,ar2,cgac_loop	; event does not match => try next
	mov	a,r5
	cjne	a,ar0,cgac_loop	; subevent does not match => try next
cgac_done:
	mov	dptr,#task_act
	mov	a,r6
	movx	@dptr,a		; save ACTION
	inc	dptr
	mov	a,r7
	movx	@dptr,a		; save SUB-ACTION
	mov	a,r6		; return ACTION in ACC
	push	acc
	mov	r1,ar7
	mov	r3,ar6
	mov	a,#098H
	lcall	030H
	mov	a,#07H
	lcall	030H
	pop	acc
	ret
;
; ---------------------------------------------------------------
;
ctsk_disp:
	mov	dptr,#task_act
	movx	a,@dptr		; get action
	mov	r0,a		; save in R0
	inc	dptr
	movx	a,@dptr		; get sub-action
	mov	r2,a		; save in R2
	mov	a,r0
	anl	a,#0F0H		; select action class
	cjne	a,#000H,ctsd_n0
; action class 0, send direct event to pulser queue 1
	mov	r4,#1
ctsd_send:
	lcall	alloc_cell	; get a cell	
	push	dpl
	push	dph
	inc	dptr		; ->NXT1
	inc	dptr		; ->PRV0
	inc	dptr		; ->PRV1
	inc	dptr		; ->USE
	inc	dptr		; ->CODE
	mov	a,r0
	movx	@dptr,a		; set event
	inc	dptr
	mov	a,r2
	movx	@dptr,a		; set subevent
	mov	a,r4
	lcall	setq
	pop	dph
	pop	dpl
	lcall	insqt		; set event to queue <R4>
ctsd_ret0:
	clr	a
	ret
ctsd_n0:
	clr	a
	ret
;
; ---------------------------------------------------------------
;
; FUNCTION:	init_cellpool
; ACTION:	initializes cell pool by marking all
;               cells in pool as unused (USE=0)
;
init_cellpool:
	mov	dptr,#cellpool+cell_use
	mov	r3,#cellpoolnum
incploop:
	clr	a
	movx	@dptr,a
	mov	a,dpl
	add	a,#cell_size
	mov	dpl,a
	mov	a,dph
	addc	a,#0
	mov	dph,a
	djnz	r3,incploop
	ret
;
; ---------------------------------------------------------------
;
init_queues:
	mov	dptr,#cellqueues
	mov	r3,#cque_num*cque_cbsize
icq_loop:
	clr	a
	movx	@dptr,a
	inc	dptr
	djnz	r3,icq_loop
	ret
;
; ---------------------------------------------------------------
;
; FUNCTION:	init_driver
;
init_driver:
	push	psw
	orl	psw,#00011000B	; select RB3
;
; place timer interrupt jump to location 0400BH
;
	mov	dptr,#0400BH
	mov	a,#002H		; 'LJMP' instruction
	movx	@dptr,a
	inc	dptr
	mov	a,#HIGH(timer_0_int)	; high address byte of timer interrupt procedure
	movx	@dptr,a
	inc	dptr
	mov	a,#LOW(timer_0_int)	; low address byte of timer interrupt procedure
	movx	@dptr,a
;
; initialize relay drivers and buffers
;
	mov	dptr,#relbase
	mov	r3,#reldrivnum
idloop1:
	clr	a
	movx	@dptr,a		; turn all relays off
	inc	dptr
	djnz	r3,idloop1
;
	mov	dptr,#reldrivbuf
	mov	r3,#reldrivnum
idloop2:
	clr	a
	movx	@dptr,a		; initialize port driver buffer
	inc	dptr
	djnz	r3,idloop2
;
	mov	dptr,#keydbuf
	mov	r3,#2*keyrownum
idloop3:
	clr	a
	movx	@dptr,a
	djnz	r3,idloop3
;
	mov	a,#0
	mov	dptr,#curkeynum
	movx	@dptr,a		; initialize key number
;
	mov	a,#01H
	mov	dptr,#keyscanbuf
	movx	@dptr,a		; initialize key scan buffer
	mov	dptr,#keydrive
	cpl	a		; convert to negative logic
	movx	@dptr,a		; initialize key scan driver
;
	lcall	init_cellpool
	lcall	init_queues
;
; initialize LED scanner phases
;
	mov	dptr,#ledphase
	clr	a
	movx	@dptr,a		; initialize phase to 0
	inc	dptr
	movx	@dptr,a		; initialize phase 0 to OFF,OFF
	inc	dptr
	mov	a,#00000001B
	movx	@dptr,a		; initialize phase 1 to OFF,ON
;
; initialize pulser
;
	mov	dptr,#puls_state
	clr	a
	movx	@dptr,a		; state = 0
	inc	dptr
	movx	@dptr,a		; P1 = 0
	inc	dptr
	movx	@dptr,a		; P2 = 0
	inc	dptr
	movx	@dptr,a		; counter = 0
	inc	dptr
	mov	a,#LOW(puls_cp)
	movx	@dptr,a
	inc	dptr
	mov	a,#HIGH(puls_cp)
	movx	@dptr,a		; set pulser control program to start
;
; initialize TASKER:
;
	mov	dptr,#task_tblptr
	mov	a,#LOW(def_task_table)
	movx	@dptr,a
	inc	dptr
	mov	a,#HIGH(def_task_table)
	movx	@dptr,a
;
; start the basic timer for first slice
;
	mov	dptr,#t0div
	clr	a
	movx	@dptr,a			; reset level 0 divider
;
	mov	a,tmod
	ANL	a,#11110001B 	; leave timer 1 alone and ...
	ORL     a,#00000001B 	; ... SET TIMER 0 MODE 1 16 BIT TIMER
	mov	tmod,a
	CLR     TR0             	; stop timer 0
	CLR     TF0             	; INIT TIMER 0
	MOV     TH0,#HIGH(TIMER_RELOAD) ;TIMER 0 IS system time base
	MOV     TL0,#LOW(TIMER_RELOAD)
	SETB    TR0                     ;START TIMER 0

	setb    et0             ; ENABLE INTERRUPT FOR TIMER 0
	setb    ea              ; .. and interrupts generally
	pop	psw
	ret
;
; ---------------------------------------------------------------
;
TIMER_0_INT:
	;
	; assume PSW already pushed
	;
        PUSH    ACC
        push    dph
        push    dpl
        CLR     TR0                     ;STOP TIMER
        CLR     TF0                     ;CLEAR EXPIRY FLAG

        MOV     TH0,#HIGH(TIMER_RELOAD)	; start basic timer again
        MOV     TL0,#LOW(TIMER_RELOAD)
        SETB    TR0                     ;START TIMER 0
timer0act:
	ORL	PSW,#00011000B		; select register bank 3
scanner:                        	; next task, scanner
        lcall   key_scanner
	lcall	pulser
	mov	dptr,#t0div
	movx	a,@dptr
	inc	a
	movx	@dptr,a			; increment t0 divisor
	cjne	a,#t0divisor,noledscan	; have we reached divisor, => no
	clr	a
	movx	@dptr,a			; reset scan divider
	lcall	led_scanner
noledscan:
;
; end of scanner
;
EXIT_TIMER0:
        pop     dpl
        pop     dph
        POP     ACC             ; restore registers
        POP     PSW
        RETI                    ; return to application

;
; ---------------------------------------------------------------
;
; FUNCTION:	Alloc_Cell
; OUTPUT:	DPTR - address of free cell
; DESTROYS:	ACC, DPTR
; ACTION:	Finds the first free cell in cellpool (USE=0)
;
alloc_cell:
	push	IE
	clr	EA		; disable all interrupts
	mov	dptr,#cellpool+cell_use
allc_loop:
	movx	a,@dptr
	jz	allc_fnd
	mov	a,dpl
	add	a,#cell_size
	mov	dpl,a
	mov	a,dph
	addc	a,#0
	mov	dph,a
	jmp	allc_loop
allc_fnd:
	mov	a,dpl
	clr	C
	subb	a,#cell_use
	mov	dpl,a
	mov	a,dph
	subb	a,#0
	mov	dph,a
	push	dpl
	push	dph
	inc	dptr
	inc	dptr
	inc	dptr
	inc	dptr		; point to CELL.USE
	mov	a,#1
	movx	@dptr,a		; mark cell as used
	pop	dph
	pop	dpl
	pop	IE		; restore interrupt state
	ret
;
; ---------------------------------------------------------------
;
; FUNCTION:	Free_Cell
; INPUT:	DPTR - points to cell to be freed
; ACTION:	return cell (DPTR) again to free cell pool
;
free_cell:
	inc	dptr
	inc	dptr
	inc	dptr
	inc	dptr
	clr	a
	movx	@dptr,a
	ret
;
; ---------------------------------------------------------------
;
; FUNCTION:	INSQT
; ACTION:	incert cell <DPTR> at tail of queue <R3,R1>
;
insqt:
	push	IE	; save interrupt state
	clr	EA	; disable all interrupts
	mov	r0,dpl
	mov	r2,dph	; save ptr to new cell in R2/0
	lcall	tstq	; has queue any entries ?
	jz	insqtf	;=yes, special for inserting only cell
	mov	dpl,r1
	mov	dph,r3	; point to QCB.HEAD
	inc	dptr
	inc	dptr	; point to QCB.TAIL
	movx	a,@dptr
	mov	r4,a
	inc	dptr
	movx	a,@dptr
	mov	r6,a	; get ptr to old tail element to R6/4
	mov 	dpl,r0
	mov	dph,r2	; point to new cell again
	mov	a,r4
	movx	@dptr,a
	inc	dptr
	mov	a,r6
	movx	@dptr,a	; let CELL.NEXT point to old tail cell
	inc	dptr
	clr	a
	movx	@dptr,a
	inc	dptr
	movx	@dptr,a	; let CELL.PREV be NULL
	mov	dpl,r4
	mov	dph,r6	; point to old tail element
	inc	dptr
	inc	dptr	; point to OLDTAIL.PREV
	mov	a,r0
	movx	@dptr,a
	inc	dptr
	mov	a,r2
	movx	@dptr,a	; let OLDTAIL.PREV point to new CELL
	mov	dpl,r1
	mov	dph,r3
	inc	dptr
	inc	dptr	; point to QCB.TAIL
	mov	a,r0
	movx	@dptr,a
	inc	dptr
	mov	a,r2
	movx	@dptr,a	; let QCB.TAIL point to new CELL
insqte:
	pop	IE	; restore interrupt state
	ret
insqtf:			; insert CELL as first and only element
	mov	dpl,r0
	mov	dph,r2
	clr	a
	movx	a,@dptr
	inc	dptr
	movx	a,@dptr
	inc	dptr
	movx	a,@dptr
	inc	dptr
	movx	a,@dptr	; set CELL.NEXT and CELL.PREV to NULL
	mov	dpl,r1
	mov	dph,r3	; point to QCB.HEAD
	mov	a,r0
	movx	@dptr,a
	inc	dptr
	mov	a,r2
	movx	@dptr,a	; let QCB.HEAD point to new CELL
	inc	dptr
	mov	a,r0
	movx	@dptr,a
	inc	dptr
	mov	a,r2
	movx	@dptr,a	; let QCB.TAIL point to new CELL
	sjmp	insqte
	
;
; ---------------------------------------------------------------
;
; FUNCTION:	REMQH
; ACTION:	remove cell from queue <R3,R1> to <DPTR>
;
remqh:
	push	IE	; save interrupt state
	clr	EA	; disable all interrupts
	lcall	tstq	; has queue any entries ?
	jz	remqhf	;=no, nothing to remove
	mov	dpl,r1
	mov	dph,r3	; point to QCB.HEAD
	movx	a,@dptr
	mov	r0,a
	inc	dptr
	movx	a,@dptr
	mov	r2,a	; get ptr to removed head element to R2/0
	mov 	dpl,r0
	mov	dph,r2	; point to cell to be removed
	inc	dptr
	inc	dptr
	movx	a,@dptr
	mov	r4,a
	inc	dptr
	movx	a,@dptr
	mov	r6,a	; get CELL.PREV to R6/4
	orl	a,r4
	jz	remqho	;=there was no previous cell
	mov	dpl,r4
	mov	dph,r6	; point to new head cell
	clr	a
	movx	@dptr,a
	inc	dptr
	movx	@dptr,a	; let NEWHEAD.NEXT be NULL
	mov	dpl,r1
	mov	dph,r3	; point to QCB.HEAD
	mov	a,r4
	movx	@dptr,a
	inc	dptr
	mov	a,r6
	movx	@dptr,a	; let QCB.HEAD point to new head cell
remqhc:
	mov	dpl,r0
	mov	dph,r2	; point to CELL again
	clr	a
	movx	@dptr,a
	inc	dptr
	movx	@dptr,a
	inc	dptr
	movx	@dptr,a
	inc	dptr
	movx	@dptr,a	; unlink cell from queue
	mov	dpl,r0
	mov	dph,r2	; return DPTR pointing to removed cell
remqhe:
	mov	a,dpl
	orl	a,dph
	pop	IE	; restore interrupt state
	ret
remqhf:			; nothing to remove, return NULL pointer
	mov	dpl,#0
	mov	dph,#0
	sjmp	remqhe
remqho:			; we removed the only cell, clear QCB
	mov	dpl,r1
	mov	dph,r3
	clr	a
	movx	@dptr,a
	inc	dptr
	movx	@dptr,a
	inc	dptr
	movx	@dptr,a
	inc	dptr
	movx	@dptr,a
	sjmp	remqhc
;
; ---------------------------------------------------------------
;
; FUNCTION:	SETQ
; DESTROYS:	ACC,R1,R3
; ACTION:	sets (R3,R1) to point to queue number (ACC)
;
setq:
	clr	c
	rlc	a
	rlc	a
	add	a,#LOW(cellqueues)
	mov	r1,a
	mov	a,#HIGH(cellqueues)
	addc	a,#0
	mov	r3,a
	ret
;
; ---------------------------------------------------------------
;
; FUNCTION:	TSTQ
; INPUT:	R3,R1 - (H,L) pointer to queue control block
; OUTPUT:	A - not zero if there are entries in the queue
; DESTROYS:	R7,DPTR
; ACTION:	test if queue <R3,R1> contains entries
;
tstq:
	mov	dpl,r1
	mov	dph,r3
	movx	a,@dptr
	mov	r7,a
	inc	dptr
	movx	a,@dptr
	orl	a,r7
	ret
;
; ---------------------------------------------------------------
;
; FUNCTION:	KEY_SCANNER
;
	USING	3
;
key_scanner:
	mov	dptr,#keyreturn
	movx	a,@dptr		; get scan return data
	cpl	a		; convert to positive logic
	mov	r0,a		; save in R0
	mov	dptr,#curkeynum
	movx	a,@dptr
	mov	r1,a		; save scanned row number in R1
	inc	a
	cjne	a,#keyrownum,kesc_nowrap1
	clr	a		; reset to first row
kesc_nowrap1:
	movx	@dptr,a		; save next scanned row number
	inc	dptr		; point to scan image buffer
	movx	a,@dptr		; get last image
	clr	c
	rlc	a		; move to next scan line
	jnz	kesc_nowrap2
	mov	a,#1		; reset to first scan line
kesc_nowrap2:
	movx	@dptr,a		; save scan pattern
	mov	dptr,#keydrive
	cpl	a		; convert to negative logic
	movx	@dptr,a		; issue new scan pattern
;
	mov	a,#LOW(keydbuf)
	add	a,r1		; index by key scan row
	mov	dpl,a
	mov	a,#HIGH(keydbuf)
	addc	a,#0
	mov	dph,a		; point to current row's debounce buffer
	movx	a,@dptr
	mov	r2,a		; save old state in R2
	anl	a,r0		; debounce
	mov	r3,a		; save debounced in R3
	mov	a,r0
	movx	@dptr,a		; save sampled value
;
	mov	a,#LOW(keycbuf)
	add	a,r1
	mov	dpl,a
	mov	a,#HIGH(keycbuf)
	addc	a,#0
	mov	dph,a
	movx	a,@dptr		; get last value to compare
	mov	r4,a		; save last value in R4
	xrl	a,r3
	mov	r5,a		; one bit set for every changed key in this row
	jz	kesc_end
kesc_diff:			; there are changed keys, new in R3
	mov	a,r3		; get new state
	movx	@dptr,a		; save as new "old" value to compare next time
	mov	r6,#0		; bit counter
kesc_dloop:
	mov	a,r3
	rrc	a		; new state to C
	mov	r3,a
	mov	F0,c		; save in F0
	mov	a,r5
	rrc	a		; change flag to C
	mov	r5,a
	jnc	kesc_dnext	;=no change, skip to next column
	push	ar1
	push	ar2
	push	ar3
	push	ar4
	push	ar5
	push	ar6 
	clr	a
	mov	C,F0		; new key state ...
	mov	acc.6,C		; ... to ACC.6
	orl	a,r6		; column to ACC.2..ACC.0
	mov	r4,a
	mov	a,r1
	rl	a
	rl	a
	rl	a		
	orl	a,r4		; row to ACC.5..ACC.3
	push	acc		; save on stack
	lcall	alloc_cell	; allocate a message cell
	pop	acc
	push	dpl
	push	dph
	push	acc
	inc	dptr
	inc	dptr
	inc	dptr
	inc	dptr		; point to CELL.USE
	mov	a,#080H
	movx	@dptr,a		; USE=128 tells KEY message
	inc	dptr		; point to CELL.CODE
	mov	a,#1
	movx	@dptr,a		; msg code 1 = KEY
	inc	dptr
	pop	acc		; restore calculated key code
	movx	@dptr,a		; key action code to CELL_CODE2
	mov	a,#0
	lcall	setq		; set queue 0
	pop	dph
	pop	dpl
	lcall	insqt		; enqueue KEY msg. to queue 0
	pop	ar6
	pop	ar5
	pop	ar4
	pop	ar3
	pop	ar2
	pop	ar1
kesc_dnext:
	inc	r6
	cjne	r6,#8,kesc_dloop
kesc_end:
	ret
;
; ---------------------------------------------------------------
;
; FUNCTION:	PULSER
;
pulser:
	mov	dptr,#puls_state
	movx	a,@dptr
	cjne	a,#psta_wevt,puls_nwevt
;
; pulser state WEVT, wait for event from queue 1
;
	mov	a,#1
	lcall	setq
	lcall	tstq
	jz	puls_end	;=no event pending
	lcall	remqh		; get the event
	inc	dptr
	inc	dptr
	inc	dptr
	inc	dptr		; point to CELL.USE
	clr	a
	movx	@dptr,a		; mark cell as unused
	inc	dptr		; point to CELL.CODE1
	movx	a,@dptr		; get event code
	mov	r0,a
	inc	dptr		; point to CELL.CODE2
	movx	a,@dptr		; get subevent code
	mov	r2,a
	inc	dptr		; point to CODE3
	inc	dptr		; point to CELL.DATA
	mov	r1,dpl
	mov	r3,dph		; save pointer to CELL.DATA
	mov	dptr,#puls_p1	; point to PULS.P1
	mov	a,r0
	movx	@dptr,a		; save event in <P1>
	inc	dptr		; point to PULS.P2
	mov	a,r2
	movx	@dptr,a	        ; save subevent in <P2>
	inc	dptr		; point to PULS.DCNT
	inc	dptr		; point to PULS.CPTR
	inc	dptr
	inc	dptr		; point to PULS.EVTDAT
	mov	r5,dpl
	mov	r7,dph		; save pointer to PULS.EVDAT(0) in R5/R7
	mov	r2,#cell_datalen
puls_wevtl:
	mov	dpl,r1
	mov	dph,r3		; point to CELL.DATA(n)
	movx	a,@dptr
	inc	dptr
	mov	r1,dpl
	mov	r3,dph
	mov	dpl,r5
	mov	dph,r7		; point to PULS.EVTDAT(n)
	movx	@dptr,a
	inc	dptr
	mov	r5,dpl
	mov	r7,dph
	djnz	r2,puls_wevtl	; repeat for all data bytes
	lcall	puls_nxtc
	sjmp	puls_end
puls_nwevt:
	cjne	a,#psta_delay,puls_ndelay
;
; pulser state DELAY, wait until delay counter expired 
;
	mov	dptr,#puls_dcnt
	movx	a,@dptr
	dec	a
	movx	@dptr,a
	jnz	puls_s1end
	lcall	puls_nxtc
puls_s1end:
	sjmp	puls_end
puls_ndelay:
puls_end:
	ret
;
; the PULSER control program
;
puls_cp:
	%P_C1BE(pev_bron,puls_cpbo)
	%P_C1BE(pev_broff,puls_cpbf)
	%P_C1BE(pev_brtog,puls_cpbt)
	%P_C1BE(pev_mron,puls_cpmo)
	%P_C1BE(pev_mroff,puls_cpmf)
	%P_C1BE(pev_mrtog,puls_cpmt)
	%P_C1BE(pev_mbon,puls_cmbon)
	%P_C1BE(pev_mboff,puls_cmboff)
	%P_C1BE(pev_mmon,puls_cmmon)
	%P_C1BE(pev_mmoff,puls_cmmoff)
puls_cpwait:
	%P_WEVT
	%P_BRA(puls_cp)
puls_cpbo:
	%P_MBRO
	%P_CBRO
puls_sbr:
	%P_PLON2
	%P_DELAY(100)
	%P_PLOFF2
	%P_BRA(puls_fdel)
puls_cpbf:
	%P_MBRF
	%P_CBRF
	%P_BRA(puls_sbr)
puls_cpbt:
	%P_CBTL
	%P_BRA(puls_sbr)
puls_cpmo:
	%P_PLON2		; ON     MO-relay <P2>
puls_fdel:
	%P_DELAY(100)
	%P_BRA(puls_cpwait)
puls_cpmf:			; OFF    MO-relay <P2>
	%P_PLOFF2
	%P_BRA(puls_fdel)
puls_cpmt:			; TOGgle MO-relay <P2>
	%P_MTOG
	%P_BRA(puls_fdel)
puls_cmbon:
	%P_MBRO
	%P_BRA(puls_cpwait)
puls_cmboff:
	%P_MBRF
	%P_BRA(puls_cpwait)
puls_cmmon:
	%P_MMRO
	%P_BRA(puls_cpwait)
puls_cmmoff:
	%P_MMRF
	%P_BRA(puls_cpwait)
;
; ------ the PULSER program processor
;
puls_nxtc:
	lcall	puls_cpgb
	cjne	a,#0,pnx_n0
; pulser command 0: end of sequence
	ret
pnx_n0:
	cjne	a,#pc_c1be,pnx_nc1be
; pulser command C1BE: compare P1 to <PB1>, if equal, branch to <PW2>
	lcall	puls_cpg3
	mov	dptr,#puls_p1
	movx	a,@dptr
	cjne	a,ar1,puls_nxtc	;=not equal, skip to next command
	ljmp	pnx_dbra        ;=equal branch to <PW1>
pnx_nc1be:
	cjne	a,#pc_sdc,pnx_nsdc
; pulser command SDC: set delay counter to <PB1>
	lcall	puls_cpgb
	mov	dptr,#puls_dcnt
	movx	@dptr,a
	ljmp	puls_nxtc
pnx_nsdc:
	cjne	a,#pc_nst,pnx_nnst
; pulser command NST: set state to <PB1>
	lcall	puls_cpgb
	mov	dptr,#puls_state
	movx	@dptr,a
	ljmp	puls_nxtc
pnx_nnst:
	cjne	a,#pc_delay,pnx_ndelay
; pulser command DELAY: wait for <PB1>*10 milliseconds
	lcall	puls_cpgb
	mov	dptr,#puls_state	
	mov	a,#psta_delay
	movx	@dptr,a		; state is DELAY
	inc	dptr
	inc	dptr
	inc	dptr		; point to delay counter
	mov	a,r1
	movx	@dptr,a
	ret			; let pulser wait for delay complete
	ljmp	puls_nxtc
pnx_ndelay:
	cjne	a,#pc_wevt,pnx_nwevt
; pulser command WEVT: wait for event in queue 1
	mov	dptr,#puls_state
	mov	a,#psta_wevt
	movx	@dptr,a		; state is WEVT
	ret			; let pulser wait for event
pnx_nwevt:
	cjne	a,#pc_plon,pnx_nplon
; pulser command PLON: switch output line <P1> to ON
	mov	dptr,#puls_p1
	movx	a,@dptr		; get line number from <P2>
	lcall	plon		; switch it ON
	ljmp	puls_nxtc
pnx_nplon:
	cjne	a,#pc_ploff,pnx_nploff
; pulser command PLOFF: switch output line <P1> to OFF
	mov	dptr,#puls_p1
	movx	a,@dptr		; get line number from <P2>
	lcall	ploff		; switch it OFF
	ljmp	puls_nxtc
pnx_nploff:
	cjne	a,#pc_bra,pnx_nbra
; pulser command BRA, branch unconditional to <PW1>
	lcall	puls_cpgw
pnx_dbra:
	mov	dpl,r0
	mov	dph,r2
	lcall	puls_sccp
	ljmp	puls_nxtc
pnx_nbra:
	cjne	a,#pc_cbro,pnx_ncbro
; pulser command CBRO, calc BI-relay ON line number from <P2> to <P2>
	mov	dptr,#puls_p2
	movx	a,@dptr
	anl	a,#0FH	; limit to 16 BI-relay
	clr	c
	rlc	a
	mov	dptr,#puls_p2
	movx	@dptr,a
	ljmp	puls_nxtc
pnx_ncbro:
	cjne	a,#pc_cbrf,pnx_ncbrf
; pulser command CBRF, calc. BI-relay OFF line number from <P2> to <P2>
	mov	dptr,#puls_p2
	movx	a,@dptr
	anl	a,#0FH	; limit to 16 BI-relay
	setb	c
	rlc	a
	mov	dptr,#puls_p2
	movx	@dptr,a
	ljmp	puls_nxtc
pnx_ncbrf:
	cjne	a,#pc_mbro,pnx_nmbro
; pulser command MBRO, memorize BI-relay <P2> as ON
	lcall	cbra
	mov	a,#0FFH
	movx	@dptr,a
	ljmp	puls_nxtc
pnx_nmbro:
	cjne	a,#pc_mbrf,pnx_nmbrf
; pulser command MBRF, memorize BI-relay <P2> as OFF
	lcall	cbra
	mov	a,#000H
 	movx	@dptr,a
	ljmp	puls_nxtc
pnx_nmbrf:
	cjne	a,#pc_mmro,pnx_nmmro
; pulser command MMRO, memorize MO-relay <P2> as ON
	lcall	cmra
	mov	a,#0FFH
 	movx	@dptr,a
	ljmp	puls_nxtc
pnx_nmmro:
	cjne	a,#pc_mmrf,pnx_nmmrf
; pulser command MBRF, memorize MO-relay <P2> as OFF
	lcall	cmra
	mov	a,#000H
 	movx	@dptr,a
	ljmp	puls_nxtc
pnx_nmmrf:
	cjne	a,#pc_cbtl,pnx_ncbtl
; pulser command CBTL, calc BI-relay <P2> toggle line to <P2>
	mov	dptr,#puls_p2
	movx	a,@dptr
	anl	a,#0FH	; limit to 16 BI-relays
	mov	r3,a
	lcall	cbra
	movx	a,@dptr	; get current state
	cpl	a	; reverse
	movx	@dptr,a	; save new state
	rrc	a	; new state flag to C
	mov	a,r3
	rlc	a	; calculate line
	mov	dptr,#puls_p2
	movx	@dptr,a	; save in P2
	ljmp	puls_nxtc 
pnx_ncbtl:
	cjne	a,#pc_plon2,pnx_nplon2
; pulser command PLON2: switch output line <P2> to ON
	mov	dptr,#puls_p2
	movx	a,@dptr		; get line number from <P2>
	lcall	plon		; switch it ON
	ljmp	puls_nxtc
pnx_nplon2:
	cjne	a,#pc_ploff2,pnx_nploff2
; pulser command PLOFF2: switch output line <P2> to OFF
	mov	dptr,#puls_p2
	movx	a,@dptr		; get line number from <P2>
	lcall	ploff		; switch it OFF
	ljmp	puls_nxtc
pnx_nploff2:
	cjne	a,#pc_mtog,pnx_nmtog
; pulser command MTOG, toggle MO-relay <P2>
	mov	dptr,#puls_p2
	movx	a,@dptr		; MO-relay number
	mov	r5,a
	lcall	cmra		; point to this relay's storage
	movx	a,@dptr
	cpl	a		; toggle state
	movx	@dptr,a		; save
	jnz	pnx_mtogon
	mov	a,r5
	lcall	ploff
	ljmp	puls_nxtc
pnx_mtogon:
	mov	a,r5
	lcall	plon
	ljmp	puls_nxtc
pnx_nmtog:
	ljmp	puls_nxtc
	
;
; ------ calculate address of BI-relay state storage <ACC>
;
cbra:
	anl	a,#0FH		; limit to 16 BI-relays
	add	a,#low(bi_rel_buff)
	mov	dpl,a
	mov	a,#high(bi_rel_buff)
	addc	a,#0
	mov	dph,a
	ret
;
; ------ calculate address of MO-relay state storage <ACC>
;
cmra:
	anl	a,#1FH		; limit to 32 MO-relays
	add	a,#low(mo_rel_buff)
	mov	dpl,a
	mov	a,#high(mo_rel_buff)
	addc	a,#0
	mov	dph,a
	ret
;
; ------ bit-mask-table
;
c_tab_bmask:
	DB	00000001B
	DB	00000010B
	DB	00000100B
	DB	00001000B
	DB	00010000B
	DB	00100000B
	DB	01000000B
	DB	10000000B
;
; ------ calc bitmask from ACC to ACC
;
cbmask:
	anl	a,#07H
	mov	dptr,#c_tab_bmask
	movc	a,@a+dptr
	ret
;
; ------ calc driver address and bit mask for output line
;
; OUTPUT:	DPTR  - output buffer address
;		R0,R2 - output driver address
;               R4,R6 - address of MO-relay state
;		ACC   - bit mask 
;
codp:
	mov	r1,a
	rr	a
	rr	a
	rr	a
	anl	a,#03h
	mov	r3,a	; save index in R3
	add	a,#low(relbase)
	mov	r0,a
	mov	a,#high(relbase)
	addc	a,#0
	mov	r2,a
	mov	a,r1
	lcall	cbmask
	mov	r1,a	; save mask in R1
	mov	a,r3
	add	a,#low(reldrivbuf)
	mov	dpl,a
	mov	a,#high(reldrivbuf)
	addc	a,#0
	mov	dph,a
	mov	a,r1
	ret
;
; ------ set output line <ACC> to ON
;
plon:
	mov	r3,a
	lcall	cmra
	mov	a,#0FFH
	movx	@dptr,a
	mov	a,r3
	lcall	codp
	movx	a,@dptr
	orl	a,r1
	movx	@dptr,a
plset:
	mov	dpl,r0
	mov	dph,r2
	movx	@dptr,a
	ret
;
; ------ set output line <ACC> to OFF
;
ploff:
	mov	r3,a
	lcall	cmra
	mov	a,#00H
	movx	@dptr,a
	mov	a,r3
	lcall	codp
	cpl	a
	mov	r1,a
	movx	a,@dptr
	anl	a,r1
	movx	@dptr,a
	ljmp	plset
	
;
; ------ load pulser control program counter to DPTR
;
puls_lcpp:
	mov	dptr,#puls_cptr
	movx	a,@dptr
	mov	r7,a
	inc	dptr
	movx	a,@dptr
	mov	dph,a
	mov	dpl,r7
	ret
;
; ------ save pulser control program counter from DPTR
;
puls_sccp:
	mov	r7,dph
	mov	a,dpl
	mov	dptr,#puls_cptr
	movx	@dptr,a
	inc	dptr
	mov	a,r7
	movx	@dptr,a
	ret
;
; ------ get byte from pulser control program to ACC
;
puls_cpgb:
	lcall	puls_lcpp
	movx	a,@dptr
	inc	dptr
	mov	r1,a
	lcall	puls_sccp
	mov	a,r1
	ret
;
; ------ get word from pulser control program to R0,R2
;
puls_cpgw:
	lcall	puls_lcpp
	movx	a,@dptr
	mov	r2,a
	inc	dptr
	movx	a,@dptr
	mov	r0,a
	inc	dptr
	lcall	puls_sccp
	ret
;
; ------ get 1 byte to R1 and one word to R0,R2 from pulser control program
;
puls_cpg3:
	lcall	puls_lcpp
	movx	a,@dptr
	inc	dptr
	mov	r1,a	
	movx	a,@dptr
	inc	dptr
	mov	r2,a
	movx	a,@dptr
	inc	dptr
	mov	r0,a
	lcall	puls_sccp
	ret	
;
; ---------------------------------------------------------------
;
; FUNCTION:	LED_SCANNER
;
led_scanner:
	mov	dptr,#ledphase
	movx	a,@dptr
	inc	a
	anl	a,#1
	movx	@dptr,a
	inc	dptr		; point to phase 0
	jz	lscn_phase	;=phase 0
	inc	dptr		; point to phase 1
lscn_phase:
	movx	a,@dptr		; get LED pattern for this phase
	cpl	a		; invert for 0-active
	anl	a,#03H
	mov	r0,a
	mov	a,p1
	anl	a,#0FCH		; clear LED bits, let rest alone
	orl	a,r0		; merge LED bits into pattern
	mov	p1,a
	ret
