;   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