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.