; ***************************************************************
; * Copyright (C) 2005, Embed Inc (http://www.embedinc.com) *
; * *
; * Permission to copy this file is granted as long as this *
; * copyright notice is included in its entirety at the *
; * beginning of the file, whether the file is copied in whole *
; * or in part and regardless of whether other information is *
; * added to the copy. *
; * *
; * The contents of this file may be used in any way, *
; * commercial or otherwise. This file is provided "as is", *
; * and Embed Inc makes no claims of suitability for a *
; * particular purpose nor assumes any liability resulting from *
; * its use. *
; ***************************************************************
;
; 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 "qq2.ins.aspic"
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 0 ;first valid opcode in opcodes jump table
lbank equ 0 ;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.
;
defram gbankadr
;
;***********************************************************************
;
; Local state.
;
defram lbankadr
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
.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
jump 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
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 ?
jump 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 ?
jump 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 0-N table entry number in REG12. Now
; jump to the selected table entry, which will in turn jump to the
; routine to perform the selected command.
;
;
; This section is for processors that have one address per
; program memory word, like the 12, 16, and 17 families.
;
if adr_word == 1
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
endif
;
; This section is for processors that have 2 addresses per
; program memory word, like the 18 family. The table
; entry number must be multiplied by the addresses per
; word to make the address offset from the start of the
; table.
;
if adr_word == 2
movlw high tbl_cmd ;get table start address high byte
movwf pclath ;init jump address high byte
btfsc reg12, 7 ;add entry offset to address high byte
incf pclath
rlncf reg12, w ;make low byte of table offset
andlw b'1111110'
addlw low tbl_cmd ;make low byte of entry address
skip_ncarr ;no carry into entry address high byte ?
incf pclath ;propagate the carry
movwf pcl ;jump to the selected jump table entry
endif
if adr_word > 2 ;no code here to handle this case ?
error "No code in the QQ2_CMD module to handle ADR_WORD > 2."
endif
;
;***********************************************************************
;
; 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 XXX
;
; This is an example command.
;
cm_xxx command
jump cmd_ack ;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
jump cm_nop ; 0 - no operation but responds with ACK
jump cm_xxx ; 1 - example command
tbl_cmd_n equ ($ - tbl_cmd) / adr_word ;number of entries in TBL_CMD
end