; Host command processor. The routines in this module interpret and
; process the command stream coming from the host computer via the
; RS-232 link.
;
include "hos.inc"
extern uart_get ;get next serial line input byte into REG0
extern uart_put ;send the byte in REG0 to the host
extern loop_main ;jump back here after handling an event
extern_flags ;declare global flag bits EXTERN
;
;***********************************************************************
;
; Configuration constants.
;
first_opcode equ 'a' ;first valid opcode in opcodes jump table
lbank equ 1 ;register bank for the local state of this module
;
; Set MSKSAVE. This is the bit mask of all the registers that are to
; be saved accross GETBYTE. The remaining registers will be trashed.
; REG0 will not be saved because the new input byte is returned in
; REG0 by GETBYTE.
;
msksave equ regf1 | regf2 | regf3
;
; Derived constants.
;
lbankadr equ bankadr(lbank) ;address within local state register bank
;
;***********************************************************************
;
; Global state. All this state is assumed to be in the GBANK register
; bank by other modules.
;
.bank#v(gbank) udata
;
;***********************************************************************
;
; Local state.
;
if lbank != gbank
.bank#v(lbank) udata
endif
ii set 1 ;init register number
while ii < numregs
if (1 << ii) & msksave ;save/restore this register accross GETBYTE call ?
savereg#v(ii) res 1 ;make save area for this register
endif
ii set ii + 1 ;advance to next register number
endw
injump res 2 ;where to jump on next input byte
lastalp res 1 ;last alphabetic character emitted
.cmd code
;
;***********************************************************************
;
; Macro GETBYTE
;
; The code in this module gets run whenever a new input byte is available.
; This macro makes it appear as if the code in this module is a separate
; thread that goes and gets the next input byte. It saves a restart address,
; then returns to the main event loop. When CMD_BYTE gets envoked the next
; time, it resumes execution at the restart address.
;
; This macro therefore appears like a subroutine call that returns with
; the next input byte in REG0. The register bank assumptions must be
; correct when this macro is used.
;
; From the caller's point of view, REG0 is returned with the new input
; byte, and the registers listed in MSKSAVE are preserved with the
; remaining registers trashed.
;
getbyte macro
local restart
;
; Set the address at which to restart next time. This is the address
; immediately after this macro.
;
dbankif lbankadr
movlw low restart ;save where to restart next time
movwf injump+0
movlw high restart
goto getbyte2 ;to non-replicated code to do the rest
;
; Set the assembler state to indicate the register bank settings.
; The settings are a function of the code in CMD_BYTE that jumps to the
; restart address.
;
restart ;end up here next time CMD_BYTE is envoked
dbankis lbankadr ;direct register set for access to local state
ibank? ;indirect bank setting is unknown
endm
;
;**********
;
; This section of code is only run implicitly from the GETBYTE macro.
; There is only one copy of this code at a fixed location, whereas
; the GETBYTE code is replicated for every use of the macro. This code
; is jumped to from GETBYTE to perform as much of the GETBYTE operation
; as possible that is not unique to each individual invocation of GETBYTE.
; This reduces redundant code which would otherwise be produced for each
; GETBYTE invocation.
;
getbyte2
;
; The low byte of the restart address has already been saved in INJUMP+0,
; and the high byte is in W. Now save the high byte into INJUMP+1.
; The direct register bank is set for access to the local state.
;
dbankis lbankadr
movwf injump+1
;
; Save some of the registers locally. These will be restored when
; CMD_BYTE is run next before it jumps to the restart address.
;
dbankif lbankadr
ii set 1 ;init register number
while ii < numregs
if (1 << ii) & msksave ;save/restore this register accross GETBYTE call ?
movf reg#v(ii), w ;get the value of this register
movwf savereg#v(ii) ;save it
endif
ii set ii + 1 ;advance to next register number
endw
gjump loop_main ;back to the main event loop
;
;***********************************************************************
;
; Subroutine CMD_INIT
;
; Initialize the state managed by this module.
;
glbsub cmd_init, noregs
dbankif lbankadr
movlw low cmd_start ;init to where to start processing next in byte
movwf injump+0
movlw high cmd_start
movwf injump+1
movlw 'Z'
movwf lastalp
leaverest
;
;***********************************************************************
;
; Routine CMD_BYTE
;
; This routine is jumped to from the main event loop when there is an
; input byte available from UART_GET.
;
glbent cmd_byte
gcall uart_get ;get the input byte into REG0
;
; Restore the registers that are preserved accross invocations of the
; GETBYTE macro.
;
dbankif lbankadr
ii set 1 ;init register number
while ii < numregs
if (1 << ii) & msksave ;save/restore this register accross GETBYTE call ?
movf savereg#v(ii), w ;get the saved copy of the register
movwf reg#v(ii) ;restore the register
endif
ii set ii + 1 ;advance to next register number
endw
;
; Jump to the restart address. The restart address was saved in INJUMP
; by GETBYTE before it jumped back to the main event loop.
;
dbankif lbankadr
movf injump+1, w ;get restart address high byte
movwf pclath ;set jump address high byte
movf injump+0, w ;get restart address low byte
movwf pcl ;jump to the restart address
;
; The restart address is jumped to with the direct register bank set for
; access to the local state. The indirect register bank setting is unknown.
;
;***********************************************************************
;
; Routine CMD_NEXT
;
; Process the next command. This routine appears to run as an infinite
; loop to the code here. It is actually run piecemeal from the main event
; loop when an input character is available. The return to the main
; event loop is hidden inside the GETBYTE macro.
;
cmd_ack unbank ;done with command, ACK it and get next command
cm_nop unbank ;NOP cmd, no operation but acts like normal command
;
; *** INSERT ACK CODE HERE IF REQUIRED ***
; Code can be filled in here to send an ACK for the command that was just
; completed. That is useful with protocols that do not use any low level
; flow control, like XON/XOFF bytes or hardware RTS/CTS lines. In these
; cases, each command is small enough to fit completely in the UART input
; FIFO, and the host does not send a new command until the previous one
; is acknowledged.
;
; Note that the code to send an ACK is commented out in this template.
; It can be left this way if the protocol does not use ACK. PUTBYTE
; and RSP_ACK are defined in the standard project include file template.
;
; putbyte rsp_ack ;send ACK response to the host
cmd_done unbank ;done processing the current command
cmd_next unbank ;get and process the next command
getbyte
cmd_start ;initial restart adr set by CMD_INIT, REG0 has opcode
;
; The new command byte is in REG0.
;
; Check for within range of jump table entries.
;
if first_opcode != 0 ;commands don't start at zero ?
movlw first_opcode ;get first valid opcode value
subwf reg0, w ;make table offset for this opcode
skip_wle ;opcode not before table start ?
goto cmd_next ;invalid opcode, ignore it
else ;the command table starts at opcode 0
movf reg0, w ;get the opcode, which is also table offset
endif
;
; The opcode is not before the start of the opcodes table,
; and the table offset is in W.
;
movwf reg12 ;save the table offset in REG12
sublw tbl_cmd_n - 1 ;compare to last valid command ID
skip_wle ;command ID is within range ?
goto cmd_next ;invalid command ID, ignore it
;
; The opcode is within range of the opcodes jump table. The original
; opcode is in REG0, and the table offset is in REG12. Now jump to
; the selected table entry, which will in turn jump to the routine
; to perform the selected command.
;
movlw high tbl_cmd ;get table start address high byte
movwf pclath ;init jump address high byte
movlw low tbl_cmd ;get table start address low byte
addwf reg12, w ;make table entry address low byte
skip_ncarr ;carry into entry address high byte
incf pclath ;propagate the carry
movwf pcl ;jump to the selected jump table entry
;
;***********************************************************************
;
; Macro COMMAND
;
; Indicate the start of a command routine that is jumped to from the
; commands jump table. This macro allows for setting particular
; register bank assumptions, etc, as the command dispatch code is
; modified.
;
; At the start of a command, REG0 contains the command opcode. The
; remaining registers are undefined. The command processing routine
; should eventually jump to CMD_DONE when done processing this command.
;
command macro
dbankis lbankadr ;direct register bank is set for access to local state
ibank? ;indirect bank setting is unknown
endm
;
;***********************************************************************
;
; Command ON
;
; Turn on the LED.
;
cm_on command
dbankif led_reg
bcf led_pin
goto cmd_done ;done with this command
;
;***********************************************************************
;
; Command OFF
;
; Turn off the LED.
;
cm_off command
dbankif led_reg
bsf led_pin
goto cmd_done ;done with this command
;
;***********************************************************************
;
; Command SALPHA
;
; Send the next alphabetic character to the host. This is a means to
; test bi-directional communication. Each time the command is run, one
; of the characters "A" thru "Z" is emitted in sequence. CRLF is emitted
; every time the sequence is restarted.
;
cm_salpha command
dbankif lbankadr
incf lastalp ;make the new character to emit
movf lastalp, w ;get the new character
xorlw 'Z' + 1 ;compare to after end of sequence
skip_z ;starting a new sequence ?
jump dnewseq ;no
;
; Start a new sequence.
;
putbyte 13 ;send CR
putbyte 10 ;send LF
dbankif lbankadr
movlw 'A' ;reset to start of sequence
movwf lastalp
dnewseq ;done dealing with starting new sequence
dbankif lbankadr
movf lastalp, w ;get the character to send this time
movwf reg0
gcall uart_put ;send it
goto cmd_done ;done with this command
;
;***********************************************************************
;
; Commands jump table.
;
; Each entry is the address of the routine to envoke to service that
; particular command. All the target addresses must be within this
; module. The first table entry is for the opcode set by the
; constant FIRST_OPCODE at the top of this module.
;
tbl_cmd ;jump table for top level commands
goto cm_on ;a - LED on
goto cm_off ;b - LED off
goto cm_salpha ;c - send next alphabetic character
tbl_cmd_n equ $ - tbl_cmd ;number of entries in TBL_CMD
end