contents:
Note that task switching is very different from the switch statement used in the C language.
See Multitasking methods@ .
This page mainly talks about cooperative multitasking. For small systems where the programmer has complete control of every task, cooperative multitasking can be superior to pre-emptive multitasking.
In other situations, pre-emptive multitasking can be superior .. but unfortunately, I'm pretty sure it's impossible to implement pre-emptive multitasking on 12x and 16x series PIC chips. On 18x series, apparently it is possible.
"Assuming this is on a PIC ... then cooperative is almost certainly the way to go. Most of the time multi-tasking isn't needed for a PIC project. When it is, it would be extremely rare indeed if cooperative multi-tasking couldn't solve the same problem with less overhead than preemptive. I've done somewhere between 50 and 100 PIC projects. Out of those maybe a dozen or so had at least one "task" in that there was code that looked like it was an infinite loop, but actually returned execution to the rest of the system thru what looked like a call or macro to the task. Only 3 have been true multi-tasking systems where everything was in a task, and every task called something like TASK_YIELD to let all the other tasks run for a while. And all of those were cooperative. I have yet to run into a PIC project where preemptive multi-tasking would have been a good solution."
http://piclist.com/techref/postbot.asp?by=time&id=piclist\1997\06\02\031933a&tgt=post has a good, simple overview of cooperative task switching in PIC assembly code.
There's a nice discussion at Stack Overflow: "Multithreading using C on PIC18" .
Even the smallest PIC can handle several ``state machines''.
[FIXME: isn't there another page on massmind somewhere that deals with state machines ?]
(I probably should have called this macro "yield" rather than "switch" -- is it too late to rename it?)
Roman Black also lists other ideas for time synchronizing multiple tasks on this page. In particular, he shows a clever scheme for making sure a task is run precisely 100 times per second on average, i.e., once every 10 ms on average, even if some other tasks occasionally "hogs" the time and runs for 90 ms.
POSIT is a very simple cooperative multitasking system. The main loop of each task is always run in sequence:
; main runtime task scheduler when POSIT is set up for 4 tasks: core call t0_run ; main loop of task 0 call t1_run ; main loop of task 1 call t2_run ; main loop of task 2 call t3_run ; main loop of task 3 goto core
I'm pretty sure the Calvin RTOS works the same way.
More complicated cooperative multitasking routines allow a task to return to the task scheduler *before* the end of it's main loop, and (once the task scheduler has run a few other routines) resumes executing right where it left off in that main loop.
Rich Leggitt says:
...I thought of a way to cut 'switch' to one instruction, it ain't gonna get any smaller than that :)switch macro retlw $+1 ; return 'return' endm context equ 0x20 movlw task1 ; note task 1 will run first movwf context movlw task2 ; task switcher call $+2 ; make a place for 'switch' to return to goto $-1 ; (i.e. here!) xorwf context,f ; then exchange w and context xorwf context,w xorwf context,f movwf pcl ; and jmp to w task1 blah switch etc switch stuff goto task1 task2 asdf switch zxcz goto task2
to which Scott Dattalo commented:
Cool! Now that you write this, I recall someone else (I believe it was Payson) doing something similar. Now that you've got the 'context switching' all confined to one section, you've opened up the possibilities of extending its functionality (without having duplicate code snippets scattered throughout).
- ) You could easily add additional tasks. Cycling through each can be done in a 'round-robin' fashion. For example, after task A then run task B, C,..., N, and back to A.
- ) You could add priorities to the tasks.
- ) With an extra instruction in the 'switch' macro, you could handle multiple pages:
switch macro movlw HIGH($+2) ;[ed: I think we need to save W to somewhere at this point to avoid over-writing it. For example: movwf switchpage ; where switchpage is a temp register.] retlw $+1 endmIf you're using the 12bit core, then you might consider populating the stack with the address of the context switcher. You could do the same with the 14bit core - but chances are you're more likely to need the stack for making calls. (on the 12bit core, the stack is only 2 levels ) Once the stack is filled, then the retlw's will take advantage of stack roll overs.
movlw task1 movwf context movlw task2 setc clrf first_time goto l2 l1 rlf first_time,f btfss first_time,1 l2 call l1 l3 xorlw context,f ; then exchange w and context xorlw context,w xorlw context,f movwf pcl ; and jmp to wfor the 14bit core you could fill the stack:
movlw task1 movwf context movlw task2 setc clrf first_time goto l2 l1 rlf first_time,f btfss first_time,7 l2 call l1 l3 xorlw context,f ; then exchange w and context xorlw context,w xorlw context,f movwf pcl ; and jmp to wI haven't tried this, but once you get the stack 'primed', you can save 4 execution cycles.
(perhaps this "switch" macro would be better named "yield" ?)
; warning: untested code. ; Is there a better way ? ; 2006-04-18:DAV: David Cary extended to handle many tasks. ; ???: Original ideas from Rich Leggitt. switch macro retlw $+1 ; return 'return' endm task1_context res 1 task2_context res 1 task3_context res 1 task4_context res 1 task5_context res 1 movlw task1 ; note task 1 will run first movwf task1_context movlw task2 movwf task2_context movlw task3 movwf task3_context movlw task4 movwf task4_context movlw task5 movwf task5_context task_switcher: movf task1_context,w call switch_to_task ; make a place for 'switch' to return to movwf task1_context movf task2_context,w call switch_to_task movwf task2_context movf task3_context,w call switch_to_task movwf task3_context movf task4_context,w call switch_to_task movwf task4_context movf task5_context,w call switch_to_task movwf task5_context goto task_switcher switch_to_task: movwf pcl task1 blah switch etc task1_main_loop switch stuff goto task1_main_loop task2 asdf switch zxcz goto task2 task3 asdf switch zxcz goto task3 task4 asdf task4_loop1 call do_something switch skpz goto task4_loop1 task4_loop2 switch call do_something_else switch call that_other_thing skpz goto task4_loop2 call oh_yeah_one_last_thing switch goto task4 task5 asdf task5_main_loop: switch zxcz goto task5_main_loop
See the answers at "What is the reason my PIC16 multitasking RTOS kernel doesn't work?" for a cooperative multitasking system for the PIC16 that apparently works even when the tasks are located on multiple pages.
An earlier attempt:
The felix.althaus asked for more details on how to extend it to properly handle multiple pages.
<felix.althaus at swissonline.ch> asks: " Hi You said, you could handle paging with the instruction 'movlw HIGH($+2)'. But how do you store it back?"
One could use the above switch code as long as you pick one page for page switching, and *only* use the ``switch'' macro on that page. Other pages can be used for subroutines, as long as you are very careful not to use the ``switch'' macro in those subroutines. (You can't use any of the switch macros on this page inside a subroutine in any case).
Another method would be to add a few instructions to the ``switch'' macro and the context switcher so ``switch'' can be called from any page.
; warning: untested code. ; Is there a better way ? ; 2001-10-19:DAV: David Cary extended to handle multiple pages. ; ???: more ideas from Scott Dattalo. ; ???: Original ideas from Rich Leggitt. ; RAM to bookmark current location in each task. context_lo equ 0x20 context_hi equ 0x21 temp equ 0x77 ; FIXME: ``temp'' is a temp register; ``banked'' location OK as long ; as that location is reserved in *every* bank as a temp location. ; ``switch'' overwrites ``temp'' and the W register. switch macro movlw LOW($+3) movwf temp retlw HIGH($+1) ; return 'return' endm ;... power_on_setup: ; ... movlw LOW(task1) ; note task 1 will run first movwf context_lo movlw HIGH(task1) movwf context_hi movlw LOW(task2) movwf temp movlw HIGH(task2) ; task switcher call $+2 ; make a place for 'switch' to return to goto $-1 ; (i.e. here!) ; Bookmark the task we came *from* ; (currently in w:temp) ; in context_hi:context_lo. ; Resume the task we want to go *to* ; (currently bookmarked in context_hi:context_lo) into PCLATH:PCL. ; Swapping code optimized for 2 tasks. ; Code to handle 3 or more tasks ; would probably be much easier to understand. ; handle Hi part (the page) first xorwf context_hi,f xorwf context_hi,w xorwf context_hi,f movwwf PCLATH ; then handle Lo part of the address last movfw temp xorwf context_lo, f xorwf context_lo, w xorwf context_lo, f ; end Strange hack. movwf pcl ; and jmp to w task1 blah switch fcall input_stuff movwf temp ; OK to use temp as long as we're done before the next switch fcall get_mask andwf temp fcall output_stuff etc switch stuff goto task1 task2 asdf switch zxcz goto task2
Scott Dattalo 2000-02-10 came up with ideas ``it might be simpler to combine a state-machine and a task switcher'' to make switching between several tasks that are not in the same bank quicker than the above code:
; warning: untested code. ; Is there a better way ? ; 2002-03-04:DAV: copied ideas from Scott Dattalo to make ; faster and shorter (?). ; 2001-10-19:DAV: David Cary extended to handle multiple pages. ; ???: more ideas from Scott Dattalo. ; ???: Original ideas from Rich Leggitt. ; RAM to bookmark current location in each task. context equ 0x20 ; ``switch'' overwrites the W register. switch macro continuation_lable retlw ((continuation_lable)-task_table_start) ; return pointer to where to continue task endm ;... power_on_setup: ; ... movlw (t1_1 - task_table_start) ; note task1_offset1 will run first movwf context movlw (t2_1 - task_table_start) ; task switcher call $+2 ; make a place for 'switch' to return to goto $-1 ; (i.e. here!) ;context_switcher: ; Swapping code optimized for 2 tasks. ; Code to handle 3 or more tasks ; would probably be much easier to understand. xorwf context,w xorwf context,f xorwf context,w addwf pcl,f task_table_start: t1_1 goto task1_offset1 t1_2 goto task1_offset2 t1_3 goto task1_offset3 t2_1 goto task2_offset1 t2_2 goto task2_offset2 t2_3 goto task2_offset3 task1_offset1 ... normal code blah ... switch task1_offset2 task1_offset2 fcall input_stuff movwf temp fcall get_mask andwf temp fcall output_stuff etc switch task1_offset2 task1_offset2 stuff more_stuff ; select the address at which the task ; will next begin executing. ; this is very tricky. btfss condition,condition_bit switch(task1_offset2) switch(task1_offset3) task1_offset3 blah blah blah goto task1 task2 asdf switch task2_offset2 task2_offset2 zxcz goto task2
Warning: none of these ``switch'' macros work right when used inside a subroutine.
John Payson 1997-06-02 http://piclist.com/techref/postbot.asp?by=time&id=piclist\1997\06\02\021004a&tgt=post writes about a variation that allows one task to have regular subroutines nested as deep as the stack will go, and that task can Yield() at any and every level. All other tasks, however, have the same limitation as the switch() / Yield() on this page -- switch() can only be called from the main loop of the task, not from any subroutine.
[FIXME: is there a good, simple explanation somewhere of exactly what a co-routine is ? http://c2.com/cgi/wiki?CoRoutine left me confused. ]
18C specific co-routines
18C specific
http://microchipc.com/Hi-Tech_C_multitask.htm "Time Sliced Multi-tasking"... timer or other interrupt triggers various tasks ... the real-time part is done right-away in the interrupt; parts that are OK to interrupt are triggered by setting a flag in the interrupt ... then the background task (main loop) checks the flags and executes the appropriate task-handler ...
(Or would some other instruction(s) work better?)
Operating systems on PCs ../oss.htm
Software Stacks sstack.htm and http://piclist.com/techref/postbot.asp?by=time&id=piclist\1997\07\18\090751a&tgt=post has some ideas about simulating a stack in software, to allow something closer to true pre-emptive multitasking (and also allowing *much* deeper stack nesting).
See http://www.piclist.com/techref/microchip/pages.htm for more details on paging and PCLATH.
Hi,
In the cooperative multitasking section of this page, there are comments and code regarding "cooperative multitasking routines [that] allow a task to return to the task scheduler *before* the end of it's main loop, and (once the task scheduler has run a few other routines) resumes executing right where it left off in that main loop".
At first I thought this was some really clever stuff, but after some examination, I'm not so sure this is different than things I achieve in a different way. It's quite conceivable that I'm missing some crucial nuance here, and I'd really like to learn what that is! ;-)
I would say there are actually 5 tasks in the example, not just "task1" and "task2". Let's call them "blah", "asdf", "etc", "stuff", and "zxcz". Please explain how the code below is any different than that proposed by Rich Leggitt
; ---- My cooperative multitasking scheme ---- ; by Karl Liebau call blah call asdf main: call etc call zxcz call stuff call zxcz goto main blah: nop return asdf: nop return etc: nop return stuff: nop goto blah zxcz: nop goto asdf ; ---- End proposed equivalent code ----
I believe this code to be functionally equivalent to the original example. This code doesn't need W or a temp register to work. It is extensible by merely adding CALL statements in the main loop as-needed. It looks easier to me, but many of the contributors here are much better at this than I am. So, I offer my humble thanks to anyone who can explain the underlying difference between the two examples.
-- Karl Liebau +
Nifty. You are right -- this proposed code *is* functionally equivalent to the original example -- running the "zxcz" code twice as often as the other bits of code. And it's simpler and faster. So this is a useful addition to the bag of tricks.
But how would this proposed task-switcher handle code like this?:
(there exists a clever method of loading the data byte with "1", then when that shifts into the carry bit, it signals we have a full 8 bits. Instead of trying to be clever, here we use a seperate "bit_count" counter. I'm attempting to write Screechingly Obvious Code .)
; ... ; using the Rich Leggitt task switcher ; ... ; the task switcher itself and the other task(s) ; omitted for clarity ; ... clock_line EQU PORTA,3 data_line EQU PORTA,5 data_byte res 1 bit_count res 1 task1: movlw 8 movwf bit_count ; wait for bit on data line (without blocking other tasks) wait_for_hi: switch btfss clock_line goto wait_for_hi wait_for_lo: switch btfsc clock_line goto wait_for_lo ; grab the bit at this falling edge rrl data_byte,f ; copy bit on data_line into lsb ; But first, get rid of random Carry flag that rrl shifted in bcf data_byte,0 btfsc data_line bsf data_byte,0 decfsz bit_count goto wait_for_hi switch movfw data_byte call write_byte nop goto task1
Is it possible to rewrite this task so the above "proposed" task switcher will work? (Maybe there is a way, but I don't see it just now). Do you see what I'm trying to say here? -- DavidCary
Hi David,Below is how I'd handle your example of a bit-banged serial receive under cooperative multitasking. We again don't need to preserve W, and other tasks can be added as desired without blocking. I do add one flag bit to the mix, and once that's done, other routines can use the other 7 bits of "flags" if needed without using any more RAM.
This was a quick spin -- there may be errors!
---- Proposed equivalent code ----
clock_line EQU PORTA,3 data_line EQU PORTA,5 data_byte res 1 bit_count res 1 flags res 1 clrf flags call Task1 ; initialize the bit counter Main: btfss flags,0 ; don't bother with Task2 unless we've seen a LO on clock_line call Task2 btfsc flags,0 ; don't bother with Task3 unless we've seen a HI on clock_line call Task3 ; don't bother with Task5 until we've collected all 8 bits ; if( 0 == bit_count ){ call Task5 }; tstf bit_count skpnz call Task5 ; other tasks here as needed ; ... goto main Task1: ; load a bit counter with d'8' movlw 8 movwf bit_count Task2 ; watch the clock_line for a HI transition (without blocking other tasks) btfsc clock_line bsf flags,0 return Task3 ; watch for the clock_line LO transition (without blocking other tasks) btfss clock_line goto Task4 return Task4 ; grab the data bit and append it to data_byte bcf flags,0 ; grab the bit at this falling edge rrl data_byte,f ; copy bit on data_line into lsb ; But first, get rid of random Carry flag that rrl shifted in bcf data_byte,0 btfsc data_line bsf data_byte,0 decfsz bit_count return Task5 ; store the received byte movfw data_byte call write_byte nop call Task1 ; re-init the bit counter nop return---- End proposed equivalent code ----
I hope you like it!~
kl
The "round robin" style (is there a better name?) is better than the "switch" style (is there a better name?) when each task starts from its beginning and runs through to its end every time. But for a complicated tasks (ones with several long-running loops), this round robin code forces me to move some of the flow-control code to the task scheduler.
For those more complicated tasks, I think the "switch" macro helps keep all the related code together. (When I suspect the latency of a series consecutive subroutine calls in one task is "too long", I can slip a "switch" statement in the middle -- or between each and every one -- without any changes to the task scheduler). -- DavidCary
If you have multiple ``things'' that need to be done, consider using multiple processors. Is anyone using multiple PICs for anything interesting ? Or even (heterogenous multiprocessing) a PIC and some other CPU/MCU ? Athough Mike Watson points out: http://piclist.com/techref/postbot.asp?by=time&id=piclist\1997\06\05\035449a&tgt=post ``Synergy does not apply to multi-processor systems. You always end up with less than the sum of the parts because of the overhead of sharing data.''
DAV mostly agrees if you're going to put all those CPUs in one box. However, if you want to control lots of differently widely-seperated things, often it's cheaper to (a) put a PIC in each box and connect all the boxes with a single fat power pair (shared power bus) + a single thin serial communication pair (daisy chain ? shared bus ?) than to (b) run several fat power lines to each thing.
DAV is currently working on a project that he expects will network dozens, perhaps hundreds of small microcontrollers in a "data collection grid".
Questions:
At the moment i have a programme which checks several inputs on the pic16f627, when an input is detected the pic initiates one of three subroutines. the subroutine initiates an output sequence which sends the output pins high sequentially with a delay of about 20s between each. I want to run a flashing LED simultaniusly while this subroutine is initiated. Thyank you to anybody that can help me. Kashif Meherali
Interested:
See also:
file: /Techref/microchip/multitasking.htm, 34KB, , updated: 2015/11/23 19:21, local time: 2024/11/23 20:16,
owner: DAV-MP-E62a,
3.142.172.190:LOG IN
|
©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions? <A HREF="http://massmind.org/techref/microchip/multitasking.htm"> PIC specific Multitasking </A> |
Did you find what you needed? |
Welcome to massmind.org! |
Welcome to massmind.org! |
.