Sequencing tasks

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.............
>                                                                               
AddressValueMeaning
0x1F00x00 bModeID specifies Mode 0
0x1F10x05 bSentences = 5, which means 80 BYTEs
0x1F2 & 0x1F3  0xE0 0xE0   Valid "signature" for a Sequencer Logic block

0x2400x01 bModeID specifies Mode 1
0x2410x02 bSentences = 2, which means 32 BYTEs
0x242 & 0x243  0xE0 0xE0   Valid "signature" for a Sequencer Logic block

0x2600x02 bModeID specifies Mode 2
0x2610x04 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 highlighted below) to have 1) the Logic Block bModeID BYTE be the first BYTE, 2) wUpdate timer WORD start index 10, and 3) wModeChange timer WORD start at index 12.

The contents of each Configuration Structure for the WS2812 LED sequencer example code:
Data
Offset(s)Type NamePurpose Details
0 BYTEbModeID Selects which Locic Block to execute Sequencer searches for this one
1BYTEbBitmapped Low 4 bits are PORT; others bitmapped:
BIT 0BIT_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 1BIT_Port1
BIT 2BIT_Port2
BIT 3BIT_Port3
BIT 4BIT_UserDefined Logic Block can use for any purposeUndesignated
BIT 5BIT_Blue Enables or disables the LED BLUE component
BIT 6BIT_Red Enables or disables the LED RED component
BIT 7BIT_Green Enables or disables the LED GREEN component
2BYTEbPortsPins Specific PINs to modulate on the PORT selected   0x3F selects all PORTC bits
3BYTEbIncDec Amount to increment or decrement each iteration
4BYTEbMaxBright Maximum brightness when reinitalizing an LED
5 & 6 WORDwLEDCount Number of WS2812 LEDs in the chainmust not exceed 480
7BYTEbUserDefined1 Byte 1 which can be used for any purposeUndesignated
8BYTEbUserDefined2 Byte 2 which can be used for any purposeUndesignated
9BYTEbUserDefined3 Byte 3 which can be used for any purposeUndesignated
10 & 11 WORDwUpdateInterval Time between calls to the Logic Blockin mSec
12 & 13 WORDwModeChangeInterval   Maximum time to remain in this modein Seconds
14 & 15   WORD  wStartingAddress WS2812 BYTE data starting SRAM address 0xFFFF means use HEAP

Here are some examples of Configuration Structures in EEPROM:

>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  ..<.............
>                                                                               
Decoding the Configuration Structures shown above:
AddressbModeIDbBitmapped bPortPinsbIncDecbMaxBright wLEDCountbUserDefined# wUpdatewModeChange wStartingAddress
0x1808 B11100010B00111100 20xFF0x00F0 = 240 120120120 120 mSec30 Sec 0xFFFF
0x1908 B11100010B00111100 10xFF240 192192192 120 mSec40 Sec 0xFFFF
0x1A05 B11110010B00111111 10xFF240 019676 500 mSec50 Sec 0xFFFF
0x1B05 B11110010B00111110 10xFF240 00240 30 mSec53 Sec 0xFFFF
0x1C05 B11110010B00111100 10xFF240 25400 50 mSec60 Sec 0xFFFF
0x1D06 B01010010B00111100 0xFF0xFF240 1255252 35 mSec45 Sec 0xFFFF
0x1E06 B10010010B00111100 0xFF0xFF240 2255252 30 mSec30 Sec 0xFFFF
0x1F06 B00110010B00111100 0xFF0xFF240 3255252 19 mSec30 Sec 0xFFFF
0x2008 B00011110B00111100 0x920xE00x00C8 = 200 208208208 166 mSec20 Sec 0xA0FF
Offset: 0 123 45 & 67 89 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:

VariableEEPROM  Default   Use when
NameAddressHow it is used ValueInvalid whenInvalid
EEbSystemMode1 its BIT_SM_SEQUENCER enables the Sequencer OFF (never; it's either ON or OFF)
EEbFlags_SeqMemUse9 bMaxBright and wUpdateInterval use of an AIn 0x22equal to 0xFF 0
EEbConfig_uVars20 total number of bytes to add to uVars.B[##] 0x60greater than 0xDF (= 223) 0
EEbSequencer_UVIndex44 first index for the 20 uVars.B[##] BYTEs needed 0greater than EEbConfig_uVars + 11 0
EEptrSeqConfig60:61 pointer to first Sequencer configuration in EEPROM   0x180less than 0x50 (= 80) 0x080
EEfptrSequence1  68:69 pointer to first Sequencer code in FLASH 0x01F0greater 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
1E 1 ^= 64 stop the Sequencer automatic timed logic
2P? display the contents of the WS2812 pixel memory
3@ single step into (i.e., call) the active Logic Block
4P? display the WS2812 pixel memory again
5ESW 0x285 = 16 change the active SRAM configuration's pixel count from 240 to 16
6@ single step the active Logic Block again
7P? display the WS2812 pixel memory again; now only 50 data BYTEs
8@ single step the active Logic Block again
9P? display the smaller WS2812 pixel memory again; note the content change
10ESW 0x285 = 240 restore the active SRAM configuration's pixel count to 240
11P? display the pixel memory again; note that no content changed
12PZ send the first 16 pixels a "sleep" command
13P? display the pixel memory again; despite pixels being off, no data are unchanged
14@ single step the active Logic Block again
15P? display the pixel memory again; note the data size is back to 722 bytes
16DS+ 0x280 20 display the active configuration in SRAM
17ESW 0x292 = Sec change the wModeChange starting time to the present System Seconds
18DS+ 0x280 20 display the active configuration again; note the new WORD at 0x292
19E 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.