Sequencer subroutines used by all the other sequencer routines

Decide on the name of your program source code program file. "SeqShared.Asm" is used in this example. Create a new file with the same first portion of the name but with the ".Ptr" extension, so that becomes "SeqShared.Ptr" to continue using our example. Put a blank or comment line in the .Ptr file and close it. One way to do that in a Windows console is like this:

C:\TestDir>Copy CON SeqShared.Ptr
 ; (press Enter then either F6 or Ctrl-Z)
^Z
        1 file(s) copied.

C:\TestDir>                                                                     

That's all for that file. Our batch file will update the .Ptr file when we assemble the completed source code file.

Next, copy the SeqTemplate.Asm template file created on the prior example page to the new SeqShared.Asm file. Append the short block below at the end of the file. This block pulls in the derived address constants contained in the ".Ptr" file, which is rebuilt during each assembly. It also has three BYTE definitions which convert portions of 16-BIT values into constituent 8-BIT parts, so they can be loaded into 8-BIT Registers. Append all of the block below at the end of the .Asm source code file and leave it open. We'll be adding more to it as we proceed through the example on this webpage.

.INCLUDE "SeqShared.Ptr"         ; Same name as this file except .Ptr extension

AHi_Heap = AHeapBegins >> 8      ; High and Low address BYTEs where the HEAP
ALo_Heap = AHeapBegins & 0xFF    ;  begins (or where it can begin for the 328P)
AHi_RAMEND = RAMEND >> 8         ; High address BYTE of the last RAM BYTE

SoftVector:                      ; This is just the absolute address 0

.ORG  0x00100                    ; Starting FLASH address of the following code

/*****************************************************************************/

Next, example then append the "Shared_Setup" subroutine contained in the code block below to the SeqShared.Asm file. All of the other WS2812B sequencer mode routines call it in order to setup their working environment. Please take a few minutes to review the comments within each code block before copying it to the end of the .ASM source file. Try to understand how each code section does what it is designed to do. The comments are there to convey what is being done by each instruction.

Shared_Setup_String:
   .STRING "Setup at 0x110:"         ; This text will appear in the FLASH image

Shared_Setup:             ; BYTEs Clocks
   EOR     R1, R1            ;  2  1   R1 should be zero, but just in case ...
.IF PROCESSOR == 328
   LDS     R15, bR15uVarsAdder; 4  2   This should already be true as well
   LDI     R17, BuVarsSize-15;  2  1   The uVars.B[##] array size is increased
   ADD     R17, R15          ;  2  1    by the value in R15 (= bR15uVarsAdder)
.ELSE
   LDI     R17, BuVarsSize-15;  2  1   The uVars.B[ 256 ] array size is fixed
.ENDIF
   CP      R24, R17          ;  2 1/2  Need uVars.B[+0] to [+15]; if less than
   BRCC    AShared_BeginLoad ;  2 1/2   16 are available, ignore the index
   ADD     R28, R24          ;  2  1   Add the index to the uVars base address
   ADC     R29, R1           ;  2  1    to get the absolute starting address

Shared_BeginLoad:
   LDD     R11, Y + IbBitmapped;2  2   uVars.B[ n+1 ] = bBitmapped
   LDD     R12, Y + IbPortPins ;2  2   uVars.B[ n+2 ] = bPortPins
   LDD     R13, Y + IbIncrDecr ;2  2   uVars.B[ n+3 ] = bIncrDecr
   LDD     R14, Y + IbMaxBright;2  2   uVars.B[ n+4 ] = bMaxBright
   LDD     R26, Y + IbLo_LEDCount    ; uVars.B[ n+5 ] = wLEDCount LSByte
   LDD     R27, Y + IbHi_LEDCount    ; uVars.B[ n+6 ] = wLEDCount MSByte
   LDD     R17, Y + IbModeSpecific1  ; uVars.B[ n+7 ] = bModeSpecific1
   LDD     R18, Y + IbModeSpecific2  ; uVars.B[ n+8 ] = bModeSpecific2
   LDD     R19, Y + IbModeSpecific3  ; uVars.B[ n+9 ] = bModeSpecific3

   MOV     R21, R11          ;  2  2   Mask only the 4 LSBits, which is the
   ANDI    R21, B00001111    ;  2  1    PORT number selected
   MOV     R22, R26          ;  2  1   Sets R22 = 0 if R27:R26 (wLEDCount) = 0
   OR      R22, R27          ;  2  1   If BOTH the bBitmapped variable (PORT
   MUL     R22, R21          ;  2  2    index) and the wLEDCount are nonzero,
   OR      R0, R1            ;  2  1    then there is something to do
   BREQ    AShared_Exit      ;  2 1/2  If either variable was zero, exit now
   EOR     R1, R1            ;  2  1   Make sure that R1 continues to be 0

Shared_SetStart:
   LDI     R25, AHi_Heap     ;  2  1   This is the default address after the
   LDI     R24, ALo_Heap     ;  2  1    last system SRAM BYTE used
.IF PROCESSOR == 328
   ADD     R24, R15          ;  2  1   This depends on the system R15 not being
   ADC     R25, R1           ;  2  1    trashed to determine the uVars.B[] size
.ENDIF
   LDD     R30, Y+IbLo_StartingAddress ; Load the starting RAM address specified
   LDD     R31, Y+IbHi_StartingAddress ; It must be less than the maximim RAM
   CPI     R31, AHi_RAMEND   ;  2  1    available, less AT LEAST 256-BYTE stack
   BRCC    AShared_DefaultZ  ;  2 1/2  If that or greater, change to default
   CP      R30, R24          ;  2  1   Check for the caller's address being
   CPC     R31, R25          ;  2  1    less than the end of system memory
   BRCC    AShared_CalcCount ;  2 1/2  OK if 'Z' is same or higher than HEAP

Shared_DefaultZ:
   MOVW    R30, R24          ;  2  1   Point 'Z' at the start of the HEAP

Shared_CalcCount:
   MOVW    R20, R26          ;  2  1   The GRB BYTE count is the 3 * LED count
   ADD     R20, R26          ;  2  1   Transfer the pixel count to R21:R20
   ADC     R21, R27          ;  2  1    then add it in 2 more times, in effect
   ADD     R20, R26          ;  2  1    multiplying by 3 to get the BYTE count
   ADC     R21, R27          ;  2  1
   MOVW    R2, R30           ;  2  1   Save 'Z': data write buffer in SRAM
   ST      Z+, R20           ;  2  2   Store the LED data BYTE count WORD at
   ST      Z+, R21           ;  2  2    the first 2 data addresses in SRAM
   RET                       ;  2  4

.BALIGN 16, 0, 15            ; BYTE align to 16 boundary; pad with 0s (NOPs)

The block below is just documentation. It is certainly optional, but it often helps greatly when it is contained in the source file at this point.
You can also use popout to open it in another tab for reference.

/******************************************************************************

   The values which the registers (should) have upon exiting Shared_Setup():

   R0          no specific value; available to be used
   R1       has the value 0
   R3:R2    pointer to the BYTE count WORD before the data BYTEs
   R4-R10      no specific values; available to be used
   R11      3 MSBits: G,R,B enable; 1:Z, 4 LSBits: Port index   uVars.B[ n+1 ]
   R12      bPortPins, the output pins selected                 uVars.B[ n+2 ]
   R13      bIncrDecr, the increment or decrement amount        uVars.B[ n+3 ]
   R14      bMaxBright, the maximum color output value          uVars.B[ n+4 ]
   R15      uVars.B[] adder in the 328P; unused & available in 1284P & 2560
   R16         no specific value; available to be used
   R17      Mode Specific BYTE #1                               uVars.B[ n+7 ]
   R18      Mode Specific BYTE #2                               uVars.B[ n+8 ]
   R19      Mode Specific BYTE #3                               uVars.B[ n+9 ]
   R21:R20  number of data BYTES (pixels * 3) to write
   R22         scratch (values OR'd together)
   R23         no specific value; available to be used
   R25:R24  pointer to the HEAP; available to be used
   R27:R26  wLEDCount WORD pixel count                     uVars.B[ n+5, n+6 ]
   R29:R28  base SRAM address for uVars.B[ n ]
   R31:R30  pointer to the starting data location (= R3:R2 + 2)

      As an example, these 2 configurations:
   0200:  01 E2 1C FF FF 27 00 00-28 08 C8 00 05 00 FF FF  .....'..(.......
   0210:  00 E2 1C FF FF 28 00 00-08 28 C8 00 05 00 FF FF  .....(...(......

      Generate these register set upon entry, when called with R24 = 0:
 10   32   54   76   98  1110 1312 1514 1716 1918 2120 2322 2524 2726 2928 3130
---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
0000 027E 1081 8128-0000 E210 FF1C 00FF-0000 2808 0078 0328-027E 0028 0200 0280
0000 027E 1081 8128-0000 E200 FF1C 00FF-0000 0828 0075 0327-027E 0027 0200 0280

   Passed: R3:R2 pointing to the BYTE count WORD before the data (as above)
           R29:R28 pointing to the base uVars.B[ n ] SRAM address (as above)

******************************************************************************/

Next up is the "Shared_Output" routine with a descriptive string embedded in the code before the actual MCU logic. This is the code which causes the range of BYTEs in SRAM to the string of WS2812B GRB LEDs. Notice that there is no RET instruction at the end of this subroutine as one might expect. Instead, it just jumps to the WS2812B Operating System output code and returns directly from there. It could have also been coded as an RCALL to the SoftVector routine and been followed immediately with a RET but this way just removes that one step. Append this block to the open SeqShared.Asm file.

.IFDEF AShared_Output
   .IF AShared_Output != 0x0182
      .WARNING "Shared_Output address expected to be 0x0182."
   .ENDIF
.ENDIF

Shared_Output_String:
.STRING "Output at 0x182: "
       ; 0123456789ABCDEF01

Shared_Output:            ; BYTEs Clocks
   LDD     R24, Y+IbBitmapped;  2  2   Read the BYTE containing the PORT index
   LDI     R30, I_Low4BitsToPORT ; 2   Call the subroutine which translates it
   RCALL   ASoftVector       ;  2  3    from an index to an SRAM address; it
   BRNE    AShared_SetPORT   ;  2 1/2   set the ZERO flag if the R25:R24 being
   LDI     R24, PORTC + 0x20 ;  2  1    returned = 0; set default SRAM address

;   Setup for the WS2812_Cmd_PortSet() call:
; Passed: R25:R24 byte * pointer to the byte data buffer to transmit
;         R23:R22 int    the number of BYTES to be transmitted
;                        Note 1: negative value sends OFF to ABS (value)
;                        Note 2: ATmega memory limits the pixel count
;         R21:R20 word   the SRAM address of the PORT to be toggled
;         R18 byte       bitmask for that port's output(s) to change

Shared_SetPORT:
   MOVW    R20, R24          ;  2  1   Transfer the SRAM address to R21:R20
   MOVW    R30, R2           ;  2  1   Point 'Z' at the start of the LED data
   LD      R22, Z+           ;  2  2    buffer just written and load the BYTE
   LD      R23, Z+           ;  2  2    count (first 2 bytes) into R23:R22
   MOVW    R24, R30          ;  2  1   Set the address of the LED data and do
   LDI     R30, I_WS2812_Cmd_PortSet ;  the WS2812B output, without any PORT
   RJMP    ASoftVector       ;  2  2    checking, returning directly to caller

/*****************************************************************************/

The next small block contains the the tiny (4-BYTE) "Shared_Exit" subroutine. It handles restoring all of the Registers PUSHed on the stack and returning to the caller's caller. Again, read and append this block to the open SeqShared.Asm file.

.IFDEF AShared_Exit
   .IF AShared_Exit != 0x01B0
      .WARNING "Shared_Exit address expected to be 0x01B0."
   .ENDIF
.ENDIF

Shared_Exit_String:
.STRING "      Exit at 0x1B0: "
       ; ABCDEF0123456789ABCDEF

Shared_Exit:              ; BYTEs Clocks
   LDI     R30, I_PopAll     ;  2  1   Select the routine: PopAll, which MUST
   RJMP    ASoftVector       ;  2  2    use a JUMP, not a CALL!

/*****************************************************************************/

The final block to append contains an example subroutine which can be used as the starting code when writing a new LED mode routine. It shows how each of the prior four (4) subroutines are called but it also has something extra. When writing and debugging soure code, it's often nice to get a snapshot of all the Registers and the Stack at a particular point (or even more than one point) in the code. This has two (2) lines included that do that, which are LDI R30, I_DumpRegs_Begin and RCALL ASoftVector. Also the usual output is in its proper place but is commented out and not called, since there is no setup before the RCALL AShared_Output. Append this block.

.IFDEF AShared_PrintRegs
   .IF AShared_PrintRegs != 0x01C8
      .WARNING "Shared_PrintRegs address expected to be 0x01C8."
   .ENDIF
.ENDIF

Shared_PrintRegs_String:
.STRING "  Example at 0x1C8:"
       ; 456789ABCDEF01234567

Shared_PrintRegs:         ; BYTEs Clocks
   LDI     R30, I_PushAll    ;  2  1   The example executes the same setup that
   RCALL   ASoftVector       ;  2  3    the mode routines use, then dumps the
   RCALL   AShared_Setup     ;  2  3    Registers and possibly the stack, based
   LDI     R30, I_DumpRegs_Begin ; 2    on the value in bDumpRegFlags (at 0x1DF
   RCALL   ASoftVector       ;  2  3    in 328P and 1284; at 0x2DF in 2560)
;  RCALL   AShared_Output    ;  ----   It goes here, but commented out
   RJMP    AShared_Exit      ;  2  2

.BALIGN 16, 0, 15            ; BYTE align to 16 boundary; pad with 0s (NOPs)

/*****************************************************************************/

.IFDEF ADivider_String
   .IF ADivider_String != 0x01E0
      .WARNING "Divider_String address expected to be 0x01E0."
   .ENDIF
.ELSE
   .PRINT "Assemble again to initialize label addresses."
.ENDIF

Divider_String:
  .LONG   0x3D3D3D3D, 0x3D3D3D3D, 0x3D3D3D3D, 0x3D3D3D3D  ; 0x3D is ASCII '='

Save the complete SeqShared.Asm source code file. You're now ready to use the Assemble.Bat file to create the iHex image. The command console command sequence and the results are shown below.

C:\TestDir>Assemble SeqShared
Assemble again to initialize label addresses.

C:\TestDir>Assemble SeqShared

C:\TestDir>Dir /b
Assemble.Bat
SeqTemplate.Asm
SeqShared.Asm
SeqShared.PBk
SeqShared.Elf
SeqShared.Hex
SeqShared.Trm
SeqShared.Dmp
SeqShared.Sym
SeqShared.Ptr

C:\TestDir>Type SeqShared.Trm                                                   

This should have caused the SeqShared.Trm file to be output to the console. It should have ended with sixteen (16) lines in iHex format. To verify that the code that you generated precisely matches that which came with MIRTOS, do the following in the device terminal window (NOT the same assembly console window used above):

>DFH+ 256 240
FLASH (program memory) contents:
:1001000053657475702061742030783131303A0055
:100110001124F0901B0111E11F0D811710F4C80F7D
:10012000D11DB980CA80DB80EC80AD81BE811F818A
:10013000288539855B2D5F706A2F6B2B659F0128A1
:10014000B9F1112492E080EA8F0D911DEE85FF85B3
:10015000F83018F4E817F90708F4FC01AD014A0F6C
:100160005B1F4A0F5B1F1F014193519308950000CD
:100170004F75747075742061742030783138323A5C
:1001800020008981EAE33CDF09F488E2AC01F10157
:1001900061917191CF01E8E733CF2020202020200A
:1001A000457869742061742030783142303A2000FB
:1001B000E1E026CF20204578616D706C6520617488
:1001C0002030783143383A00E0E01ADFA1DFEBE776
:1001D00017DFEECF0000000000000000000000006C
:1001E0003D3D3D3D3D3D3D3D3D3D3D3D3D3D3D3D3F
:00000001FF
>                                                                               

If the iHex in the .TRM file which you created does not match precisely, the first and best option is to check your code. If you're certain that your code is correct, you can easily replace the code in the 328P-based device with your new code. To do that, first enter Manager mode in the terminal windows used to communicate with your MIRTOS-based device. The '$' first key toggles Manager mode, which is indicated by seeing that the prompt changed from ">" to "m>". Copy up those sixteen (16) rows of iHex format and paste them into the terminal window. This writes (i.e., saves) those 240 BYTEs to the device's FLASH memory. Exit Manager mode.

Next, display the same range above in Debug format.

>DF+ 256 240
FLASH (program memory) contents:
0100:  53 65 74 75 70 20 61 74-20 30 78 31 31 30 3A 00  Setup at 0x110:.
0110:  11 24 F0 90 1B 01 11 E1-1F 0D 81 17 10 F4 C8 0F  .$..............
0120:  D1 1D B9 80 CA 80 DB 80-EC 80 AD 81 BE 81 1F 81  ................
0130:  28 85 39 85 5B 2D 5F 70-6A 2F 6B 2B 65 9F 01 28  (.9.[-_pj/k+e..(
0140:  B9 F1 11 24 92 E0 80 EA-8F 0D 91 1D EE 85 FF 85  ...$............
0150:  F8 30 18 F4 E8 17 F9 07-08 F4 FC 01 AD 01 4A 0F  .0............J.
0160:  5B 1F 4A 0F 5B 1F 1F 01-41 93 51 93 08 95 00 00  [.J.[...A.Q.....
0170:  4F 75 74 70 75 74 20 61-74 20 30 78 31 38 32 3A  Output at 0x182:
0180:  20 00 89 81 EA E3 3C DF-09 F4 88 E2 AC 01 F1 01   .....<.........
0190:  61 91 71 91 CF 01 E8 E7-33 CF 20 20 20 20 20 20  a.q.....3.
01A0:  45 78 69 74 20 61 74 20-30 78 31 42 30 3A 20 00  Exit at 0x1B0: .
01B0:  E1 E0 26 CF 20 20 45 78-61 6D 70 6C 65 20 61 74  ..&.  Example at
01C0:  20 30 78 31 43 38 3A 00-E0 E0 1A DF A1 DF EB E7   0x1C8:.........
01D0:  17 DF EE CF 00 00 00 00-00 00 00 00 00 00 00 00  ................
01E0:  3D 3D 3D 3D 3D 3D 3D 3D-3D 3D 3D 3D 3D 3D 3D 3D  ================
>                                                                               

Notice at the bottom the text which says, "Example at 0x1C8:". That example is called Shared_PrintRegs in our source code. It calls Shared_Setup, displays the Registers (and Stack, if selected), and jumps to Shared_Exit. To demonstrate it, the calling environment first needs to be initialized. This last section shows how to do that interactively.

>ES 0x1DF = 0x7A ; Dump Stack after Registers (use 0x2DF for 2560)
>R          ; The contents of the Registers and Stack "before" the load

 10   32   54   76   98  1110 1312 1514 1716 1918 2120 2322 2524 2726 2928 3130  SP  RetAdd   SREG
---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ------ -------- 
0000 00DA 090B 0B28-0004 0000 0000 6000-0021 00FF 5201 0000-0000 0230 0280 3158 08F5  69DE  Ithsvnzc 
STACK contents:
08F0:                 31 34 EF-31 29 30 02 30 FF 3A D6       14.1)0.0.:.
>P@ 0x180   ; Load the sequencer configuration from EEPROM address 0x180
>R          ; Verify that the configuration loaded

 10   32   54   76   98  1110 1312 1514 1716 1918 2120 2322 2524 2726 2928 3130  SP  RetAdd   SREG
---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ------ --------
0000 0D2A 0912 1228-0004 0180 0241 6000-0001 00FF 5201 0000-0000 0230 0280 3158 08F5  69DE  Ithsvnzc
STACK contents:
08F0:                 31 34 EF-31 29 30 02 30 FF 3A D6       14.1)0.0.:.

>; '$' first char (must be in Manager mode to use the '@' subroutine call)
m>@ 0x1C8
 10   32   54   76   98  1110 1312 1514 1716 1918 2120 2322 2524 2726 2928 3130  SP  RetAdd   SREG
---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ------ --------
00E1 0300 0924 2428-0004 E280 023C 60FF-7821 7878 02D0 00F0-0300 00F0 0280 307B 08D5  01D2  Ithsvnzc
STACK contents:
08D0:                 30 00 E9-00 00 2C 0E 24 09 28 24       0....,.$.($
08E0:  04 00 80 01 41 02 00 60-21 00 00 00 10 0D E4 00  ....A..`!.......
08F0:  00 00 00 00 80 02 00 30-31 29 30 02 30 FF 3A D6  .......01)0.0.:.
m>

Here is how to decode the Stack: The "30" at address 0x8D5 is where SP (the Stack Pointer) is pointing. Since the MCU uses a "PUSH first then change the SP address" (auto-post-decrementing) mode, the "30" has no meaning to us. The next 2 BYTEs ("00 E9") are actually the WORD 0x00E9, which which is the WORD return address. Its BYTE return address, therefore, is simply twice that value, which is 0x01D2. This is the address of the BYTE after "RCALL ASoftVector" in the Shared_PrintRegs routine. Examine the SeqShared.Dmp Assembly listing file to verify that.

The next 32 BYTEs, from 0x8D8 through 0x8F7, are the values of Registers R0 through R31 as they were PUSHed on the Stack by the I_PushAll indexed RCALL at the top of the Shared_PrintRegs routine. Just to remove any ambiguity, these are those thirty-two (32) Register values at that time (R0 at 0x8D8 = 0, R1 at 0x8D9 = 0, R2 at 0x8DA = 0x2C, ... , R28 at 0x8F4 = 0x80, R28 at 0x8F4 = 2, R28 at 0x8F4 = 0, and R31 at 0x8F7 = 0). Just to verify, check the 'Y' pointer (R29:R28) shown above. It has the value 0x0280 in all Register Dumps, as it should, since it maintains the pointer to uVars.B[0], which is the start of the User V-Registers.

08D0:                         -00 00 2C 0E 24 09 28 24          ..,.$.($
08E0:  04 00 80 01 41 02 00 60-21 00 00 00 10 0D E4 00  ....A..`!.......
08F0:  00 00 00 00 80 02 00 30-                         .......0

Notice that the last eight (8) BYTEs on the Stack have the exact same values in all three Register Dumps above. They are the return address WORDs and Register values PUSHed by the MIRTOS System routines which eventually called Shared_PrintRegs. One of these would be the '@' command handler. Those eight (8) BYTEs really don't provide much meaningful information for us.

The next step is to add a Sequencer Mode module, the first of which begins on the "Next Example" webpage.