Every RTOS for a PIC which does not have a software-addressable stack generally requires that all but one of the tasks must have its work divided into uninterruptable pieces which begin and end at the top stack level; the "task-yield" operation does not use a function call, but rather a sequence like
// This code is part of task C (assume for this example, there are tasks
// called A, B, C
movlw JumpC4 & 255
goto TASK_SWITCH_FROM_C
TargetC4:
Elsewhere in the code would be some code like:
TASK_SWITCH_FROM_A:
movwf nextJumpA // Save state of task C
// Now dispatch next instruction for task A
movlw TaskB_Table >> 8
movwf PCLATH
movf nextJumpB,w
movwf PCL
TASK_SWITCH_FROM_B:
movwf nextJumpB // Save state of task C
// Now dispatch next instruction for task A
movlw TaskC_Table >> 8
movwf PCLATH
movf nextJumpC,w
movwf PCL
TASK_SWITCH_FROM_C:
movwf nextJumpC // Save state of task C
// Now dispatch next instruction for task A
movlw TaskA_Table >> 8
movwf PCLATH
movf nextJumpA,w
movwf PCL
At the end of the code, for each task, there would be a jump table; each table would have to fit within a 256-word page (and could thus have a maximum of 256 jumps)
TaskC_Table:
JumpC0 : goto TargetC0
JumpC1 : goto TargetC1
JumpC2 : goto TargetC2
JumpC3 : goto TargetC3
JumpC4 : goto TargetC4
...etc.
Effectively, the movlw
at the start of the task-switch sequence loads the W register with the LSB of the address of the instruction at JumpC4
. The code at TASK_SWITCH_FROM_C
would stash that value someplace, and then dispatch the code for task A. Later, after TASK_SWITCH_FROM_B
is executed, the stored JumpC4
address would be reloaded into W and the system would jump to the instruction pointed to thereby. That instruction would be a goto TargetC4
, which would in turn resume execution at the instruction following the task-switch sequence. Note that task switching doesn't use the stack at all.
If one wanted to do a task switch within a called function, it might be possible to do so if that function's call and return were handled in a manner similar to the above (one would probably have to wrap the function call in a special macro to force the proper code to be generated). Note that the compiler itself wouldn't be capable of generating code like the above. Instead, macros in the source code would generate directives in the assembly-language file. A program supplied by the RTOS vendor would read the assembly-language file, look for those directives, and generate the appropriate vectoring code.
What you are trying to do is tricky, but very educational (if you are prepared to spend a lot of effort).
First, you must realise that this kind of PC-only (as opposed to PC+SP) task switching (which is the only thing you can do on a plain 12 or 14-bit PIC core) will only work when all the yield() statements in a task are in the same funtion: they can't be in a called function, and the compiler must not have messed with the function structure (as optimization might do).
Next:
currentTask->pch = PCLATH;\
currentTask->pcl = PCL + 8;\
asm("goto _taskswitcher");
- You seem to assume that PCLATH is the upper bits of the program counter, as PCL is the lower bits. This is NOT the case. When you write to PCL the PCLATH bits are written to the PC, but the upper PC bits are never (implicitly) written to PCLATH. Re-read the relevant section of the datasheet.
- Even if PCLATH was the upper bits of the PC, this would get you into trouble when the instruction after the goto is on not on the same 256-instruction 'page' as the first instruction.
- the plain goto will not work when _taskswitcher is not in the current PCLATH page, you will need an LGOTO or equivalent.
A solution to your PCLATH problem is to declare a label after the goto, and write the lower and upper bits of that label to your pch and pcl locations. But I am not sure you can declare a 'local' label in inline assembly. You sure can in plain MPASM (Olin will smile).
Lastly, to this kind of context switching you must save and restore ALL context that the compiler might depend on, which might include
- indirection register(s)
- status flags
- scratch memory locations
- local variables that might overlap in memory because the compiler does not realise that your tasks must be independent
- other things I can't imagine right now but the compiler author might use in the next version of the compiler (they tend to be very imaginative)
The PIC architecture is more problematic in this respect because a lot of resources are loacted all over the memory map, where more traditional architectures have them in registers or on the stack. As a consequence, PIC compilers often do not generate reentrant code, which is what you definitely need to do the things you want (again, Olin will probaly smile and assemble along.)
If you are into this for the joy of writng an task switcher I suggest that you swicth to a CPU that has a more traditional organization, like an ARM or Cortex. If you are stuck with your feet in a concrete plate of PICs, study existing PIC switchers (for instance salvo/pumkin?).
Best Answer
Page 98 of the HITEC C Manual lists all the pre-defined macros.
One of the entries is:
so you have, as other examples:
etc.