There is more than one way to do this task. The first way is to continue to add to the first SeqShared.Asm source code file. The second is to save the addresses of certain subroutines and to simply call them by name, as is done with the Operating System routines. Both methods will be shown shown below. Create the nearly empty "Seq_Mode0.Ptr" file, as was done before.
The first block is the explanation of the routine. Although it can be omitted, it is highly advisable to retain it. Create a new file named Only_Mode0.Asm, for example and place this first block in it.
/****************************************************************************** * * Routine "FadeToBlack" * * Purpose: Dim each pixel down to 0. Leave each off upon reaching black. * * Assumes: R29:R28 points to the start of the uVars.B[] array * R1 contains the value 0 * * Passed: R24 byte wOffset into the uVars.B[] array for our 16 bytes * if invalid (e.g., 0xFF), 0 will be used instead * * Notes: The WS2812 configuration block (or structure) layout: * +---------+-----------+--------+----------+--------+----+----+-/ * | Display | Bitmapped | Port's | Decrease | MaxBrt | LED | * | Mode |GRB|Z| Port| Pin(s) | Amount |unused | Count | . . . * +---------+-3-+1+--4--+--------+----------+--------+----+----+-/ * +0 +1 +2 +3 +4 +5 +6 * +7 +8 +9 +10, 11 +12, 13 +14 +15 * /-+----+----+----+----+-----+------+------+-----+-----+ * |Mode Specific | Update | Mode Change | Starting | * . . . |#1 | #2 | #3 | Interval | Interval | Address | * /-+----+----+----+----+-----+------+------+-----+-----+ *(unused) milliSec Seconds * * * The GRB (Green-Red-Blue) LED BYTE data image layout: * +-----+-----+-------+-------+-------+-------+-------+-------+-/ * | Byte Count| Green | Red | Blue | Green | Red | Blue | * | LSB MSB | LED#1 | LED#1 | LED#1 | LED#2 | LED#2 | LED#2 | . . . * +-----+-----+-------+-------+-------+-------+-------+-------+-/ * +0 +1 +2 +3 +4 +5 +6 +7 * * Setup example: * E 0x240 = 0 0x12 0x3C 2 -1 240 0 -1 -1 -4 40 0 30 0 -1 -1 * Result in EEPROM: * 0240: 00 12 3C 02 FF F0 00 FF-FF FC 28 00 1E 00 FF FF ..<.......(..... * ******************************************************************************/
The first two (2) BYTES of code in every Sequencer block must be the unique Mode ID then the number of 16-BYTE "sentences." They must be followed by the "LDI R30, I_PushAll" instruction. MIRTOS searches through the Sequencer blocks until it finds the one that matches the desired Mode. If it doesn't match, it skips the number of 16-BYTE blocks that are specified by the second BYTE. The Mode ID can be 0 through 127 (= 0x7F.) If the most significant BIT of the Mode ID BYTE is set (so, values 0x80 = 128 to 0xFE = 254), it treats that Mode as, in effect, "commented out." If the Mode ID BYTE is 0xFF, it treats it as the end of the Sequencer block chain and ends its search.
This block is the Mode 0 initialization code. It's really very simple. Append it to the Only_Mode0.Asm source code file. Note that 4-BYTE CALLs and JMPs are being used throughout this Mode's code. More will be explained on that topic later.
.IFNDEF AFadeToBlack .PRINT "Assemble again to initialize label addresses." AFadeToBlack = 0x102 AMode0End = 0x144 .ENDIF Mode0Start: .BYTE 0 ; The MODE byte: Mode 0 .BYTE (AMode0End - AFadeToBlack) / 16 + 1 ; Number of 16-byte blocks used FadeToBlack: ; Bytes Clocks LDI R30, I_PushAll ; 2 1 Select the routine: PushAll, which MUST CALL ASoftVector ; 4 4 use a CALL, not a JUMP! CALL AShared_Setup ; 4 4 Use 4-BYTE CALLs/JMP for this example LDI R18, 0 ; 2 1 Clear the pixel change flag OR R13, R13 ; 2 1 If the IbIncrDecr is nonzero, accept it BRNE AMode0_OuterLoop ; 2 1/2 but no change makes no sense, so when INC R13 ; 2 1 it is zero, change it to 1
The next block is the looping code. There is an outer and an inner loop. The outer loop counts the number of LEDs (or pixels); the inner loop works with the three (3) color BYTEs for each LED. The looping continues until the outer loop count (in R27:R26) reaches zero. If any color decrease is needed to any BYTE of any pixel, R18 is modified to a nonzero value. This is to know if and when all the three (3) BYTEs for every LED are set to 0, 0, and 0, i.e., when all pixels are black.
Mode0_OuterLoop: LDI R16, 3 ; 2 1 Initialize the Inner Loop: 3 BYTEs / LED Mode0_InnerLoop: LD R17, Z ; 2 2 Read the LED color component; if it is CPI R17, 0 ; 2 1 already zero, there's no need to change BREQ AMode0_Write ; 2 1/2 it, so skip to the write section OR R18, R17 ; 2 1 Any value in R18 prevents the timeout SUB R17, R13 ; 2 1 Decrement the GR/RED/BL component value BRCC AMode0_Write ; 2 1/2 If it ran past 0, set it to 0 instead LDI R17, 0 ; 2 1 (would also work if it was already 0) Mode0_Write: ST Z+, R17 ; 2 2 Write the new component value and post- DEC R16 ; 2 1 increment the byte position pointer BRNE AMode0_InnerLoop ; 2 1/2 Loop until all 3 colors are updated SBIW R26, 1 ; 2 2 Decrement the remaining LED count WORD BRNE AMode0_OuterLoop ; 2 1/2 (kept in R27:R26) until it reaches 0
The final block is the checking and output logic. The first seven (7) lines check to see if checking for all pixels off is even desired. If so and they are now all off (zeros), then it sets the mode timeout WORD to 1 second. The code after Mode0_Exit is the output section, which is again pretty simple, since it lets the Shared_Output routine handle it. The last two (2) lines pad out to the end to the 16-BYTE "sentence" with 0xFFs written to any unused BYTEs.
Mode0_Check: SBRS R11, BIT_R11_MODAL; 2 1/2 If the Modal bit in R11 is clear, don't RJMP AMode0_Exit ; 2 2 check to see if any pixel(s) changed CPI R18, 0 ; 2 1 If any pixels were updated this time, BRNE AMode0_Exit ; 2 1/2 skip to the exit, otherwise, change LDI R26, 1 ; 2 1 R27:R26 = 0x0001 to cause this mode to STD Y+12, R26 ; 2 2 timeout the next time it is checked, STD Y+13, R27 ; 2 2 thereby advancing to the next mode Mode0_Exit: MOV R18, R12 ; 2 2 Shared_Output() needs bPortPins in R18 CPSE R18, R1 ; 2 1/2 If uVars.B[ n+2 ] is zero, there will be CALL AShared_Output ; 4 4 no output, which may well occur when JMP AShared_Exit ; 4 3 only calculating/updating the LED data Mode0End: .BALIGN 16, 0xFF, 15 ; Byte align to 16 boundary; pad with 0xFFs
The next step is to save the file and append the new Only_Mode0.Asm to the SeqShared.Asm file from the prior example to create the new to Seq_Mode0.Asm source code. We want to combine them in a way that preserves the original source code file. Sometimes there's a need to revert to a prior iteration. One quick and easy way to do it without using a text editor is shown in the next command box. If we assemble the combined file after that, the assembly doesn't fail, but reports that at least one of the label addresses is unresolved.
C:\TestDir>Copy /B SeqShared.Asm+Only_Mode0.Asm Seq_Mode0.Asm SeqShared.Asm Only_Mode0.Asm 1 file(s) copied. C:\TestDir>Assemble Seq_Mode0 Assemble again to initialize label addresses. C:\TestDir>Assemble Seq_Mode0 Assemble again to initialize label addresses.
Upon examining the Seq_Mode0.Dmp file, we notice that NONE of the new label addresses have been resolved! That means that all of the branch instructions are shown as breq .+0, or brcc .+0, or brne .+0, and even rjmp .+0. Whenever one of those ".+0" branches are found, is usually indicates that the .Ptr file didn't get imported correctly. In rechecking the source code, we're still pulling in the SeqShared.Ptr file, not Seq_Mode0.Ptr. Make that change by editing Seq_Mode0.Asm, finding SeqShared.Ptr (at about line 115), changing it to Seq_Mode0.Ptr and closing the file. Run the Assemble Seq_Mode0 command again. No errors should be reported.
It's ALWAYS a good idea to check the .DMP source listing file for any ".+0 line. Finding one nearly always means there is an error somewhere. Skipping a branch or a jump will certainly create a "bug". Since this is one easy check to perform, it should always be done. It is also one of the reasons for including the .IFNDEF checking that prints a note to the console There don't need to be a lot of them, but including at least one is a great idea.
This is the resulting new code, in both iHex and Debug formats. Notice that the first four (4) BYTEs are 0 (Mode ID), 5 (0x50 BYTEs in the module), then 0xE0, 0xE0 (the "LDI R30, I_PushAll" instruction). Those are a proper signature for a Sequencer module. Also note that it is padded out to the end of the last 16 BYTEs with 0xFFs.
>DP+H 0x1F0 80 FLASH (program memory) contents: :1001F0000005E0E00E9400000E94880020E0DD2869 :1002000009F4D39403E01081103021F0212B1D1943 :1002100008F410E011930A95B1F7119799F7B4FE1D :1002200005C0203019F4A1E0AC87BD872C2D211129 :100230000E94C1000C94D800FFFFFFFFFFFFFFFFEB :00000001FF >DP+- 0x1F0 80 01F0: 00 05 E0 E0 0E 94 00 00-0E 94 88 00 20 E0 DD 28 ............ ..( 0200: 09 F4 D3 94 03 E0 10 81-10 30 21 F0 21 2B 1D 19 .........0!.!+.. 0210: 08 F4 10 E0 11 93 0A 95-B1 F7 11 97 99 F7 B4 FE ................ 0220: 05 C0 20 30 19 F4 A1 E0-AC 87 BD 87 2C 2D 21 11 .. 0........,-!. 0230: 0E 94 C1 00 0C 94 D8 00-FF FF FF FF FF FF FF FF ................ >
Although the examples of Sequencer Modules in MIRTOS are in increasing numerical order, they need not be. They can be in any numerical order. They can also be relocatable, meaning that they be moved around in memory and still function correctly. There are some considerations when creating that kind of module, however, in order to make them relocatable.
Open the Only_Mode0.Asm file which we created above. This is the one that we created before appending it to SharedSeq.Asm; it should still exist in its original form. Scroll down to this section:
FadeToBlack: ; Bytes Clocks LDI R30, I_PushAll ; 2 1 Select the routine: PushAll, which MUST CALL ASoftVector ; 4 4 use a CALL, not a JUMP! CALL AShared_Setup ; 4 4 Use 4-BYTE CALLs/JMP for this example LDI R18, 0 ; 2 1 Clear the pixel change flag OR R13, R13 ; 2 1 If the IbIncrDecr is nonzero, accept it
The two CALL instructions above could also be coded using their 2-BYTE counterparts, as shown below.
FadeToBlack: ; Bytes Clocks LDI R30, I_PushAll ; 2 1 Select the routine: PushAll, which MUST RCALL ASoftVector ; 2 3 use a CALL, not a JUMP! RCALL AShared_Setup ; 2 3 These are 2-BYTE RCALLs LDI R18, 0 ; 2 1 Clear the pixel change flag OR R13, R13 ; 2 1 If the IbIncrDecr is nonzero, accept it
The RCALL instruction calls a subroutine relative to the current code position. Although shorter, these RCALLs to addresses outside of the current code block would prevent the routine from being relocatable. An RCALL inside the same code block does not prevent it from being movable. To illustrate this, examine the following code, which occurs near the end of our file.
Mode0_Check: SBRS R11, BIT_R11_MODAL; 2 1/2 If the Modal bit in R11 is clear, don't RJMP AMode0_Exit ; 2 2 check to see if any pixel(s) changed CPI R18, 0 ; 2 1 If any pixels were updated this time, BRNE AMode0_Exit ; 2 1/2 skip to the exit, otherwise, change LDI R26, 1 ; 2 1 R27:R26 = 0x0001 to cause this mode to STD Y+12, R26 ; 2 2 timeout the next time it is checked, STD Y+13, R27 ; 2 2 thereby advancing to the next mode Mode0_Exit: MOV R18, R12 ; 2 2 Shared_Output() needs bPortPins in R18 CPSE R18, R1 ; 2 1/2 If uVars.B[ n+2 ] is zero, there will be CALL AShared_Output ; 4 4 no output, which may well occur when JMP AShared_Exit ; 4 3 only calculating/updating the LED data
The RJMP AMode0_Exit is to a location within this same module, so it will work fine. The CALL and JMP in the last two lins need to be the absolute 4-BYTE versions for the module to remain relocatable.
In order to resolve all of the label references correctly, for this source code to assemble correctly, and the .DMP output file to make sense, the following thirteen (13) lines should be added to the file, after the documentation header but before the .IFNDEF AFadeToBlack line. The reason for each additional line is explained in its associated comment.
.INCLUDE "Indices.Def" ; Needed to resolve "LDI R30, I_PushAll" .INCLUDE "Only_Mode0.Ptr" ; To resolve label references inside this module AShared_Setup = 0x0110 ; These three CALL and JMP target addresses can be AShared_Output = 0x0182 ; found in the text embedded in the code generated AShared_Exit = 0x01B0 ; by the SeqShared (see the Debug output below) BIT_R11_MODAL = 4 ; The constant used to check for pixel change SoftVector: ; Could also be "ASoftVector = 0" to set address .ORG 0x1F0 ; This is just where the module began above .IFNDEF AFadeToBlack ; (the existing line of code)
Again, the values assigned to the three (3) AShared_* variables above came from this block, which should already be in FLASH:
C:\TestDir>DP+- 256 0x140 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 ================ 01F0: 00 05 E0 E0 0E 94 00 00-0E 94 88 00 20 E0 DD 28 ............ ..( 0200: 09 F4 D3 94 03 E0 10 81-10 30 21 F0 21 2B 1D 19 .........0!.!+.. 0210: 08 F4 10 E0 11 93 0A 95-B1 F7 11 97 99 F7 B4 FE ................ 0220: 05 C0 20 30 19 F4 A1 E0-AC 87 BD 87 2C 2D 21 11 .. 0........,-!. 0230: 0E 94 C1 00 0C 94 D8 00-FF FF FF FF FF FF FF FF ................ >
Notice above that the first (file appending) coding method generated these 72 BYTEs of code and had eight (8) padding BYTEs of 0xFFs at the end of the module. They are shown in Debug format above and in iHex format below.
:1001F0000005E0E00E9400000E94880020E0DD2869 :1002000009F4D39403E01081103021F0212B1D1943 :1002100008F410E011930A95B1F7119799F7B4FE1D :1002200005C0203019F4A1E0AC87BD872C2D211129 :100230000E94C1000C94D800FFFFFFFFFFFFFFFFEB
If each of the four (4) 4-BYTE instructions is converted to its 2-BYTE counterpart, the results are eight (8) fewer BYTEs in this module. This would cause the module to look like this in iHex format:
:1001F0000004E0E005DF8CDF20E0DD2809F4D39483 :1002000003E01081103021F0212B1D1908F410E0BB :1002100011930A95B1F7119799F7B4FE05C02030F4 :1002200019F4A1E0AC87BD872C2D2111AADFC0CF26
There are no padding BYTEs, since the the module now has 64 BYTEs of code, shortening the line to precisely four (4) 16-BYTE sentences. The point here is that there is often a tradeoff between module size and relocatability. It's up to you to determine their relative importance and if so, to see if there actually is a difference. Sometimes there is no difference in code block size to include relocation as well. All it would have taken is one more instruction within the module in our file above, for example, for the Sequencer module to have required 80 BYTEs (including padding) in either case. Code smartly and accordingly!
The next step is to add a second Sequencer Mode module. The next example uses 2-BYTE RCALLs and RJMPs.