Sequencing means executing a specfic task, or step, until either some event has occurred or a preset time has passed, at which point the next task in the sequence is begun. That task also then has a preset time or terminating event. This process continues until all tasks have been completed, a "hold" step is entered, or the sequencing logic is preset to automatically loop back and restart with the initial step. Even a "hold" step can wait for an input and restart, or the sequencer can remain at that step until manually reinitialized.
Sequencing "Logic Blocks" are written and placed in FLASH memory to execute the logic for each step. A series of Configuration Structures, each selecting a specific Logic Block and containing timer configurations are created and place in EEPROM. These cause the Operating System Sequencer to execute the Logic Blocks at preset intervals and move from one to the next at another interval or upon specific events.
This Operating System comes preconfigured with WS2812 LED illumination tasks to illustrate the Sequencer logic. Other examples uses would be for clear water filter backwash controller, with valve reversal and pump controls, or a time and temperature controller for a solder reflow oven. Neither of those examples are built-in applications, however.
MIRTOS Logic Blocks contain the service logic to perform the desired
Sequencer task. They must be in increments of sixteen (16) BYTEs. The
first four (4) BYTEs of each Sequencer Logic Block in FLASH memory are:
the bModeID, from 0 to 127 (> 127 becomes "commented out"),
the number of "Sentences" (16 BYTEs) in the Logic Block, and
the 2-BYTE opcode for "LDI R30, I_PushAll", which is 0xE0 0xE0.
Shown below is the FLASH memory contents of the Logic Blocks for the first three (3) Modes which came with the Operating System:
> 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 ................ 0240: 01 02 E0 E0 DD DE 64 DF-11 93 21 93 31 93 11 97 ......d...!.1... 0250: D9 F7 2C 2D 21 11 95 DF-AB CF FF FF FF FF FF FF ..,-!........... 0260: 02 04 E0 E0 CD DE 54 DF-13 E0 00 81 0D 0D 0E 15 ......T......... 0270: 11 F0 08 F0 0E 2D 01 93-1A 95 B9 F7 11 97 A1 F7 .....-.......... 0280: B4 FE 05 C0 20 30 19 F4-A1 E0 AC 87 BD 87 2C 2D .... 0........,- 0290: 21 11 77 DF 8D CF FF FF-FF FF FF FF FF FF FF FF !.w............. >
Address | Value | Meaning |
---|---|---|
0x1F0 | 0x00 | bModeID specifies Mode 0 |
0x1F1 | 0x05 | bSentences = 5, which means 80 BYTEs |
0x1F2 & 0x1F3 | 0xE0 0xE0 | Valid "signature" for a Sequencer Logic block |
0x240 | 0x01 | bModeID specifies Mode 1 |
0x241 | 0x02 | bSentences = 2, which means 32 BYTEs |
0x242 & 0x243 | 0xE0 0xE0 | Valid "signature" for a Sequencer Logic block |
0x260 | 0x02 | bModeID specifies Mode 2 |
0x261 | 0x04 | bSentences = 4, which means 64 BYTEs |
0x262 & 0x263 | 0xE0 0xE0 | Valid "signature" for a Sequencer Logic block |
Since it is the smallest example Logic Block, the Source Code for Mode 1 is shown below. It sets all the pixels to the same color, with individual values for the GREEN, RED, and BLUE LED components. It uses the setup, output and exit routines which all Sequencer Logic Blocks can utilize, making the code size for this module 26 BYTEs. The six (6) BYTEs of padding is shown above as the range 0x25A to 0x25F, bringing the total block size to 32 BYTEs.
Mode1Start: .BYTE 1 ; The MODE byte: Mode 1 .BYTE (AMode1End - ASetSameColor) / 16 + 1 ; Number of 16-byte blocks used SetSameColor: ; 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 2 Use 2-BYTE CALLs/JMP for this example Mode1_Loop: ; Begin the loop; 1 loop for each WS2812 LED pixel ST Z+, R17 ; 2 2 Write the LED color GREEN component ST Z+, R18 ; 2 2 Write the LED color RED component ST Z+, R19 ; 2 2 Write the LED color BLUE component SBIW R26, 1 ; 2 2 Decrement the remaining LED count WORD, BRNE AMode1_Loop ; 2 1/2 which is maintained in R27:R26 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 is no RCALL AShared_Output ; 2 3 need to call Shared_Output() RJMP AShared_Exit ; 2 2 Mode1End: .BALIGN 16, 0xFF, 15 ; Realign to the Sentence boundary
In additional to Logic Blocks, the Sequencer uses a series of Configuration
Structures, each of which occupies 16 bytes of EEPROM. If the Sequencer
is used for some purpose other than WS2812 LEDs, eleven (11) of the BYTEs in
the configuration structure can be given other uses. The MIRTOS Sequencer
is expecting Configuration Structures to have precisely 16 bytes and (as
The contents of each Configuration Structure for the WS2812 LED sequencer example code:
Data | ||||
---|---|---|---|---|
Offset(s) | Type | Name | Purpose | Details |
0 | BYTE | bModeID | Selects which Locic Block to execute | Sequencer searches for this one |
1 | BYTE | bBitmapped | Low 4 bits are PORT; others bitmapped: | |
BIT 0 | BIT_Port0 | These 4 bits specify which PORT is to be used to output the WS2812 data to the LEDs (i.e., PORTA=0, PORTB=1, PORTC=2, PORTD=3) | ||
BIT 1 | BIT_Port1 | |||
BIT 2 | BIT_Port2 | |||
BIT 3 | BIT_Port3 | |||
BIT 4 | BIT_UserDefined | Logic Block can use for any purpose | Undesignated | |
BIT 5 | BIT_Blue | Enables or disables the LED BLUE component | ||
BIT 6 | BIT_Red | Enables or disables the LED RED component | ||
BIT 7 | BIT_Green | Enables or disables the LED GREEN component | ||
2 | BYTE | bPortsPins | Specific PINs to modulate on the PORT selected | 0x3F selects all PORTC bits |
3 | BYTE | bIncDec | Amount to increment or decrement each iteration | |
4 | BYTE | bMaxBright | Maximum brightness when reinitalizing an LED | |
5 & 6 | WORD | wLEDCount | Number of WS2812 LEDs in the chain | must not exceed 480 |
7 | BYTE | bUserDefined1 | Byte 1 which can be used for any purpose | Undesignated |
8 | BYTE | bUserDefined2 | Byte 2 which can be used for any purpose | Undesignated |
9 | BYTE | bUserDefined3 | Byte 3 which can be used for any purpose | Undesignated |
10 & 11 | WORD | wUpdateInterval | Time between calls to the Logic Block | in mSec |
12 & 13 | WORD | wModeChangeInterval | Maximum time to remain in this mode | in Seconds |
14 & 15 | WORD | wStartingAddress | WS2812 BYTE data starting SRAM address | 0xFFFF means use HEAP |
Here are some examples of Configuration Structures in EEPROM:
Decoding the Configuration Structures shown above:> DE+ 0x180 144 EEPROM contents: 0180: 08 E2 3C 02 FF F0 00 78-78 78 78 00 1E 00 FF FF ..<....xxxx..... 0190: 08 E2 3C 01 FF F0 00 C0-C0 C0 78 00 28 00 FF FF ..<.......x.(... 01A0: 05 F2 3F 01 FF F0 00 00-C4 4C F4 01 32 00 FF FF ..?......L..2... 01B0: 05 F2 3E 01 FF F0 00 00-00 F0 1E 00 35 00 FF FF ..<.........5... 01C0: 05 F2 3C 01 FF F0 00 FE-00 00 32 00 3C 00 FF FF ..<.......2.<... 01D0: 06 52 3C FF FF F0 00 01-FF FC 23 00 2D 00 FF FF .R<.......#.-... 01E0: 06 92 3C FF FF F0 00 02-FF FC 1E 00 1E 00 FF FF ..<............. 01F0: 06 32 3C FF FF F0 00 03-FF FC 13 00 1E 00 FF FF .2<............. 0200: 08 1E 3C 92 E0 C8 00 D0-D0 D0 A6 00 14 00 FF A0 ..<............. >
Address | bModeID | bBitmapped | bPortPins | bIncDec | bMaxBright | wLEDCount | bUserDefined# | wUpdate | wModeChange | wStartingAddress | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|
0x180 | 8 | B11100010 | B00111100 | 2 | 0xFF | 0x00F0 = 240 | 120 | 120 | 120 | 120 mSec | 30 Sec | 0xFFFF |
0x190 | 8 | B11100010 | B00111100 | 1 | 0xFF | 240 | 192 | 192 | 192 | 120 mSec | 40 Sec | 0xFFFF |
0x1A0 | 5 | B11110010 | B00111111 | 1 | 0xFF | 240 | 0 | 196 | 76 | 500 mSec | 50 Sec | 0xFFFF |
0x1B0 | 5 | B11110010 | B00111110 | 1 | 0xFF | 240 | 0 | 0 | 240 | 30 mSec | 53 Sec | 0xFFFF |
0x1C0 | 5 | B11110010 | B00111100 | 1 | 0xFF | 240 | 254 | 0 | 0 | 50 mSec | 60 Sec | 0xFFFF |
0x1D0 | 6 | B01010010 | B00111100 | 0xFF | 0xFF | 240 | 1 | 255 | 252 | 35 mSec | 45 Sec | 0xFFFF |
0x1E0 | 6 | B10010010 | B00111100 | 0xFF | 0xFF | 240 | 2 | 255 | 252 | 30 mSec | 30 Sec | 0xFFFF |
0x1F0 | 6 | B00110010 | B00111100 | 0xFF | 0xFF | 240 | 3 | 255 | 252 | 19 mSec | 30 Sec | 0xFFFF |
0x200 | 8 | B00011110 | B00111100 | 0x92 | 0xE0 | 0x00C8 = 200 | 208 | 208 | 208 | 166 mSec | 20 Sec | 0xA0FF |
Offset: | 0 | 1 | 2 | 3 | 4 | 5 & 6 | 7 | 8 | 9 | 10 & 11 | 12 & 13 | 14 & 15 |
Note that even some of the predefined BYTEs (e.g., bIncDec and bMaxBright) may be redefined inside a specific Logic Block's code.
You may well ask, "Why have Logic Blocks and Configuration Structures? Why do it that way?" A primary design critereon was that the code should be in small sections and be individually replaceable. It's a lot easier to debug smaller chunks of code than larger ones. A second critereon was that the Logic Blocks should be both callable and configurable without having to replace any code in FLASH memory. That is reason why the Configuration Structures were designed and implemented.
Perhaps it's easiest to see the Sequencer actually at work. The example uses the configuration example (starting at address 0x180) shown above. There are some "toggle keys." which enable optional Sequencer debugging output. To see the output below, select the ')' and '+' toggle keys.
> E 1 ^= 64 > FindNextSeqConfig(0x0000) called at FindSeqCfg_Next, R25:R24 = 0x0180 Read config byte at EEPROM (0x0180) = 0x08 WORD (LED count) at EEPROM offsets 5 and 6 (0x0185) = 0x00F0 Hit FindSeqCfg_Found; returning = 0x0180 Service_Sequencer() called Hit SvcSeq_SaveCfg; saving EEptr (in R11:R10) = 0x0180 GetUVarsIndexLtd() returned 0x00 GetUVarsAddress() returned 0x0280 FindLogicBlock(0x08) called FindLogBlkWalk ReadPGM(0x01F0) returned mode = 0x00 and length = 0x05 Mode 0x08 not found; next PGM address to check = 0x0240 FindLogBlkWalk ReadPGM(0x0240) returned mode = 0x01 and length = 0x02 Mode 0x08 not found; next PGM address to check = 0x0260 FindLogBlkWalk ReadPGM(0x0260) returned mode = 0x02 and length = 0x04 Mode 0x08 not found; next PGM address to check = 0x02A0 FindLogBlkWalk ReadPGM(0x02A0) returned mode = 0x03 and length = 0x05 Mode 0x08 not found; next PGM address to check = 0x02F0 FindLogBlkWalk ReadPGM(0x02F0) returned mode = 0x04 and length = 0x05 Mode 0x08 not found; next PGM address to check = 0x0340 FindLogBlkWalk ReadPGM(0x0340) returned mode = 0x05 and length = 0x08 Mode 0x08 not found; next PGM address to check = 0x03C0 FindLogBlkWalk ReadPGM(0x03C0) returned mode = 0x06 and length = 0x0D Mode 0x08 not found; next PGM address to check = 0x0490 FindLogBlkWalk ReadPGM(0x0490) returned mode = 0x08 and length = 0x07 FindLogicBlock() returning 0x0490 Beginning Sequence 0x08 >
The output above shows that the Sequencer logic located the first Configuration Structure at address 0x0180, determined the desired bModeID (= 8), began "walking" through the Logic Blocks until it found the Logic Logic Block with that bModeID starting address at FLASH address 0x0490, verified that it was valid and copied the 16-byte Configuration Structure to SRAM, starting at the desired uVars.B[##] index (as specified by EEbSequencer_UVIndex, which is 0 in this case). Four (4) additional BYTEs are needed in SRAM (for the two timer starting times) than the configurarion in EEPROM, so 20 BYTEs total are used. The Sequencer initializer also loaded some important configuration values in Registers R9 through R14.
This is an SRAM and Register capture shortly after the output above:
> DS+ 0 20 SRAM contents: 0280: 08 E2 3C 02 FF F0 00 78-78 78 78 00 1E 00 FF FF ..<....xxxx..... 0290: 74 3A 00 00 t:.. >R 10 32 54 76 98 1110 1312 1514 1716 1918 2120 2322 2524 2726 2928 3130 ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- 0000 0303 0909 0928-0004 0180 0249 4000-0041 00FF 5201 0000-0000 0230 0280 3166 >DF+ 0x490 0x70 ; Logic Block at 0x490: FLASH (program memory) contents: 0490: 08 07 E0 E0 0E 94 00 00-0E 94 88 00 43 E0 50 E0 ............C.P. 04A0: 2F 01 60 81 60 30 21 F0-6D 19 08 F4 60 E0 53 95 /.`.`0!.m...`.S. 04B0: 61 93 4A 95 B1 F7 55 2B-C9 F4 F2 01 20 E8 40 EE a.J...U+.... .@. 04C0: CB 2C C4 22 09 F4 C4 2E-80 E0 6C 2D 62 23 51 F0 .,."......l-b#Q. 04D0: F2 2E 8D 01 3F 01 8E 2D-EF E6 0E 94 00 00 2F 2D ....?..-....../- 04E0: D8 01 F3 01 81 93 26 95-20 32 70 F7 11 97 B1 F6 ......&. 2p..... 04F0: 2A 81 21 11 0E 94 C1 00-0C 94 D8 00 FF FF FF FF *.!............. >
R9 has the EEbFlags_SeqMemUse value read from EEPROM once each second.
R11:R10 holds the pointer to active Sequencer Configuration in EEPROM, which is 0x0180 above.
R13:R12 holds the WORD pointer Logic Block in FLASH, which is 0x0249 above.
R14 has the base UVars.B[##] index used by the Sequencer, which is zero above.
Check out the code for Mode 8 above.
The value 0x249 in R13:R12 is the Logic Block's WORD address. We are most familiar with FLASH BYTE addresses, not WORD addresses. Multiply the PROGRAM WORD address by 2 to get the PROGRAM BYTE address (so 0x249 * 2 =) 0x0492. This is the entry point for the bModeID 8 Logic Block with 0xE0 0xE0, the required first instruction's opcode of "LDI R30, I_PushAll" being the MCU instruction at that location. The Logic Block 8 shown has 4 BYTEs of padding (0x4FC through 0x4FF.)
The EEPROM variables which are of interest to the MIRTOS Sequencer functions:
Variable | EEPROM | Default | Use when | ||
---|---|---|---|---|---|
Name | Address | How it is used | Value | Invalid when | Invalid |
EEbSystemMode | 1 | its BIT_SM_SEQUENCER enables the Sequencer | OFF | (never; it's either ON or OFF) | |
EEbFlags_SeqMemUse | 9 | bMaxBright and wUpdateInterval use of an AIn | 0x22 | equal to 0xFF | 0 |
EEbConfig_uVars | 20 | total number of bytes to add to uVars.B[##] | 0x60 | greater than 0xDF (= 223) | 0 |
EEbSequencer_UVIndex | 44 | first index for the 20 uVars.B[##] BYTEs needed | 0 | greater than EEbConfig_uVars + 11 | 0 |
EEptrSeqConfig | 60:61 | pointer to first Sequencer configuration in EEPROM | 0x180 | less than 0x50 (= 80) | 0x080 |
EEfptrSequence1 | 68:69 | pointer to first Sequencer code in FLASH | 0x01F0 | greater than 0x39FF | 0x01F0 |
Want to have some fun? Well, OK, what I call "fun" and what you call
"fun" may be different, but try this anyway:
Step | Enter this command | to effect this action and notice | |
---|---|---|---|
1 | E 1 ^= 64 | stop the Sequencer automatic timed logic | |
2 | P? | display the contents of the WS2812 pixel memory | |
3 | @ | single step into (i.e., call) the active Logic Block | |
4 | P? | display the WS2812 pixel memory again | |
5 | ESW 0x285 = 16 | change the active SRAM configuration's pixel count from 240 to 16 | |
6 | @ | single step the active Logic Block again | |
7 | P? | display the WS2812 pixel memory again; now only 50 data BYTEs | |
8 | @ | single step the active Logic Block again | |
9 | P? | display the smaller WS2812 pixel memory again; note the content change | |
10 | ESW 0x285 = 240 | restore the active SRAM configuration's pixel count to 240 | |
11 | P? | display the pixel memory again; note that no content changed | |
12 | PZ | send the first 16 pixels a "sleep" command | |
13 | P? | display the pixel memory again; despite pixels being off, no data are unchanged | |
14 | @ | single step the active Logic Block again | |
15 | P? | display the pixel memory again; note the data size is back to 722 bytes | |
16 | DS+ 0x280 20 | display the active configuration in SRAM | |
17 | ESW 0x292 = Sec | change the wModeChange starting time to the present System Seconds | |
18 | DS+ 0x280 20 | display the active configuration again; note the new WORD at 0x292 | |
19 | E 1 ^= 64 | restart the Sequencer automatic timed logic System Function |
Notes: | |
If the MSBit of a Logic Block's bModeID (i.e., the first) BYTE is set (or TRUE or ON), that Logic Block will be skipped during the search. This allows a Logic Block with code being tested to be "commented out" and not be used by the Sequencing System Function. That code can still be called with the "@" (indirect call) System Command, of course. Be careful! | |
If the MSBit of a Configuration Structure's bModeID BYTE is set, that Configuration will be skipped. This allows any Configuration Structure to be "commented out", but all of its other contents (including the rest of bModeID) to be retained. | |
If a Logic Block's bModeID is 0xFF, the Sequencer will stop searching at that point. Since uninitialized FLASH has the value 0xFF, the Sequencer understands that to mean that no more Logic Blocks are available, in effect. Upon coming to the end of a search without finding the target Logic Block, no more searches will be done until the System Seconds changes. | |
If a Configuration Structure's bModeID is 0xFF, it marks the end of the Configuration Table. The next Configuration will be that defined by EEptrSeqConfig, in effect, looping back to the first Configuration Structure the next time it is checked. | |
If a Logic Block's bBlockSize (i.e., the second) BYTE is less than 2 or more than 62, it will end the search at that point and will not search again until the System Seconds changes. | |
The reason fruitless searches are abandoned for the duration of the current System Second is to prevent them from consuming all of the available time. This is a cooperative multitasking Operating System, after all. | |
Modifying the active Configuration Structure in SRAM, which was copied from EEPROM, does not change the content of the one in EEPROM. This means that any of the sixteen (16) bytes may be temporarily changed (like the timer or pixel count above) for testing purposes without making the change more permanent. | |
Modifying a Configuration Structure in EEPROM does not change the active one in SRAM. | |
When a Logic Block has been loaded and the Sequencer stopped, neither of its timers are checked. | |
Sequencing can be resumed (using E 1 ^= 64) at any time; both timers will be immediately checked, which may cause the step to advance if the wModeChange timer times out. | |
When coding a new subroutine, consider placing a range of NOPs in it. Code can then be added, any DumpRegisters() or DumpRegs_Begin() System Calls to be made within the block and that section of code reloaded fairly quickly, thereby speeding up testing. |
See also: | |
The special toggle keys, the "@" indirect subroutine call, and the "P" pixel System Commands as well as the DumpRegisters and DumpRegs_Begin System Function calls. |