MIRTOS uVars.B[] editing by array index Command Extension

The uVars.B[] array is the block of SRAM in a constant location which is allocated and reserved for User Variables (or V-Registers.) As can be done with all of SRAM, they can be displayed using the MIRTOS "DS" (Display SRAM) command and edited using the "ES" Edit SRAM command. This Command Extension allows them to be edited using a "U" or "V" command as well. For example, you could use "V 4 = 200" to set uVars.B[4] to 200 instead of using the more explicit "ES 0x284 = 200".

This Command Extension comes with MIRTOS. Although the source code below only assembles to us about 170 BYTEs of FLASH, if you don't use V-Registers, it can be removed. This source code is contained in two subroutines, both of which are documented here. It is probably best to place both subroutines in the same file. CmdExtUorV.Asm is the filename used by this example and is the file pulled in by the main Command Extensions source code module. Again, you may want to add a blank line or two between blocks of code as you copy it in order to improve readability.

/******************************************************************************
*
*                            Routine "CmdExt_UorV"
*
*  Purpose: Verify that the type of 'V' command issues is in the syntax that
*           is requesting V-Register(s) be changed (e.g., "V 11 += 2 3 4".)
*           Examine and modify the command buffer, implementing the command
*           to change one or more value(s) in the User Variable (uVars.B[##])
*           SRAM array.  Change the 'V' command to the Operating System's
*           built-in "ES" (Edit SRAM) system command to perform the change(s).
*
*  Assumes: The buffer being parsed is expected to be the command buffer, but
*           it need not necessarily be that.  The command buffer has the same
*           address MSByte for all elements (e.g., 0x230 to 0x27F) and the
*           buffer being parsed should also have that attribute.  Some BYTEs
*           should be available in the buffer passed, if needed - up to
*           either three (3) for the 328P, or four (4) for the 1284P or 2560.
*
*  Passed:  R29:R28 *uVars.B[0]
*           R27:R26 *bBuffer being parsed (format notes below):
*           R23     third  buffered character, 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
*
*  Returns: ZERO FLAG  TRUE if NOT handled, FALSE if it was handled
*                      (the first command upon return is BRNE to the EXIT)
*
*  Notes:   This is just a reminder that the only safe changes to make when
*           MIRTOS is to process the command are those to the FLAGs and to
*           Registers R0, R18, R19, R24, R25, R30, and R31.  Of course, the
*           point of this routine is to possibly modify the command string!
*
******************************************************************************/

This is the entry point to these subroutines. The main Command Extension routine passes execution here after only verifying that the first command letter is either a "U" or a "V", either uppercase or lowercase. There must be an equal sign within the command for this to be the syntax that these extensions support.

CmdExt_UorV:
   MOV     R18, R22                  ; Save 2nd buffered character for exit
   MOVW    R24, R26                  ; R27:R26 passed is the buffer pointer
   LDI     R22, '='                  ; Search for the first '='; if not found,
   RCALL   ACmdExt_strchr            ;  the command is not in the format that
   BREQ    ACmdExt_UorV_Exit         ;  this routine is designed to handle

;   Setup for the strchr() call (above and below):
; Pass:    R25:R24 byte * szBuffer address to examine
;              R22 byte   bChar to find first occurrence
; Returns: R25:R24 byte * pointer to the character in the buffer or NULL
;  (Also):    ZERO FLAG   TRUE if NOT found (R25:R24 = NULL), FALSE if found
; Alters:  Only R24, R25, and the FLAGS are changed
;
CmdExt_UorV_CheckComment:
   MOVW    R30, R24                  ; Save the pointer to '=' returned
   MOVW    R24, R26                  ; Reload the buffer pointer then set the
   LDI     R22, ';'                  ;  search character (comment delimiter)
   RCALL   ACmdExt_strchr            ; Only check the relative position of the
   BREQ    ACmdExt_UorV_Process      ;  '=' BYTE when there was a comment
   SUB     R24, R30                  ; If the first '=' is after the ';', it is
;- SBC     R25, R31   ; (not needed) ;  within the comment, so process as the
   BRLO    ACmdExt_UorV_Exit         ;  original command by just returning

As the comment for the first line of source code below states, the helper routine being called handles most of the command transformation. Since more Registers are needed to parse and modify the command buffer, it's much easier to use a subroutine at this point. If the helper routine rejects the syntax, it will return with both the Registers and the buffer preserved so the original, default Operating System command handler can process the command.

CmdExt_UorV_Process:
   RCALL   ACmdExt_UorV_Helper       ; The subroutine does most of the work; it
   BRNE    ACmdExt_UorV_Exit         ;  returns the ZERO flag CLEAR if rejected

;   Setup for the strcpy() call:
;  Passed:  R25:R24 byte * bPtrTo pointer to the destination SRAM buffer
;           R23:R22 byte * bPtrFrom pointer to the source SRAM buffer
;  Returns: R25:R24 byte * the same bPtrTo pointer as the caller passed
;  Alters:  R21, the FLAGs, and the destination buffer specified
;
   MOVW    R24, R26                  ; The source pointer is the buffer address
   ADIW    R24, 1                    ;  plus 1 (skip the string length), then
   MOVW    R22, R24                  ;  the destination pointer is that value
   ADIW    R24, 1                    ;  plus another 1, making room (1 BYTE) to
   RCALL   ACmdExt_strcpy            ;  change the "V" to an "ES" -> Edit SRAM

The last five (5) assembly instructions above and those before CmdExt_UorV_Exit: below are only executed if the helper routine has already made some changes to the command buffer.

Register pair R23:R22 was pointed at the second BYTE in the SRAM buffer by the code above; the call to strcpy() didn't change it. The original SRAM buffer pointer in "X" (R27:R26) has not been modified either, so point "Z" (R31:R30) at it after determining the string length. The task here is to replace the "U" or "V" with "ES" in the command buffer, to update R21, R22, R23 with the first three (3) buffered characters, and to set the new command string length.

;   Setup for the strlen() call:
; Passed:  R25:R24 byte * szBuffer address to examine
; Returns: R25:R24 word   the count of bytes in the ASCIIZ string
;             ZERO FLAG   set (TRUE) when length is zero (and R25:R24 = 0)
;                         clear (FALSE) when length is > 0 (and R25:R24 > 0)
; Alters:  Only R25 and R24 (the value being returned)
;
CmdExt_UorV_PrepareToExit:
   MOVW    R24, R22                  ; The source buffer pointer as used above
   CALL    Astrlen                   ; R24 has the BYTE string length returned
   MOVW    R30, R26                  ; Pointer to the buffer; 1st BYTE is count

; When changing the command to (also) have MIRTOS handle it, some Registers
; and SRAM (buffer) contents will change.  Specifically, set these here:
;    R20  string length, or buffered BYTE count
;    R21  first  buffered character, uppercase; the command BYTE 'E'
;    R22  second buffered character, uppercase; the BYTE 'S'
;    R23  third  buffered character, uppercase; a size modifier or ' '
;
   ST      Z, R24                    ; Replace the prior buffered BYTE count
   MOV     R20, R24                  ;  with that obtained from strlen() call
   LDI     R21, 'E'
   STD     Z+1, R21                  ; Set the 1st buffered character (now 'E')
   MOV     R23, R18                  ; 2nd uppercase buffered char is now 3rd
   LDI     R18, 'S'                  ; Notice that R22 is set from R18 below
   STD     Z+2, R18                  ; Set the 2nd buffered character (now 'S')

This is the "parent" routine's exit point. Whether the command buffer was changed or not, it returns the same result, which is to process either the original command or the modified commmand.

CmdExt_UorV_Exit:
   MOV     R22, R18                  ; Restore/set the 2nd buffered character
   RJMP    ACmdExt_NotHandled        ; Let the Operating System do the work
;- SEZ                ; (alternate   ; The ZERO flag TRUE tells the caller "NOT
;- RET                 ;  method)    ;  HANDLED", or "go ahead and process it"

Section 2 - U or V command handler helper subroutine

A rather long subroutine explanation header follows. It lists quite a few examples of possible command transformations, for each of the three (3) supported microcontrollers. The "Exp." column shows the number of BYTES that the command "expanded." The "+1" is for the "V" to "ES" common to all and the second value is the change in the number of numeric characters when transforming a V index to an absolute SRAM address.

/******************************************************************************
*
*                         Routine "CmdExt_UorV_Helper"
*
*  Purpose: Assist the routine above.  Since more than just a few Registers
*           need to be preserved and restored, a routine that begins with a
*           PushAll() and exits with a PopAll() is a great way to do that.
*
*  Assumes: The buffer being parsed may be the command buffer, but it need
*           not necessarily be.  The buffer has the same address MSByte for
*           all elements (e.g., 0x230 to 0x27F for the command buffer.) Some
*           BYTEs should still be available in the buffer, if needed.  Up to
*           either three (3) for 328, or four (4) for the 1284 and 2560.
*
*  Passed:  R29:R28 *uVars.B[0]
*           R27:R26 *bBuffer being parsed (format notes below):
*           R22     the character ';', from the comment check
*           R21     first buffered character, uppercase; the command byte 'V'
*           R20     string length, or buffered byte count
*           R18     second buffered character, uppercase
*
*  Returns: The ZERO flag  CLEAR if rejected for any reason (or)
*                          SET if processed and the buffer was changed
*
*  Alters:  The command buffer and the FLAGS.  Since it uses the PushAll()
*           subroutine on entry and the PopAll() subroutine on exit, none of
*           the caller's Register values are (or even can be) modified.
*
*  Example: Some example commands, with their resulting buffer changes:
*       Command       328P       Exp.     1284P      Exp.       2560      Exp.
*     -----------  ------------------  ------------------  -------------------
*     V 0 += 1     ES 640 += 1   +1+2  ES 864 += 1   +1+2  ES 1472 += 1   +1+3
*     V 0x2 += 1   ES 642 += 1   +1+0  ES 866 += 1   +1+0  ES 1474 += 1   +1+1
*     V 10 = 1 2   ES 650 = 1 2  +1+1  ES 874 = 1 2  +1+1  ES 1482 = 1 2  +1+2
*     VW 14 += 1   ESW 654 += 1  +1+1  ESW 878 += 1  +1+1  ESW 1486 += 1  +1+2
*     VL 16 -= 1   ESL 656 -= 1  +1+1  ESL 880 -= 1  +1+1  ESL 1488 -= 1  +1+2
*     VL 0x10 = 2  ESL 656 = 2   +1-1  ESL 880 = 2   +1-1  ESL 1488 = 2   +1+0
*     V 100 ~= 2   ES 740 ~= 2   +1+0  ES 964 ~= 2   +1+0  ES 1572 ~= 2   +1+1
*     V 136 ^= 4   ES 776 ^= 4   +1+0  ES 1000 ^= 4  +1+1  ES 1508 ^= 4   +1+1
*     V 255 |= 8   ES 895 |= 8   +1+0  ES 1119 |= 8  +1+1  ES 1727 |= 8   +1+1
*     V 0xFF = 9   ES 895 = 9    +1-1  ES 1119 = 9   +1+0  ES 1727 = 9    +1+0
*
*  Notes:  Avoid modifying R8, the various "Quick Access" bits, especially
*          if using any system memory dump routines to debug the code.
*
******************************************************************************/

The code begins by searching for the field delimiter, a space. If one is found, it makes a ParseValue system call. That routine both skips past any space (or spaces) that precede the expected numeric value and returns flags indicating, among other things, if a numeric result was parsed from the buffer. The exit is taken unless a valid number was parsed into R25:R24:R23:R22 and R25, R24 and R23 are all zeros.

CmdExt_UorV_Helper:
   RCALL   ACmdExt_PushAll           ; Since we'll need most of the Registers
   MOVW    R6, R26                   ; Save in a low, preserved, register pair

CmdExt_UorV_SearchLoop:
   LD      R25, X+                   ; Search the buffer until either the first
   CPI     R25, 0                    ;  NULL (string terminator) or a space
   BREQ    ACmdExt_UorV_FailExit     ;  character is found; this ends up either
   CPI     R25, ' '                  ;  bailing out on the NULL or with R27:R26
   BRNE    ACmdExt_UorV_SearchLoop   ;  pointing to the first non-space
   MOVW    R10, R26                  ; Save the pointer to the array index text

;   Setup for the ParseValue() call:
; Passed:  R25:R24 char * cPtrBuffer pointer to buffer to examine
; Returns: R25:R24:R23:R22 long   lData   4-byte data value read
;  (Also):         R27:R26 char * pointer to next byte to parse (or NULL)
;                      R21 byte   bFlags  indicates results (BIT_PV_*)
;   BIT_PV_VALID   = 0 -> B00000001    BIT_PV_OVERFLOW  = 4 -> B00010000
;   BIT_PV_ACTIVE  = 1 -> B00000010    BIT_PV_POSITIVE  = 5 -> B00100000
;   BIT_PV_HEX     = 2 -> B00000100       _PV__spare6__ = 6  (unused; spare)
;   BIT_PV_NONZERO = 3 -> B00001000    BIT_PV_NEGATIVE  = 7 -> B10000000
;
   MOVW    R24, R26                  ; Point to the index text in the buffer
   CALL    AParseValue               ;  and parse the value at that address

CmdExt_UorV_AfterParse:
   SBRS    R21, BIT_PV_VALID         ; If the PV_VALID bit is set, skip over
   RJMP    ACmdExt_UorV_FailExit     ;  the bailout RJMP instruction
   OR      R24, R23                  ; Result in R25:R24:R23:R22 cannot be more
   ADIW    R24, 0                    ;  than 255, so R25, R24, and R23 must all
   BREQ    ACmdExt_UorV_Validate     ;  be zero; accept the command if so

This code block below begins with the fail exit. In addition to the code immediately above, which can either branch around or fall into it, there are three (3) other places in this subroutine that jump to this point, one of which is in the 328P conditional code just below it. Note that no change has been made to the original command buffer yet.

The comments in the middle portion should be sufficient. They last portion of this code block are the set up for the loop which replaces all of the index text BYTEs (i.e., the "20" portion of "V 20 = 4") with spaces. The comment on the line with "not needed" is because the MSByte of the SRAM address of the system command buffer's first and last BYTE is the same. Explicitly, the range is 0x0230 to 0x027F in the 328P and 1284P and 0x0330 to 0x037F in the 2560. The comment was left as a reminder of a potential bug source if some SRAM buffer other than the system command buffer is used.

CmdExt_UorV_FailExit:
   CLZ                               ; Set the "rejected" return status and
   RJMP    ACmd_Ext_UorV_SubRet      ;  jump to exit using the PopAll() jump

CmdExt_UorV_Validate:
.IF PROCESSOR == 328                 ; Only in the 328, the V-Register count
   LDI     R25, 32                   ;  can be between 32 and 256; R15 has the
   ADD     R25, R15                  ;  adder to the 32; if the index is the
   CP      R22, R25                  ;  same or more than that configured value
   BRSH    ACmdExt_UorV_FailExit     ;  (R15+32), exit since it's invalid
.ENDIF

; Note: Commands with multiple values assigned (e.g., "V 255 += 1 2 3 4") can
;  overrun past the end of the uVars.B[##] array.  That aspect is NOT checked.

   SUB     R26, R10                  ; Calculate the index text BYTE count
;- SBC     R27, R11   ; (not needed) ; Parsing buffer range MSByte assumption
   LDI     R19, ' '                  ; Overwrite the index text with spaces
   MOV     R24, R26                  ; Set the number of BYTEs to replace
   MOVW    R30, R10                  ; Point to where the index text begins

It takes more code to test whether or not to overwrite the index text than to just replace it with spaces. Also, "V 0x000F += 1", for example, is a valid command. Leaving any part of the "0x000F" would cause a bug. As noted above, the command buffer range is usually 0x230 to 0x27F or 0x330 to 0x37F, so its size is 0x40 (= 80) BYTEs, but the command may not be presented from there.

CmdExt_UorV_ReplaceLoop:
   ST      Z+, R19                   ; Loop here, replacing all of the index
   DEC     R24                       ;  string characters with spaces (it only
   BRNE    ACmdExt_UorV_ReplaceLoop  ;  takes 6 BYTEs, or 3 OPs for each loop)
   MOV     R12, R22                  ; Save the array index value just parsed

;   Register contents at this point:
;  R7:R6   Pointer to the buffer to examine (BYTE #1 is buffered byte count)
; R10:R11  Pointer to the array index text (e.g., "15") within the buffer
;     R12  The array index (the value parsed above, transferred from R22)
;   * R15  The uVars.B[##] array size adder (* only used for the 328P MCU)
;     R19  A space (' ') character, which should persist until PopAll
;     R21  The bFlags results (BIT_PV_*) from the ParseValue() call
;     R22  The parsed value of the array index text (about to be trashed)
;     R26  BYTEs in the array index text (e.g., 2 for "15" in "V 15 += 1")

Some number of BYTEs which are after the index text need to be moved to make room for the SRAM address. Determine what that number of BYTEs to advance is. The decimal absolute SRAM address text (i.e., after "ES") is always 3 BYTEs in the 328P, always 4 BYTEs in the 2560, but can be either 3 or 4 BYTEs in the 1284P. Since any extra space will be ignored during "Edit SRAM" command parsing, 4 BYTEs are always used the 1284P as well.

.IF PROCESSOR == 328
   POSITIONS_NEEDED = 3              ; This is the number of BYTEs needed to
.ELSE                                ;  represent any uVars.B[##] index in
   POSITIONS_NEEDED = 4              ;  decimal (not hexadecimal) format
.ENDIF

CmdExt_UorV_Expand:
   LDI     R20, POSITIONS_NEEDED     ; Number of BYTEs needed for any index
   SUB     R20, R26                  ; How many characters to insert into the
   BREQ    ACmdExt_UorV_NewIndex     ;  index string location; skip the copy if
   BRLO    ACmdExt_UorV_NewIndex     ;  no characters need to be inserted

;   Setup for the strcpy() call:
;  Passed:  R25:R24 byte * bPtrTo pointer to the destination SRAM buffer
;           R23:R22 byte * bPtrFrom pointer to the source SRAM buffer
;  Returns: R25:R24 byte * the same bPtrTo pointer as the caller passed
;  Alters:  R21, the FLAGs, and the destination buffer specified
;
   MOVW    R22, R10                  ; The source and destination pointers are
   MOVW    R24, R10                  ;  the same, except add how many BYTEs to
   ADD     R24, R20                  ;  insert to the destination pointer
   RCALL   ACmdExt_strcpy            ; Move the substring, inserting spaces

The validated index (0 to 255) returned is in R12, to which the uVars.B[##] starting address is added. That value is 640 (= 0x280) for 328P, 864 (= 0x360) for the 1284P, or 1472 (= 0x5C0) for the 2560.

;   Setup for the FormatWORD() call:
; Passed: R25:R24 WORD   wValue to be formatted
;         R23:R22 byte * pointer to the szDest buffer
;             R20 byte   bFlags with 3 bit variables used:
;                          BIT_FMT_COMMAS  (1 includes commas, 0 omits)
;                          BIT_FMT_POST_Sp (1 pad a ' ' after, 0 don't)
;                          BIT_FMT_PRINT   (1 print the string, 0 don't)
; Alters: R18, R20, R21, R22, R23, R24, and R25
;
CmdExt_UorV_NewIndex:
   LDI     R25, uVars >> 8           ; Calculate the uVars.B[##] array index
   LDI     R24, uVars & 0xFF         ;  of the value parsed from the buffer,
   ADD     R24, R12                  ;  noting that it is a 2-BYTE value, so a
   ADC     R25, R1                   ;  CARRY may be needed
   MOVW    R22, R10                  ; Set the string's starting address
   LDI     R20, 0                    ; No BIT_FMT_* options selected
   CALL    AFormatWORD               ; Let the system routine insert the text
   MOVW    R30, R10                  ; Reload the index string pointer

CmdExt_UorV_Find0Loop:
   LD      R25, Z+                   ; FormatWORD terminates each string with a
   CPI     R25, 0                    ;  NULL byte; since the index text parsed
   BRNE    ACmdExt_UorV_Find0Loop    ;  can vary in length, find the first NUL
   SBIW    R30, 1                    ;  after that string (the "SBIW R30, 1" is
;- LDI     R19, ' '    ; (redundant) ;  needed because Z was post-incremented)
   ST      Z, R19                    ; Replace that NUL terminator with a space
;  RCALL   ACmdExt_DumpRegs_Begin    ; Remove either or both comments here to
;  RCALL   ADumpParsingBuffer        ;  see the Registers or buffer changes
   SEZ                               ; Set the "valid, processed" return status

Cmd_Ext_UorV_SubRet:
   RJMP     ACmdExt_PopAll           ; MUST use JUMP for all PopAll() accesses

The buffer has been changed if the bulk of the code block above was executed. It is not yet ready to be give to the System Command Processor, however, since the command length BYTE as well as Registers R21, R22, and R21 have not been updated. That little buffer cleanup task is done by the CmdExt_UorV routine, to which the helper above is returning.

C:\TestDir>Copy CON CmdExtUorV.Asm
   (copy each of the eleven (11) blocks above)
^Z

C:\TestDir>                                                                     

After copying all of source code blocks into the CmdExtUorV.Asm file, use Control-Z (or F6) to end input and close that file. Note that this code will not assemble as a stand-alone file. Return to the main Command Extensions source code, which pulls this file into the Assembly.