7

I am trying to understand how real time operating systems work. I have looked at the source codes of some RTOSes. I want to learn by creating my simple RTOS, something like FLIRT.

I am using PIC16 series and XC8 C compiler with MPLABX. I also want to implement this very simple RTOS to PIC12 series.

So, I decided that I should start by learning how to manipulate stack (like supercat did in this answer) and I started searching and came across AN818 which is titled "Manipulating the Stack of the PIC18 Microcontroller". Cited from the application note:

Traditionally, the microcontroller stack has only been used as a storage space for return addresses of subroutines or interrupt routines, where all ‘push’ and ‘pop’ operations were hidden.

For the most part, users had no direct access to the information on the stack. The PIC18 microcontroller diverges from this tradition slightly. With the new PIC18 core, users now have access to the stack and can modify the stack pointer and stack data directly.

I am confused. How come there are RTOSes made for PIC microcontrollers that work with PIC16 cores? For example, OSA RTOS is available for PIC12/16 with mikroC compiler.

Can you direct me to some resources, or if possible give examples, so that I can learn about stack switching?

abdullah kahraman
  • 5,930
  • 7
  • 59
  • 104

3 Answers3

4

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.

supercat
  • 45,939
  • 2
  • 84
  • 143
  • I could not understand the code except from the PCLATH setting part. What are JUMPCx and TargetCx? What does moving `JumpC4 & 255` to W does? – abdullah kahraman Jun 09 '12 at 20:38
  • Thanks for the edit! Would be better if you have noticed me about it :) As far as I understand, at every yield in a task, there is a return target at the task's table. This is what I do in C now, something very similar to [this](http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html). I do not understand why there is a need to the tables? [This](http://www.piclist.com/techref/microchip/multitasking.htm#cooperative) page shows that it can be done only using one variable per task, or two if there is paging. – abdullah kahraman Jun 10 '12 at 12:27
  • 1
    There are a number of slightly-different approaches one can use to achieve multitasking on the PIC. The advantage of the jump-table approach is that it uses single-byte variables to keep track each task's next step, but still allows tasks themselves to use 2K worth of code. If the code for each task will fit in a 256-byte page, one can forgo the jump table. Alternatively, if one doesn't want to use the jump table, one can use two-byte variables for each task's 'next step', but the task switching code gets slower and bulkier. – supercat Jun 10 '12 at 16:10
  • As for using `movlw/goto` versus `retlw`, the latter saves a word of code space per task-switch, but ends up being a cycle slower because of the need to `call` a "springboard". More significantly, when using `movlw/goto`, it's possible for one task to perform task switches from within nested routines; if one were to limit the use of `retlw` to tasks which didn't need to switch from within nested routines, that would be fine, but mixing task-switching paradigms could add confusion. – supercat Jun 10 '12 at 16:15
4

The traditional 14 bit core PICs (mostly PIC 16 and a few PIC 12) don't have a way for the program to access the call stack. This therefore makes a true multi tasking system impossible.

However, you can still have what I call pseudo tasks. This is a routine that is called periodically from the main event loop. It maintains a restart address internally. When the main even loop calls it, it jumps to the restart address of the pseudo task private to that module. After some processing, the task code invokes a YIELD macro that sets the restart address to immediately after the macro, then returns from the original call. Since the call stack can't be unwound, YIELD can only be invoked from the top task level. Still this is a useful abstraction.

You can see a example of such a pseudo task being used to process a command stream from a host computer in my QQQ_CMD.ASPIC template module. In this case the return to the caller is hidden in the GETBYTE macro, which appears to go and get the next input byte from the task's point of view. Install my PIC Development Tools release from the software downloads page. QQQ_CMD.ASPIC will be in the SOURCE > PIC directory within the software installation directory.

To see how real multi-tasking can be done when you have access to the call stack, take a look at TASK.INS.ASPIC in the same directory. That is my generic multi-tasking system for the PIC 18. You can see the same thing for the dsPIC architecture by looking at TASK.INS.DSPIC in the SOURCE > DSPIC directory.

Olin Lathrop
  • 310,974
  • 36
  • 428
  • 915
  • It seems like I have to start learning assembly. – abdullah kahraman Jun 09 '12 at 08:46
  • @abdullah: Yes, you need assembly language to perform unnatural acts on the stack, which is necessary in most task switching schemes. You must at least understand assembly anyway to know what's going on in the low levels, even if you program in a high level language. On microcontrollers, you can't ignore hardware details. It's not like on a large general purpose system where there are many layers of abstraction between you and the hardware. All EEs using microcontrollers *must* know assembly. – Olin Lathrop Jun 09 '12 at 12:36
  • I think I don't have to memorize all the instructions and such, they are all in the datasheet. What I should understand are the hardware details. I have looked at the `GETBYTE` macro but couldn't understand anything at all. I will get back to it later on. Now, I am looking at [Pic'Xe](http://picxe.sourceforge.net/) and I can understand the assembly when I glance the datasheet. However, I do not understand what do FSR and INDF etc. mean, which are hardware. So, I am now following PIC x14 architecture tutorials [here](http://www.pictutorials.com/Flash_Tutorials.htm). – abdullah kahraman Jun 09 '12 at 13:23
  • @abdullah: What part of GETBYTE don't you understand? I can walk thru it, but specific questions would help. Note that my code assumes the macros defined in STD.INS.ASPIC and is also run thru my preprocessor (PREPIC, described in DOC directory) before MPASM sees any of it. I just looked at GETBYTE, and it does use some of my macros (DBANKIF, JUMP, etc), but not the preprocessor. – Olin Lathrop Jun 09 '12 at 14:46
  • Ah, it is really too hard for me to understand GETBYTE. However, what I understand from YIELD macro is that it saves the restart address of the function, which is immediately after the macro, into the function's stack. When a task switch trigger occurs, it loads the PC with the next task's restart address. What I understand is that if I do not need any task switching in the sub-routines, which I have never did, then this is effectively the same technique with stack switching. – abdullah kahraman Jun 09 '12 at 20:27
  • Now, if I have understood the concept of "pseudo tasks", I should find ways to first make it work with bare minimum and then adapting it to C, since I am desperate with the assembler. It will take me months to be familiar with the assembler. One thing I have noticed is that assembler is far more effective than C, however it will take me weeks to finish writing a program with assembler, where with C it will take me hours. – abdullah kahraman Jun 09 '12 at 20:32
  • @abdullah: What YIELD macro? I scanned all my code in the PIC source directory and the only YIELD macro I could find was in NET_DHCP.INS.ASPIC. That's actually a pretty good example of a pseudo-task, but I didn't menition is previously because I forgot it was there. You will need some assembler to implement a pseudo task since it requires jumping to a arbitrary address stored in a variable. You're supposed to be a EE, so go learn assembler. – Olin Lathrop Jun 10 '12 at 11:59
  • Well, actually I was referencing the YIELD macro in your answer. I had a class in my final year (I've graduated last year) in the university about 8086 assembler and I got an A on that. So I am familiar with assembler. I have learnt about PIC assembler recently, and I have understood all the code on [this](http://www.piclist.com/techref/microchip/multitasking.htm#cooperative) page, finally :). That is actually what I want to implement in C. – abdullah kahraman Jun 10 '12 at 12:18
  • By the way, I have looked at the listing files that are generated by my compiler and oh, dear! It is basically creating mess! – abdullah kahraman Jun 10 '12 at 12:19
2

The PIC16 series "14 bit core" chips have a "hardware stack RAM" that is completely separate and independent of the "data RAM". (It even has a different size and width than the data RAM). The "core block diagram" for any of those chips clearly shows that the "hardware stack" is connected only to the program counter -- the only instruction that writes to that hardware stack is (the various types of) CALL, and the only instruction that reads from that hardware stack is (the various types of) RETURN.

"stack switching" is impossible for the "hardware stack" in these "14 bit core" chips.

This makes most traditional OS task-switching methods impossible on this series of chips.

There are a few RTOSes that do run on this series of chips, so therefore there must be a few task-switching methods that are possible on this chip ( "PIC Microcontroller specific Multitasking Methods" ).

In practice, all the RTOSes I've seen have some sort of cooperative task-switcher that "yields" from one task to the next only in the top-level code of each task. Because the yield is never from inside a subroutine, there are no return addresses on the hardware stack.

In principle, it's possible to program these chips to build a software stack in RAM and use that to simulate a return stack. (Using a "user defined stack" in data RAM, rather than the hardware stack). Then you could use all the traditional OS task-switching stuff, such as giving each task a separate block of RAM for its local stack, etc. I don't know if the extra complexity and non-standard call sequences would be worth it.

davidcary
  • 17,426
  • 11
  • 66
  • 115