MIRTOS Command Extensions

The Command Extension feature of MIRTOS allows you to implement changes to the command set supported by MIRTOS. The new commands can be executed using MIRTOS scripting, just as the built-in system commands can. As the Purpose: section of the header below explains, new commands can be added, and any existing commands that are part of the MIRTOS Operating System can be either skipped entirely or modified.

The documentation and source code below can be used to recreate the Command Extensions that came with your MIRTOS-based device. Before copying it into a text file, please read through each section and try to understand what it is doing. This code is meant to be both starting point and example. The online documentation (using '?') can be changed or deleted. Please change or delet the examples provided by the '4' through '8' commands, for example. They are only there to provide simple examples.

The source code file name DefCmdExts.Asm (for Default Command Extensions) is used by this example. You may want to add a blank line or two between blocks of copied code in order to improve readability.

/******************************************************************************
*
*                              Command Extensions
*
*  Purpose: Allow interception of any command passed to the Operating System's
*           Command Processor, so the command can be selectively inspected,
*           augmented, discarded, replaced, or otherwise handled before the
*           system parses and takes any action upon that command.
*
*  Assumes: The original Command Extensions that came with MIRTOS begin at
*           FLASH adddress 0x2C00 for the help strings and at 0x3600 for the
*           the start of the code in the 328P.  In both the 1284P and 2560,
*           help strings begin at address 0x0E400 and the code at 0x0EE00.
*
*           This is more of a reminder, actually.  Preserve Register R26 when
*           when changing it to set a 3-BYTE FLASH pointer in the 1284P or
*           2560 (i.e., when RAMPZ is defined.)  Also, do not assume that
*           R27:R26 points to the beginning of the parsing buffer.  If there
*           are spaces preceding the first command character, for example,
*           R27:R26 will be advanced to account for the number of spaces to
*           be skipped.
*
*  Passed:  R31:R30 *CommandExtensions (this subroutine's address via ICALL)
*           R29:R28 pointer to uVars.B[0]
*           R27:R26 pointer to the bBuffer being parsed (format notes below):
*           R23     third  buffered character3, uppercase (but may be 0)
*           R22     second buffered character, uppercase (but may be 0)
*           R21     first  buffered character, uppercase; the command byte
*           R20     string length, or buffered byte count
*           R19:R18 the WORD address of the jump table handler
*
*  Returns: ZERO FLAG TRUE (or set) if NOT handled, FALSE if it was handled
*             (Note that the very first command upon return is a BRNE past
*              the default Operating System command handler to the EXIT.)
*
*  Alters:  If desiring the default command processor to handle it in its
*           usual way (i.e., not intercepted), the only changes that can be
*           safely made are to the FLAGs, R0, R18, R19, R24, R25, R30, and
*           R31.  When no further system command processing is to be done,
*           all of those plus R20 to R23 may be changed without consequence.
*           Save and restore the state of all other Registers used.  With
*           the exception of R26 and R27 also being available, these are
*           the customary Register usage rules for all subroutines.
*
*  Notes:   If unit serial number is invalid, this routine is not be called.
*
*           The expected buffer format on entry:
*                   +-------+-------+------+------+-/ /-+----+-/ /-+---+---+
*       R27:R26 --> | Byte  |Command| Byte | Byte |     |NULL|     |   |   |
*       pointer     | Count |  Char |  #2  |  #3  | ... |(=0)| ... | 0 | 0 |
*                   +-------+-------+------+------+-/ /-+----+-/ /-+---+---+
*                 in:  R20     R21     R22    R23         ^ string terminator
*
*           To activate this code, first write the iHEX image to FLASH then
*           update the WORD pointer at EEPROM address 0x46 (= 70 decimal) to
*           point to the start of the code, NOT where the help strings begin.
*           When using the addresses below, this MIRTOS command would be:
*             "EW 70 = 0x3600" in the 328P MCU or,
*             "EW 70 = 0xEE00" in the 1284P or 2560.
*           Enter the address of CommandExtensions, not CSZ_HelpStrings.
*
*           The code may be placed at another non-system address in FLASH,
*           as long as it is above address 0x0100 and below address 0x3FAD
*           in the 328P and 0x0FFFF in either the 1284P or 2560.
*
******************************************************************************/

The Assembler instructions begin at the start of the next block. Notice that the first few lines verify that some target microcontroller is specified. Multiple MCUs are supported through conditional assembly. The .ORGs allow you to change where the Assembler is to begin placing code in FLASH memory. All of the character strings are placed after that. None of them include a Carriage Return or a New Line ("\r\n" = 0x0D, 0x0A) character, since MIRTOS automatically sends both characters after each NULL-terminated string is output. It stops upon encountering the first empty string (i.e., a string of zero length, which is when the first BYTE is zero.) That means that the help string block must end with two (2) consecutive NULL BYTEs in order to terminate help output.

.IFNDEF PROCESSOR               ; You may want to explicitly specify an MCU
   PROCESSOR = 328              ;  above, but if not ...
   .PRINT "Default 328P processor selected."
.ENDIF

.INCLUDE "CommonDefs.Def"       ; TRUE, FALSE, YES, NO, BIT_PV_*, et.al.
.INCLUDE "BinDefs.Def"          ; The B0 to B11111111 definitions
.IF PROCESSOR == 328
   .INCLUDE "IOM328P.Def"       ; Register definitions for the ATmega328P
   .INCLUDE "Addresses328.Def"  ; All 328P system routines' Jump Table addresses
   .INCLUDE "SystemSRAM328.Def" ; Names & addresses of 328P system SRAM variables
   .ORG  0x02C00
.ELSE
   .ORG  0x0E400
   .IF PROCESSOR == 1284
      .INCLUDE "IOM1284P.Def"
      .INCLUDE "Addresses1284.Def"
      .INCLUDE "SystemSRAM1284.Def"
   .ELSE
      .INCLUDE "IOM2560.Def"
      .INCLUDE "Addresses2560.Def"
      .INCLUDE "SystemSRAM2560.Def"
   .ENDIF
.ENDIF
.INCLUDE "SystemEEPROM.Def"     ; Names and addresses of system EEPROM variable
.INCLUDE "SystemFLASH.Def"      ; Names and addresses of system FLASH constants

The next source code block is rather long, but it contains all of the online help strings which are output in response to the "?" command. Since strings take up so much FLASH, the online help was done this way so they are more easily removed or changed. Be sure to copy the entire block below.

/******************************************************************************
*
*  Assumes: The HelpStringTable and all the strings pointers which is contains
*           all point into the same segment (i.e., the same ACSZ_* >> 16.)
*
*  Notes:   This series of constant Help Strings leaves only a few BYTEs
*           between itself and the CommandExtensions() subroutine.  An easy
*           way to allocate more FLASH for help strings is to change the .ORG
*           statements in the conditional above to lower addresses.
*
******************************************************************************/

CSZ_HelpStrings:                ; Each an ASCIIz string (i.e., NULL terminated)
   .STRING  "? displays this help sequence (** - means intercepted)"
   .STRING  "0 turns all 3 EISP test outputs (B0, B1, and B2) OFF"
   .STRING  "1 turns those same 3 EISP test outputs all ON"
   .STRING  "2 integer to 2 post-decimal digits formatting example"
   .STRING  "3 same as '2', except to 3 digits"
   .STRING  "4 same as '2', except to 4 digits (**)"
   .STRING  "5 same as '2', except to 5 digits (**)"
   .STRING  "6 same as '2', except to 6 digits (**)"
   .STRING  "7 was unused (**)"
   .STRING  "8 was unused (**)"
   .STRING  "9 displays command line parsing results"
   .STRING  "A displays an Analog value (0 to 10)"
   .STRING  "B0 (in Manager mode) reloads Boot Block at FLASH address 0"
   .STRING  "C calculates a CRC over a string"
   .STRING  "D displays EEPROM, SRAM or FLASH memory contents"
   .STRING  "E allows EEPROM or SRAM to be edited, copied, filled, or zeroed"
   .STRING  "F displays the MCU fuse (0, 2, 3) or lock (1) bytes"
   .STRING  "G displays the MCU signature (0, 2, 4) or OSCCAL (1) bytes"
   .STRING  "H displays last 10 (H*) or all 24 (H**) MCU signature bytes"
   .STRING  "I   (unused)"
   .STRING  "J first letter of input for the Admin level password"
   .STRING  "K signed LONG division example with results in Registers"
   .STRING  "L ULONG division example (or) lock/logout/logoff"
   .STRING  "M displays HEAP and STACK memory usage and slack bytes"
   .STRING  "N accesses the nRF24L01+ command set"
   .STRING  "O sends ON or OFF commands to outputs 0 through 5"
   .STRING  "P accesses the WS2812 pixel command set"
   .STRING  "Q example of the PushAll() and PopAll() subroutines"
   .STRING  "R displays the MCU Registers, and (optionally) the stack contents"
   .STRING  "S displays the script pointer and/or allows it to be changed"
   .STRING  "T updates the system time, weekday, date, and year"
   .STRING  "U is the same as typing \"DU\" - display uVars.B[] array"
   .STRING  "V   (same as 'U')"
   .STRING  "W   (unused)"
.IF PROCESSOR == 328
   .STRING  "X erases a 128-byte block of FLASH"
.ELSE
   .STRING  "X erases a 256-byte block of FLASH"
.ENDIF
   .STRING  "Y tests the Watchdog timer function (only in Admin mode)"
   .STRING  "Z allows access to the sleep mode BYTE"
   .STRING  ": parses iHex strings into EEPROM, SRAM or FLASH memory"
   .STRING  "; starts a comment; disregards the line that point on"
   .STRING  "< decreases the CPU speed and revises UBRR to maintain the COM rate"
   .STRING  "= displays the Registers, with R24 = CPU MHz"
   .STRING  "> increases the CPU speed and adjusts UBRR to keep the same COM rate"
   .STRING  "@ call a subroutine (in Manager mode) or single step a Pixel sequence"
   .STRING  " "                        ; (this is just a spacing line)
   .STRING  "   toggle keys (first byte on a line; display suppressed):"
   .STRING  "_ serial port received character echoing"     ; BIT_SER_ECHO
   .STRING  "' show command before processing"             ; BIT_SER_SHOWCMD
   .STRING  "# EEPROM write access notifications"          ; BIT_DDT_EEPROM
   .STRING  "[ suppress script change info"                ; BIT_SER_QUIET
   .STRING  "] reports as script lines are processed"      ; BIT_DDT_SCRIPT
   .STRING  "\" shows nRF block details (with ICALLs)"     ; BIT_DDT_DETAILS
   .STRING  "+ shows sequencer mode change summaries"      ; BIT_DDT_SEQCHG
   .STRING  ") shows sequencer search details"             ; BIT_DDT_SEARCH
   .STRING  "} register dump and optional stack dump"      ; BIT_DDT_REGS
   .STRING  "   reserved user-defined firstkeys (MIRTOS only toggles them)"
   .STRING  "* in bFlags_Debug (meant for LEDs)"           ; BIT_DDT_LEDS
   .STRING  "% in bFlags_Debug"                            ; BIT_DDT_USER
   .STRING  "^ in bFlags_Serial"                           ; BIT_SER_UPARROW
   .STRING  "| in bFlags_Serial"                           ; BIT_SER_VERTBAR

HelpStringTrailer:
   .BALIGN 256, 0, 255

.INCLUDE "DefCmdExts.Ptr"            ; Or the file name which you've chosen

.IFNDEF ACSZ_HelpStrings
   .PRINT "Assemble again to initialize label addresses."
   ACSZ_HelpStrings   = 0x2D00
   AHelpStringTrailer = 0x3600
.ENDIF

.IF PROCESSOR != 328                 ; Not needed for the 328P (Max = 0x7FFF)
   AHH_FirstHelpString = ACSZ_HelpStrings   >> 16
   AHH_HelpTrailer     = AHelpStringTrailer >> 16
   .IF AHH_FirstHelpString != AHH_HelpTrailer
      .ERROR "BYTE 3 of first Help String and Help Trailer differ!"
   .ENDIF
.ENDIF

In this next short section, there are two additional "First Key" selection options which have been commented out and are shown as "greyed out" text. This is the only place in this example where this is done. It is just intended to be a reminder to discern lines which might look like code, but are really just comments. It also shows the use of bFlags_Debug, which is another system BYTE that has flags (i.e., BITs) for your use.

Source code for the "DumpParsingBuffer" routine is also included as part of the published Command Extension source made available.

/******************************************************************************
*
*   Note: The term "command buffer" is also be called the "parsing buffer",
*         although those terms might not ALWAYS be synonymous.
*
******************************************************************************/

.IF PROCESSOR == 328
   .ORG  0x03600
.ELSE
   .ORG  0x0EE00
.ENDIF

CommandExtensions:                   ; Example of how to use debugging output
   CALL    ADumpRegisters            ; The firstkey "}" toggles DumpRegisters()

   PUSH    R31                       ; Optional code to dump the parsing buffer
;----------
;UserFlag_Option1:                   ;  on one of three user firstkey toggles
;  IN      R31, bFlags_Serial        ; bFlags_Serial = GPIOR2 (at SRAM 0x4B)
;  SBRC    R31, BIT_SER_UPARROW      ; The "^" firstkey toggles this flag
;UserFlag_Option2:
   IN      R31, bFlags_Serial        ; The "|" firstkey toggles this flag
   SBRC    R31, BIT_SER_VERTBAR
;UserFlag_Option3:
;  LDS     R31, bFlags_Debug         ; The "%" firstkey toggles this flag
;  SBRC    R31, BIT_DDT_USER         ; If the flag is OFF, skip over the RCALL
;----------
   RCALL   ADumpParsingBuffer        ; Display the parsing (or command) buffer
   POP     R31                       ; Restore the stack

This is where the first character of the commands to intercept or add are checked and the service routine for each selected. Since each check is followed by a BREQ BRranch if EQual instruction, once a match is found, execution flow does not come back to this section until after the next command is input. The "W" command check is commented out, but left as an example. This is where additional command checking should be added, if desired. The CmdExt_NotHandled exit point is used by several subroutines below.

CmdExt_CheckCommand:
   CPI     R21, '?'                  ; If the command began with the key '?'
   BREQ    ACmdExt_Question          ;  jump to that subroutine
   CPI     R21, '4'                  ; If it was a '4', suppress whatever that
   BREQ    ACmdExt_Handled           ;  key did
   CPI     R21, '5'                  ; If it was a '5', check a little further
   BREQ    ACmdExt_5                 ;  before deciding what to do
   CPI     R21, '6'                  ; If it was '6', jump there
   BREQ    ACmdExt_6
   CPI     R21, '7'                  ; Similiarly, if it was '7', jump there
   BREQ    ACmdExt_7                 ;  (and so on)
   CPI     R21, '8'
   BREQ    ACmdExt_8
   CPI     R21, 'U'                  ; uVars.B (or "V Register") indexed access
   BREQ    ACmdExt_UorV
   CPI     R21, 'V'
   BREQ    ACmdExt_UorV
;  CPI     R21, 'W'                  ; Call a subroutine at a specific address
;  BREQ    ACmdExt_W                 ;  in FLASH (at 0x1000, as coded below)

CmdExt_NotHandled:
   SEZ                               ; Set the ZERO flag to inform the calling
   RET                               ;  subroutine "NOT HANDLED" (you do it)

The code below causes all of the help strings to be output to the COM1 serial port. It "blocks", meaning that that it completes execution before returning to the caller. It continues looping and printing strings until the first zero-length string (or "NULL string") is encountered. This method allows the help string block to be easily changed.

;------------

CmdExt_Question:
   CPI     R20, 1                    ; If the command was the single character,
   BRNE    ACmdExt_NotHandled        ;  '?', handle it here, otherwise, just

AHH_FirstHelpString =  ACSZ_HelpStrings >> 16
AHi_FirstHelpString = (ACSZ_HelpStrings >> 8) & 0xFF
ALo_FirstHelpString =  ACSZ_HelpStrings & 0xFF

.IF PROCESSOR > 328
   LDI     R26, AHH_FirstHelpString  ; Needed if FLASH address can be > 0xFFFF
.ENDIF
   LDI     R31, AHi_FirstHelpString  ; Point 'Z' at the first help string
   LDI     R30, ALo_FirstHelpString  ; Note: print routines leave 'Z' unchanged

CmdExt_HelpPrintLoop:
   MOVW    R24, R30                  ; Point R25:R24 at the next string and
   CALL    AstrlenPGM                ;  determine its length; if the ZERO flag
   BREQ    ACmdExt_Handled           ;  is set upon return, its a NULL string
   MOVW    R22, R30                  ; Save the pointer to the string that was
   ADD     R30, R24                  ;  just checked and add the string length
   ADC     R31, R25                  ;  + 1 (for the NULL) to 'Z' to set it up
   ADIW    R30, 1                    ;  for the next loop
   MOVW    R24, R22                  ; Restore the pointer to the string which
   CALL    APrintASCIIz              ;  was just checked then print it
   CALL    APrintNewLine             ; After each string, print a CR then a LF
   RJMP    ACmdExt_HelpPrintLoop     ; Loop back to check the next string

The next little block only generates six (6) BYTEs into the assembly image downloaded to the device. It shows how the CPSE Compare and Skip if Equal instruction can be used to determine whether to handle the command as an extension or pass it back to the Operating System to handle it.

Register R1 is used for zero comparisons since it should always have the value zero, or if used, the value zero restored into it. R22 contains the second BYTE in the command buffer, which is the NULL command string terminator for single-character commands. If it is a multiple-character command, the first RJMP is taken, otherwise, for single-character commands, the second RJMP is taken.

;------------

CmdExt_5:                            ; This is as terse as it can be
   CPSE    R22, R1                   ; Process as before if a nonzero character
   RJMP    ACmdExt_NotHandled        ;  is found after the '5', otherwise
   RJMP    ACmdExt_Handled           ;  suppress the prior keystoke handling

Next is the code which handles any commands that begin with the number "6". To support multiple processors and multiple possible source code locations, this code has nested conditional assembly blocks. Precisely one (1) of the five (5) RCALLs below is assembled into the output image. Like the "5" command above, this only generates six (6) BYTEs in the assembled image, regardless of the processor selected.

;------------

.IFNDEF ACSZ_Key6
   ACSZ_Key6 = 0
   ACSZ_Key7 = 0
.ENDIF
AHH_CSZ_Key6 =  ACSZ_Key6 >> 16
AHi_CSZ_Key6 = (ACSZ_Key6 >> 8) & 0xFF
ALo_CSZ_Key6 =  ACSZ_Key6 & 0xFF

CmdExt_6:
   LDI     R25, AHi_CSZ_Key6         ; Method 1: limited Register access
   LDI     R24, ALo_CSZ_Key6
.IF PROCESSOR == 328
   RCALL   ACmdExt_PrintASCIIz
.ELSE
.IF AHH_CSZ_Key6 == 0
   RCALL   ACmdExt_PrintASCIIz_0
.ENDIF  ; of ".IF AHH_CSZ_Key6 == 0"
.IF AHH_CSZ_Key6 == 1
   RCALL   ACmdExt_PrintASCIIz_1
.ENDIF  ; of ".IF AHH_CSZ_Key6 == 1"
.IF AHH_CSZ_Key6 == 2
   RCALL   ACmdExt_PrintASCIIz_2
.ENDIF  ; of ".IF AHH_CSZ_Key6 == 2"
.IF AHH_CSZ_Key6 == 3
   RCALL   ACmdExt_PrintASCIIz_3
.ENDIF  ; of ".IF AHH_CSZ_Key6 == 3"
.ENDIF  ; of ".IF PROCESSOR == 328"

These are the last two (2) of the four (4) exit points shared among the Command Extension routines. The first one is CmdExt_NotHandled, which was already encountered above. The last one is CmdExt_HandledPopAll, which is the CmdExt_7 subroutine exit in the code block after this one.

CmdExt_Handled:
   CLZ                               ; Set ZERO to indicate the "command was
CmdExt_JustReturn:                   ;  handled" in the return (or if the flag
   RET                               ;  is already set, just exit)

This subroutine handles any commands that begin with the number "7". Compare it with CmdExt_6 above. Notice how this subroutine starts with a CALL to PushAll and ends with a JUMP to PopAll. This is a good method when more than just a few Registers need to be preserved using PUSH and POP, and execution time is not of higher importance. This is often the case in a command handler. This routine and the one above each just print a string and exit. They are provided as examples and starting points for your command extensions.

;------------

AHH_CSZ_Key7 =  ACSZ_Key7 >> 16
AHi_CSZ_Key7 = (ACSZ_Key7 >> 8) & 0xFF
ALo_CSZ_Key7 =  ACSZ_Key7 & 0xFF

CmdExt_7:                            ; Method 2: for access to all Registers,
   RCALL   ACmdExt_PushAll           ;  start with PushAll()
   LDI     R25, AHi_CSZ_Key7
   LDI     R24, ALo_CSZ_Key7
.IF PROCESSOR == 328
   RCALL   ACmdExt_PrintASCIIz
.ELSE
.IF AHH_CSZ_Key7 == 0
   RCALL   ACmdExt_PrintASCIIz_0
.ENDIF  ; of ".IF AHH_CSZ_Key7 == 0"
.IF AHH_CSZ_Key7 == 1
   RCALL   ACmdExt_PrintASCIIz_1
.ENDIF  ; of ".IF AHH_CSZ_Key7 == 1"
.IF AHH_CSZ_Key7 == 2
   RCALL   ACmdExt_PrintASCIIz_2
.ENDIF  ; of ".IF AHH_CSZ_Key7 == 2"
.IF AHH_CSZ_Key7 == 3
   RCALL   ACmdExt_PrintASCIIz_3
.ENDIF  ; of ".IF AHH_CSZ_Key7 == 3"
.ENDIF  ; of ".IF PROCESSOR == 328"

CmdExt_HandledPopAll:
   CLZ                               ; Set "command handled" before jumping to
   RJMP    ACmdExt_PopAll            ;  PopAll(), which preserves all FLAGs

The next example is the extension executed when "8" is entered. The IFNDEF If Not DEFined conditional is needed to prevent the assembler from exiting with an "invalid operands" error because the address of Display_Vin is undefined. The Display_Vin source code and documentation are contained on another example page. If ADisplay_Vin (i.e., the Address of the Display_Vin label, or subroutine entry point) may be defined if it is included in the set of files assembled.

Rather than blindly calling Display_Vin, this example demonstrates the use of the MIRTOS ICALL_IfValid_PGM function. The target subroutine's address is loaded into R25:R24 and checked; if the WORD at that address (presumed to be an instruction opcode) is neither 0x0000 or 0xFFFF, that routine will be called. The value 0x0000 is a NOP No Operation (just a placeholder) and the value 0xFFFF indicates and unused FLASH location. ICALL_IfValid_PGM returns if it finds either of those two values.

;------------

.IFNDEF ADisplay_Vin
   .IF PROCESSOR == 328
      ADisplay_Vin = 0x3840
      .PRINT "Setting address ADisplay_Vin = 0x3840."
   .ELSE
      ADisplay_Vin = 0xF040
      .PRINT "Setting address ADisplay_Vin = 0xF040."
   .ENDIF
.ENDIF
AHH_Display_Vin =  ADisplay_Vin >> 16
AHi_Display_Vin = (ADisplay_Vin >> 8) & 0xFF
ALo_Display_Vin =  ADisplay_Vin & 0xFF

CmdExt_8:                            ; Replace the '8' keypress
;----------
;  CALL    ADisplay_Vin              ; Rather than call it directly, ...
;  RJMP    ACmdExt_Handled
;------ better:
   LDI     R25, AHi_Display_Vin      ; Use the system routine which will first
   LDI     R24, ALo_Display_Vin      ;  verify that that address is not 0xFFFF

CmdExt_IfValidPGM:                   ; Call the subroutine which displays the
   CALL    AICALL_IfValid_PGM        ;  value the Vcc, as the MCU chip sees it
   RJMP    ACmdExt_Handled           ; This is the common If_Valid exit point

; The ICALL_IfValid_PGM above uses a 2-BYTE (i.e., R25:R24) pointer.
; Either of these accept a 3-BYTE (i.e., R26:R25:R24) pointer:
;  CALL    AICALL_IfValid3BYTE_PGM   ; To address 0x3A400
;  CALL    AICALL_IfV3_PGM           ; To address 0x3AD00 (in hidden FLASH)
;  RJMP    ACmdExt_Handled

This was simply a single-letter shortcut that was used to execute a longer command. Every line contained in this code block is a comment.

;----------
;CmdExt_W:                           ; Replace the 'W' command (now unused)
;  LDI     R25, 0x12
;  LDI     R24, 0x34                 ; Point to the address 0x1234 and use the
;  RJMP    ACmdExt_IfValidPGM        ;  existing code (so no login is needed)

Section 2 - U or V command handler additions

The default U and V commands built into MIRTOS also have a Command Extension that comes with the Operating System. Due mainly to size and the ability to seperate them into another file, the source code and documentation for these subroutines is contained on a separate U or V Command Extension page.

If you have no need for these commands, omit the next block as can bu

;----------

.INCLUDE "CmdExtUorV.Asm"            ; The Assembly command to pull the file in

Section 3 - Command Extension shared subroutines

Rather than have multiple inline 4-BYTE FAR CALLs to and JMPs into the Operating System, this block contains a single FAR JMPs to each of those used above. If access to one of these system routine targets is required twice, using NEAR RCALLs or RJMPs with these generates the same amount of image code, which is 8 BYTEs. After any needs to be accessed more than twice, less code is generated by using these than by using multiple inline FAR CALLs and JMPs. The comment below, embedded in the source code, explains the purpose.

/******************************************************************************
*
*  These single, shared, local, FAR JMPs to system routine can be reached by
*  any subroutine in the Command Extensions code using a 2-BYTE RCALL.
*
******************************************************************************/

.IF PROCESSOR == 328            ; OPT  They do NOT have to be located at these
   .ORG  0x03776                ; OPT   particular addresses; they were placed
.ENDIF                          ; OPT   here so they immediately precede the
.IF PROCESSOR == 1284           ; OPT   the CSZ_Key? strings and the
   .ORG  0x0EF58                ; OPT   DumpParsingBuffer() routine
.ENDIF                          ; OPT  Placing them here allows additional
.IF PROCESSOR == 2560           ; OPT   CmdExt_CheckCommand comparisons and
   .ORG  0x0EF4C                ; OPT   2-BYTE jumps (BREQs) as used above to
.ENDIF                          ; OPT   more easily be added later

CmdExt_PushAll:
   JMP     APushAll                  ; Push Registers R31, R30, ... , R1, R0
CmdExt_PopAll:
   JMP     APopAll                   ; Pop Registers R0, R1, ... , R30, R31
CmdExt_DumpRegisters:
   JMP     ADumpRegisters            ; Dump Registers, toggled by '}' firstkey
CmdExt_DumpRegs_Begin:
   JMP     ADumpRegs_Begin           ; Unconditional Register dump
CmdExt_strchr:
   JMP     Astrchr                   ; Search for a character in a string
CmdExt_strcpy:
   JMP     Astrcpy                   ; Copy a string from SRAM to SRAM

These print subroutines are available to be used by all routines within the Command Extension block. Conditional assembly is used so only those for the target processor are assembled into the output file. The last two lines generate the character strings that two of the extension subroutines above output.

.IF PROCESSOR == 328
CmdExt_PrintASCIIz:                  ; Print a string that is in FLASH memory,
   JMP     APrintASCIIz              ;  passing the pointer to it in R25:R24

.ELSE                                ; These two are for both 1284P and 2560
CmdExt_PrintASCIIz_0:
   PUSH    R26                       ; Set the MSByte of the string's address
   LDI     R26, 0                    ;  to 0
CmdExt_PrintASCIIz_Common:
   CALL    APrintASCIIz              ; Print a string that is in FLASH that
   POP     R26                       ;  is in FLASH using R26:R25:R24 as the
   RET                               ;  pointer to it, restore R26, and exit

CmdExt_PrintASCIIz_1:
   PUSH    R26
   LDI     R26, 1                    ; Set the string address MSByte to 1
   RJMP    ACmdExt_PrintASCIIz_Common;

.IF PROCESSOR == 2560
CmdExt_PrintASCIIz_2:                ; For the 2560, the other 2 possibilities
   PUSH    R26                       ;  are addresses at 2:R25:R24 and ...
   LDI     R26, 2                    ;
   RJMP    ACmdExt_PrintASCIIz_Common;

CmdExt_PrintASCIIz_3:
   PUSH    R26
   LDI     R26, 3                    ;  ... and 3:R25:R24
   RJMP    ACmdExt_PrintASCIIz_Common;
.ENDIF  ; of ".IF PROCESSOR == 2560"
.ENDIF  ; of ".IF PROCESSOR == 328"

;-----------------------------------

CSZ_Key6:  .STRING  "Keypress '6' handler\r\n"
CSZ_Key7:  .STRING  "Keypress '7' handler\r\n"

Section 4 - Display the parsing buffer

DumpParsingBuffer is an optional subroutine included in the default MIRTOS Command Extensions. You might have noticed the call to it between the blocks of greyed out comments at the at the beginning of this page. It can be toggled on or off using the "|" (vertical bar) firstkey as it is shown there in the source code.

The DumpParsingBuffer source code and documentation are contained on a different page. In order to build the Command Extensions image file to the same image that it came in MIRTOS, this subroutine should be included in the DefCmdExts.Asm source code file at this point, as shown in this last block.

.INCLUDE "DumpParsingBuffer.Asm"     ; The Assembly command to pull the file in

.BALIGN 16, 0xFF, 15                 ; Pad out to sentence boundary with 0xFFs

The next step is to create the source code file from the sixteen (16) source code blocks above. One way to do it without using a word processing program is shown below. After all of the source code blocks have been copied and the file closed (using Control-Z), the file size of the resulting DefCmdExts.Asm should be about 21,500 BYTEs. Certainly, if you're more familiar with a word processing program that can create pure text, by all means, use it.

The second step is to create the initial DefCmdExts.Ptr file. It can contain a single blank line or a comment line. It will get rebuilt by the Assembly batch process but needs to have an initial file to open or the Assembler will exit with "Error: can't open DefCmdExts.Ptr" and create nothing.

C:\TestDir>Copy CON DefCmdExts.Asm
   (copy and paste all sixteen (16) code blocks above)
^Z
        1 file(s) copied.

C:\TestDir>Copy CON DefCmdExts.Ptr
  ; (a single space or this line then Enter and Control-Z)
^Z
        1 file(s) copied.

C:\TestDir>                                                                     

If CmdExtUorV.Asm and DefCmdExts.Asm are to be included, be sure that they have been created before issuing the first Assemble command below. Remember to Assemble three times. The first time should recreate the DefCmdExts.Ptr file with all labeled addresses. The second time imports all of those addresses constants and inserts them into JUMP, CALL, and STRING addresses. If any JMP or CALL changed to a RJMP or RCALL, some labeled address might change. This will be reflected in the .Ptr file. The third Assembly should create the final downloadable image. The three (3) Assemble commands should result in screen output similar to the results shown below.

C:\TestDir>Assemble DefCmdExts
Default 328P processor selected.
Assemble again to initialize label addresses.
Setting address ADisplay_Vin = 0x3840.
Assemble again to define ACSZ_PBHeader.

C:\TestDir>Assemble DefCmdExts
Default 328P processor selected.
Setting address ADisplay_Vin = 0x3840.

C:\TestDir>Assemble DefCmdExts
Default 328P processor selected.
Setting address ADisplay_Vin = 0x3840.

C:\TestDir>                                                                     

It's always a good practice to search the resulting .Dmp Assemble listing file for any unresolved NEAR RJMP or RCALL instructions. Simply search for the string ".+0" to do that. There should be none in the CmdExtUorV.Dmp file created.

To change the target processor, open DefCmdExts.Asm in a text processor and find the line that with ".IFNDEF PROCESSOR" (above line 68.) This should be the first line which is not a part of the header comment. Insert a line before that and put "PROCESSOR = 1284" or "PROCESSOR = 2560" on that line, save, and close the file. All constants are case sensitive. Replace the DefCmdExts.Ptr with a one again containing the single blank line. Run the Assemble batch file three times.

*           in the 328P and 0x0FFFF in either the 1284P or 2560.
*
******************************************************************************/

.IFNDEF PROCESSOR               ; You may want to explicitly specify an MCU
   PROCESSOR = 328              ;  above, but if not ...
   .PRINT "Default 328P processor selected."
.ENDIF

Before and after example.

*           in the 328P and 0x0FFFF in either the 1284P or 2560.
*
******************************************************************************/

PROCESSOR = 1284                ; Insert this line to select the MCU

.IFNDEF PROCESSOR               ; You may want to explicitly specify an MCU
   PROCESSOR = 328              ;  above, but if not ...
   .PRINT "Default 328P processor selected."
.ENDIF

The DefCmdExts.Hex file contains the final iHex image and its trimmed version is in the DefCmdExts.Trm file. Its memory usage is from 0x2C00 through 0x37FF (3,072 BYTES) in the 328P and from 0x0E400 to 0x0EFEF (3,056 BYTEs) in both the 1284P and 2560.

Section 5 - Loading the image into the device

For .TRM files which contain only a few lines, it's simplest just to copy and paste into the CommTool terminal buffer. But for larger files like this one (over 9,000 BYTEs), it easier to configure CommTool to send the file to the your device. To do that, open the CommTool.Cfg file and add a line similar to one of these:

F6=Send DefCmdExts.Trm
AF6=Send C:\TestDir\DefCmdExts.Trm   ; Accepts Fully Qualified Filenames
CF6=Bytewise DefCmdExts.Trm          ; Waits for a reply to each BYTE

Use "Bytewise" in place of "Send" if there are synchronization issues when using the "Send" keyword. Of course, you can select which soft Function key to use. "AF6" means Alt-F6, "CF6" is Control-F6 and "SF6" would select Shift-F6. All of the function keys and their Alt, Shift, and Control modified versions can usually be specified. One you've restarted CommTool, make sure that you're logged on in Manager Mode and press the specified function key to transmit the file.