11
\$\begingroup\$

I am trying to create a semi-pre-emptive (co-operative) RTOS for PIC x16 microcontrollers. In my previous question, I've learnt that accessing hardware stack pointer is not possible in these cores. I have looked at this page in PIClist, and this is what I am trying to implement using C.

My compiler is Microchip XC8 and currently I am working on a PIC16F616 with 4MHz internal RC oscillator selected in the configuration bits.

I have learnt that I can access PCLATH and PCL registers with C, looking at the header file of my compiler. So, I tried to implement a simple task switcher.

It works as wanted in the debugger if I pause the debugger after restart, reset, and set PC at cursor when the cursor is not on the first line (TRISA=0;) but on an another line (for example ANSEL=0;). In the first start of the debugger I get these messages in the Debugger Console:

Launching
Programming target
User program running
No source code lines were found at current PC 0x204

Edit: I don't know what made it work, but debugger now works perfectly. So, omit the above output and paragraph.

Edit: Changing the main definition like this makes the code below work. This starts the main function at program address 0x0099. I don't know what causes this. This is not a real solution. I am now guessing that there is a compiler specific error.

void main(void) @ 0x0099
{

Here is my C code:

/* 
 * File:   main.c
 * Author: abdullah
 *
 * Created on 10 Haziran 2012 Pazar, 14:43
 */
#include <xc.h> // Include the header file needed by the compiler
__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & CP_OFF & IOSCFS_4MHZ & BOREN_ON);
/*
 * INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN
 * WDT disabled and can be enabled by SWDTEN bit of the WDTCON register
 * PWRT enabled
 * MCLR pin function is digital input, MCLR internally tied to VDD
 * Program memory code protection is disabled
 * Internal Oscillator Frequency Select bit : 4MHz
 * Brown-out Reset Selection bits : BOR enabled
 */

/*
 * OS_initializeTask(); definition will copy the PCLATH register to the task's PCLATH holder, which is held in taskx.pch
 * This will help us hold the PCLATH at the point we yield.
 * After that, it will copy the (PCL register + 8) to current task's PCL holder which is held in taskx.pcl.
 * 8 is added to PCL because this line plus the "return" takes 8 instructions.
 * We will set the PCL after these instructions, because
 * we want to be in the point after OS_initializeTask when we come back to this task.
 * After all, the function returns without doing anything more. This will initialize the task's PCLATH and PCL.
 */
#define OS_initializeTask(); currentTask->pch = PCLATH;\
                             currentTask->pcl = PCL + 8;\
                             asm("return");

/*
 * OS_yield(); definition will do the same stuff that OS_initializeTask(); definition do, however
 * it will return to "taskswitcher" label, which is the start of OS_runTasks(); definition.
 */

#define OS_yield();          currentTask->pch = PCLATH;\
                             currentTask->pcl = PCL + 8;\
                             asm("goto _taskswitcher");

/*
 * OS_runTasks(); definition will set the "taskswitcher" label. After that it will change the
 * current task to the next task, by pointing the next item in the linked list of "TCB"s.
 * After that, it will change the PCLATH and PCL registers with the current task's. That will
 * make the program continue the next task from the place it left last time.
 */

#define OS_runTasks();       asm("_taskswitcher");\
                             currentTask = currentTask -> next;\
                             PCLATH = currentTask->pch;\
                             PCL = currentTask->pcl;

typedef struct _TCB // Create task control block and type define it as "TCB"
{
    unsigned char pch; // pch register will hold the PCLATH value of the task after the last yield.
    unsigned char pcl; // pcl register will hold the PCL value of the task after the last yield.
    struct _TCB* next; // This pointer points to the next task. We are creating a linked list.
} TCB;

TCB* currentTask; // This TCB pointer will point to the current task's TCB.

TCB task1; // Define the TCB for task1.
TCB task2; // Define the TCB for task2.

void fTask1(void); // Prototype the function for task1.
void fTask2(void); // Prototype the function for task2.

void main(void)
{
    TRISA = 0; // Set all of the PORTA pins as outputs.
    ANSEL = 0; // Set all of the analog input pins as digital i/o.
    PORTA = 0; // Clear PORTA bits.

    currentTask = &task1; // We will point the currentTask pointer to point the first task.

    task1.next = &task2; // We will create a ringed linked list as follows:
    task2.next = &task1; // task1 -> task2 -> task1 -> task2 ....

    /*
     * Before running the tasks, we should initialize the PCL and PCLATH registers for the tasks.
     * In order to do this, we could have looked up the absolute address with a function pointer.
     * However, it seems like this is not possible with this compiler (or all the x16 PICs?)
     * What this compiler creates is a table of the addresses of the functions and a bunch of GOTOs.
     * This will not let us get the absolute address of the function by doing something like:
     * "currentTask->pcl=low(functionpointer);"
     */
    fTask1(); // Run task1 so that we get the address of it and initialize pch and pcl registers.
    currentTask = currentTask -> next; // Point the currentTask pointer to the next pointer which
    fTask2(); // is task2. And run task2 so that we get the correct pch and pcl.

    OS_runTasks(); // Task switcher. See the comments in the definitions above.
}

void fTask1(void)
{
    OS_initializeTask(); // Initialize the task
    while (1)
    {
        RA0 = ~RA0; // Toggle PORTA.0
        OS_yield(); // Yield
        RA0 = ~RA0; // Toggle PORTA.0
    }
}

void fTask2(void)
{
    OS_initializeTask(); // Initialize the task
    while (1)
    {
        RA1 = ~RA1; // Toggle PORTA.1
        OS_yield(); // Yield
        RA1 = ~RA1; // Toggle PORTA.1
    }
}

And here is the disassembly listing file that my compiler created. Starts at line 74.

I have programmed the actual chip, and no change on PORTA at all; it doesn't work.

What is the reason my program doesn't work?

\$\endgroup\$

6 Answers 6

10
+50
\$\begingroup\$

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?).

\$\endgroup\$
6
  • \$\begingroup\$ Thanks for the great info! I am determined on creating a co-operative task switcher. XC8 and PIC are not on my side on this, I am aware of that :) Yes, as you can see, it is possible to create labels as I did in one of my answers to this question. \$\endgroup\$ Commented Jun 21, 2012 at 18:15
  • \$\begingroup\$ Also, to my luck, there is no paging of the program memory for the PIC16F616 which I am working on, that is a great advantage at this point, right? \$\endgroup\$ Commented Jun 21, 2012 at 18:18
  • \$\begingroup\$ Could you explain more how will local variables overlap in memory, and also "scratch memory locations"? \$\endgroup\$ Commented Jun 21, 2012 at 18:22
  • \$\begingroup\$ If you restrict yourself to chips with 2K code or less you can indeed forget about the lgoto, but not about the 256-instruction 'pages'. Scratch: a compiler can assume anything it does in memory stays in place unless it it is 'volatile'. So it might put partial calculations in some location that can be shared by different functions. Ovelap: if main() calls both f() and g() (and there are no other calls), the local variables of f() and g() can be mapped to the same memory locations. \$\endgroup\$ Commented Jun 21, 2012 at 18:40
  • \$\begingroup\$ Well, it seems like it is almost impossible to reach those variables and store, due to their random place in memory, right? \$\endgroup\$ Commented Jun 21, 2012 at 19:08
7
\$\begingroup\$

I browsed through the assembly listing you provided, and nothing jumps out as obviously broken.

If I were you, my next steps would be:

(1) I would choose some other method of blinking the LEDs. The notorious "read-modify-write problem" may (or may not) be triggered by the "XORWF PORTA, F" in the assembly listing.

Perhaps something like:

// Partial translation of code from abdullah kahraman
// untested code
// feel free to use however you see fit
void fTask2(void)
{
    OS_initializeTask(2); // Initialize task 2
    while (1)
    {
        PORTC = 0xAA;
        OS_yield(2); // Yield from task 2
        PORTC = 0x55;
        OS_yield(2); // Yield from task 2
    }
}

(If you really want to see detailed explanations about why "XORWF PORTA, F" often causes problems, see " What causes turning ON a single output pin on Microchip PIC16F690 to spontaneously turn OFF another pin on the same port? "; " What happens when data is written to LATCH? "; "The Read-Modify-Write problem"; "Read before Write" )

(2) I would single-step through the code, making sure that the variables are being set to the expected values and in the expected sequence. I'm not sure if there exists a single-step hardware debugger for the PIC16F616, but there are many excellent PIC microcontroller simulators such as PICsim that can simulate PIC16 series chips.

Single-stepping code (in a simulator or with a single-step hardware debugger) is a good way to understand the details of what is really going on, confirm that things are happening the way you intended, and it lets you see things that are practically impossible to see when running the program full-speed.

(3) If I'm still stumped, I would try translating the code to use arrays rather than pointers. Some people find using pointers a bit tricky and difficult to debug. I often find that, in the process of translating tricky pointer code into array-oriented code, I find out what the bug is. Even if I end up reverting to the original pointer code and throwing away the array version, the exercise is useful because it helped me find and fix the bug. (Sometimes the compiler can generate shorter, faster code from array-oriented code, so there are times I throw out the original pointer code and keep the array version).

Perhaps something like

// Partial translation of code from abdullah kahraman
// untested code
// feel free to use however you see fit
struct TCB_t // Create task control block and type define it as "TCB_t"
{
    unsigned char pch; // PCLATH value
    unsigned char pcl; // PCL value
    int next; // This array index points to the next task. We are creating a linked list.
};

int currentTask = 1; // This TCB index will point to the current task's TCB.

struct TCB_t tasks[3]; // Define the TCB for task1 and task2.

#define OS_initializeTask(x); tasks[x].pch = PCLATH;\
                             tasks[x].pcl = PCL + 8;\
                             asm("return");

#define OS_runTasks();       asm("_taskswitcher");\
                             currentTask = tasks[currentTask].next;\
                             PCLATH = tasks[currentTask].pch;\
                             PCL = tasks[currentTask].pcl;

#define OS_yield(x);         tasks[x].pch = PCLATH;\
                             tasks[x].pcl = PCL + 8;\
                             asm("goto _taskswitcher");
\$\endgroup\$
1
  • \$\begingroup\$ I am implementing arrays now. Thanks for the recommendation. \$\endgroup\$ Commented Jun 12, 2012 at 17:21
3
\$\begingroup\$

I would basically agree with davidcary. It looks like it could work.

I don't know what made it work, but debugger now works perfectly.

I'm guessing by this you mean that it works perfectly in the simulator.

1) Check that your tasks work on their own, in a non-RTOS environment in the real chip.

2) Do in-circuit debugging. Step through the program on the real chip, and watch all of the relevant variables to be sure that everything's going as planned.

\$\endgroup\$
1
  • \$\begingroup\$ Yes, I've meant the debugger, that is the simulator of MPLABX. Tasks work on their own, in a non-RTOS environment. I do not have an ICD. I only have mikroElektronika easyPIC5 with ICD, however, it only works with mikroC compiler. Now, changing compilers will not let me find the problem, or, will it? \$\endgroup\$ Commented Jun 12, 2012 at 6:21
1
\$\begingroup\$

I only looked at your code briefly, but it makes no sense. In several places you are writing to PCL, then expecting it to excute other instructions following that.

As I also said before, C is inappropriate for this kind of low level accessing of fundamental hardware registers. You really need to use assembly for this. Trying to figure out why C code is not working is just a pointless waste of time.

\$\endgroup\$
9
  • \$\begingroup\$ I could not combine assembly and C. I had to do lots of work. Both the dis-assembly and C code seems logical to me. Where are you referring that I am expecting to execute instructions that follow a write to PCL? I have watched the debugger both for assembly and C, and it works as wished. \$\endgroup\$ Commented Jun 11, 2012 at 11:47
  • \$\begingroup\$ Sorry for the -1. I should have pressed accidently and I have noticed that now. \$\endgroup\$ Commented Jun 11, 2012 at 11:48
  • \$\begingroup\$ @abdullah: On the machine I am at now, I can't see the source code. It is permanently collapsed in the browser. I remember that you assigned stuff to PCLATH, then PCL, then I think in one case attempted to do a RETURN. As soon as you write to PCL, execution is going to jump to the address you stuffed into PCLATH:PCL, so any following instructions are irrelevant. It is really not good to do this in C because you are messing with compiler managed resources and thereby possibly invalidating compiler assumptions. Use real assembly already. I'm getting tired of having to repeat this. \$\endgroup\$ Commented Jun 11, 2012 at 11:53
  • 1
    \$\begingroup\$ Looking at the code, there is nowhere that PCL is modified just before another statement. The only place it seems to be modified is at the very end of main(). But it's a good point that you must be very sure you are not fighting the compiler for its resources. Both of you will lose. \$\endgroup\$ Commented Jun 11, 2012 at 15:33
  • 3
    \$\begingroup\$ C is perfectly acceptable for this kind of work, and in fact it's preferable to write in a mid-level language like C as opposed to assembly language because it's easier to read and maintain. A decent compiler will generate code not too far off from what the average person will write anyway. I typically only write assembler for the very basic startup code, specific areas where I can optimize better than the compiler or for fast interrupts, or if code size constraints dictate it. There isn't much need for pure assembly these days. \$\endgroup\$
    – akohlsmith
    Commented Jun 19, 2012 at 19:13
1
\$\begingroup\$

Below is the way to do it with in-line assembly using XC8 compiler, and it works now! However, I need to add develop more code for saving and restoring the STATUSregister, which seems a little trickier than it is for a normal register.

Edit: Code is changed. Please refer to older versions of this post for the previous code.

/*
 * File:   main.c
 * Author: abdullah
 *
 * Created on 10 Haziran 2012 Pazar, 14:43
 */
#include <xc.h> // Include the header file needed by the compiler
#include "RTOS.h" // Include the header for co-operative RTOS.
__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & CP_OFF & IOSCFS_4MHZ & BOREN_ON);

unsigned char OS_currentTask; // This register holds the current task's place in the array OS_tasks
unsigned char OS_tasks[4]; // This array holds PCL and PCLATH for tasks. This array will have..
//                            .. (number of tasks)*2 elements, since every task occupies 2 places.

void fTask1(void); // Prototype the function for task1.
void fTask2(void); // Prototype the function for task2.

void main(void)
{
    TRISA = 0; // Set all of the PORTA pins as outputs.
    TRISC = 0; // Set all of the PORTC pins as outputs.
    ANSEL = 0; // Set all of the analog input pins as digital i/o.
    PORTA = 0; // Clear PORTA bits.
    PORTC = 0; // Clear PORTC bits.

    OS_currentTask = 0; // Current task is first task.
    fTask1(); // Call task to initialize it.
    OS_currentTask += 2; // Increment task pointer by two since every task occupies 2 places in the array.
    fTask2(); // Call task to initialize it.
    OS_runTasks(4); // Run the tasks in order. The argument of this macro takes is: (Number of tasks) * 2
}

void fTask1(void)
{
    OS_initializeTask(); // Initialize the task so that task runner can get its ingredients.
    while (1)
    {
        PORTC = 0xAA;
        OS_yield(); // Yield CPU to other tasks.
        PORTC = 0x55;
        OS_yield(); // Yield CPU to other tasks.
    }
}

void fTask2(void)
{
    OS_initializeTask(); // Initialize the task so that task runner can get its ingredients.
    while (1)
    {
        PORTC = 0xFF;
        OS_yield(); // Yield CPU to other tasks.
        PORTC = 0x00;
        OS_yield(); // Yield CPU to other tasks.
    }
}

And here is the header file RTOS.h:

/* 
 * File:   RTOS.h
 * Author: abdullah
 *
 * Created on 21 Haziran 2012 Perşembe, 10:51
 */

#ifndef RTOS_H
#define RTOS_H

asm("OS_yield MACRO");
asm("local OS_tmp");
asm("movlw   _OS_tasks            ; Store the address of tasks, which is the start address of our task 'array'."); 
asm("addwf   _OS_currentTask, w   ; Add current task's index to the start address."); 
asm("movwf   fsr                  ; We have the index of current task in W. Copy it to FSR"); 
asm("movlw   high(OS_tmp)         ; Copy PCLATH register's contents for the label, to W register.");
asm("movwf   indf                 ; Copy W to current task's first item. We now store PCLATH of the current state of the task."); 
asm("incf    fsr, f               ; Increment index, so that we will point to the next item of current task."); 
asm("movlw   low(OS_tmp)          ; Copy PCL of the label to W register. This will let us save the PCL of the current state of the task.");
asm("movwf   indf                 ; Copy W to task's next item. With that, we will initialize the current task.");
asm("goto    OS_taskswitcher");
asm("OS_tmp:                      ; We will use this label to gather the PC of the return point.");
asm("ENDM"); 

#define OS_yield(); asm("OS_yield");

asm("OS_initializeTask MACRO");
asm("local   OS_tmp");
asm("movlw   _OS_tasks            ; Store the address of tasks, which is the start address of our task 'array'."); 
asm("addwf   _OS_currentTask, w   ; Add current task's index to the start address."); 
asm("movwf   fsr                  ; We have the index of current task in W. Copy it to FSR"); 
asm("movlw   high(OS_tmp)        ; Copy PCLATH register's contents for the label, to W register."); 
asm("movwf   indf                 ; Copy W to current task's first item. We now store PCLATH."); 
asm("incf    fsr,f                ; Increment index, so that we will point to the next item of current task."); 
asm("movlw   low(OS_tmp)         ; Copy PCL of the label to W register. This will let us save the PCL of the current state of the task."); 
asm("movwf   indf                 ; Copy W to task's next item. With that, we will initialize the current task."); 
asm("return                       ; We have gathered our initialazation information. Return back to main."); 
asm("OS_tmp                      ; We will use this label to gather the PC of the return point.");
asm("ENDM"); 

#define OS_initializeTask(); asm("OS_initializeTask");

asm("OS_runTasks MACRO numberOfTasks");
asm("global OS_taskswitcher");
asm("OS_taskswitcher:");
asm("CLRWDT"); 
asm("movlw   0x02                 ; W = 2"); 
asm("addwf   _OS_currentTask, f   ; Add 2 to currentTask, store it in currentTask."); 
asm("movlw   numberOfTasks        ; W = numOfTasks");
asm("subwf   _OS_currentTask, w   ; w= f - w"); 
asm("btfsc   status, 0            ; If currentTask >= numOfTasks"); 
asm("clrf    _OS_currentTask      ; Clear currentTask"); 
asm("movlw   _OS_tasks            ; Store the address of tasks, which is the start address of our task 'array'."); 
asm("addwf   _OS_currentTask, w   ; Add current task's index to the start address."); 
asm("movwf   fsr                  ; We have the index of current task in W. Copy it to FSR"); 
asm("movf    indf, w              ; Copy the contents of current task's first item to W"); 
asm("movwf   pclath               ; Copy W to PCLATH. As a result, current task's PCLATH will be in PCLATH register."); 
asm("incf    fsr, f               ; Increment index, so that we will point to the next item of current task."); 
asm("movf    indf, w              ; Copy the contents of current task's second item to W."); 
asm("movwf   pcl                  ; Copy W to PCL. Finally, current task's PCL will be in PCL register.");
asm("ENDM");

#define OS_runTasks(numberOfTasks); asm("OS_runTasks "#numberOfTasks);

#endif  /* RTOS_H */
\$\endgroup\$
6
  • \$\begingroup\$ Looks like you're going to win your own bounty. Congratulations! :-) \$\endgroup\$
    – stevenvh
    Commented Jun 26, 2012 at 10:40
  • \$\begingroup\$ @stevenvh Ah, does that happen, I didn't know? Thanks :) \$\endgroup\$ Commented Jun 26, 2012 at 10:42
  • \$\begingroup\$ Congratulations for getting it to work! \$\endgroup\$
    – davidcary
    Commented Jul 10, 2012 at 14:58
  • \$\begingroup\$ Thanks @davidcary ! I really appreciate your congratulations guys. \$\endgroup\$ Commented Jul 10, 2012 at 15:50
  • 1
    \$\begingroup\$ Do you really need to restore STATUS? If so, you'll need to use the "swapf" instruction, for reasons documented elsewhere: "P. Anderson", "Microchip Mid-range family manual: section 8.5 Context Saving", "PIC saving W and STATUS" \$\endgroup\$
    – davidcary
    Commented Jul 13, 2012 at 17:48
0
\$\begingroup\$

Below is how to implement this using assembly. Access the same code with formatting (links to Pastebin). How can it be improved? This is my first program in PIC assembly, any comment is appreciated.

list p=16f616
#include p16f616.inc

;*** Configuration Bits ***
__CONFIG _FOSC_INTOSCIO & _WDTE_OFF & _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _IOSCFS_8MHZ & _BOREN_ON
;**************************

;*** Variable Definitions ***
VARS        UDATA                   ; Define undefined data(s).
numOfTasks  res     1               ; This variable holds the number of tasks multiplied by 2.
currentTask res     1               ; Index variable that points to the current task's index in "tasks"
tasks       res     4               ; This is task "array". Every task occupies 2 bytes.
;****************************

;*** Reset Vector ***
RESET   CODE    0x0000              ; Define a code block starting at 0x0000, which is reset vector, labeled "RESET"
        goto    start               ; Start the program.
;********************

;*** Main Code ***
MAIN    CODE
start                               ; Label the start of the program as "start".
        banksel TRISA               ; Select appropriate bank for TRISA.
        clrf    TRISA               ; Clear TRISA register. Configure all of the PORTA pins as digital outputs.
        clrf    TRISC               ; Clear TRISC register. TRISC and TRISA are at the same bank, no need for "banksel".
        clrf    ANSEL               ; Clear ANSEL register and configure all the analog pins as digital i/o.
        banksel PORTA               ; Select appropriate bank for PORTA.
        clrf    PORTA               ; Clear PORTA register.
        clrf    PORTC               ; Clear PORTC register. PORTC and PORTA are at the same bank, no need for "banksel".


        movlw   0x04                ; W = Number of tasks * 2.
        movwf   numOfTasks          ; Since every task has two datas in it, we will multiply by 2.
        clrf    currentTask         ; Set the task#0 as current task.

        CALL    task0               ; Call task#0 since we need to initialize it. We are going to get..
                                    ; ..its PCL and PCLATH values at the start address.
        movlw   0x02                ; W = 2
        addwf   currentTask, f      ; Increment currentTask by 2, since every task occupies 2 places.

        CALL    task1               ; Call task#1, for initialazation.

taskswitcher
        movlw   0x02                ; W = 2
        addwf   currentTask, f      ; Add 2 to currentTask, store it in currentTask.
        movf    numOfTasks, w       ; W = numOfTasks
        subwf   currentTask, w      ; w= f - w
        btfsc   STATUS, 0           ; If currentTask >= numOfTasks
        clrf    currentTask         ; Clear currentTask

        movlw   tasks               ; Store the address of tasks, which is the start address of our task "array".
        addwf   currentTask, w      ; Add current task's index to the start address.
                                    ; For example; task1's index is 2:  [task0_1][task0_2][task1_1][task1_2]....
                                    ;                                       0        1        2        3
        movwf   FSR                 ; We have the index of current task in W. Copy it to FSR
        movf    INDF, w             ; Copy the contents of current task's first item to W
        movwf   PCLATH              ; Copy W to PCLATH. As a result, current task's PCLATH will be in PCLATH register.

        incf    FSR, f              ; Increment index, so that we will point to the next item of current task.
        movf    INDF, w             ; Copy the contents of current task's second item to W.
        movwf   PCL                 ; Copy W to PCL. Finally, current task's PCL will be in PCL register.

        goto    $                   ; This instruction is not effective. But, enter the endless loop.

;*** TASK 0 ***
TASK0   CODE
;**************
task0
        movlw   tasks               ; Store the address of tasks, which is the start address of our task "array".
        addwf   currentTask, w      ; Add current task's index to the start address.

        movwf   FSR                 ; We have the index of current task in W. Copy it to FSR
        movf    PCLATH, w           ; Copy PCLATH register's contents to W register.
        movwf   INDF                ; Copy W to current task's first item. We now store PCLATH.

        incf    FSR,f               ; Increment index, so that we will point to the next item of current task.
        movlw   low($+3)            ; Copy PCL+3 to W register. This will let us save the PCL of the start of the task.
        movwf   INDF                ; Copy W to task's next item. With that, we will initialize the current task.
        return                      ; We have gathered our initialazation information. Return back to main.

task0main
        banksel PORTA               ; Select the appropriate bank for PORTA
        movlw   0xAA                ; Move literal to W so that W = 0xAA
        movwf   PORTA               ; PORTA = 0xAA. Use a LATA register to create more robust code.

        movlw   tasks               ; Store the address of tasks, which is the start address of our task "array".
        addwf   currentTask, w      ; Add current task's index to the start address.

        movwf   FSR                 ; We have the index of current task in W. Copy it to FSR
        movf    PCLATH, w           ; Copy PCLATH register's contents to W register.
        movwf   INDF                ; Copy W to current task's first item. We now store PCLATH of the current state of the task.

        incf    FSR,f               ; Increment index, so that we will point to the next item of current task.
        movlw   low($+3)            ; Copy PCL+3 to W register. This will let us save the PCL of the current state of the task.
        movwf   INDF                ; Copy W to task's next item. With that, we will initialize the current task.

        goto    taskswitcher        ; Yield the CPU to the awaiting task by going to task switcher.

        banksel PORTA               ; Select the appropriate bank for PORTA
        movlw   0x55                ; Move literal to W so that W = 0x55
        movwf   PORTA               ; PORTA = 0xAA. Use a LATA register to create more robust code.

        goto    task0main           ; Loop by going back to "task0main". We will continuously toggle PORTA.

;*** TASK 1 ***
TASK1   CODE
;**************
task1
        movlw   tasks               ; Store the address of tasks, which is the start address of our task "array".
        addwf   currentTask, w      ; Add current task's index to the start address.

        movwf   FSR                 ; We have the index of current task in W. Copy it to FSR
        movf    PCLATH, w           ; Copy PCLATH register's contents to W register.
        movwf   INDF                ; Copy W to current task's first item. We now store PCLATH.

        incf    FSR,f               ; Increment index, so that we will point to the next item of current task.
        movlw   low($+3)            ; Copy PCL+3 to W register. This will let us save the PCL of the start of the task.
        movwf   INDF                ; Copy W to task's next item. With that, we will initialize the current task.
        return                      ; We have gathered our initialazation information. Return back to main.

task1main
        banksel PORTA               ; Select the appropriate bank for PORTA
        movlw   0xAA                ; Move literal to W so that W = 0xAA
        movwf   PORTA               ; PORTA = 0xAA. Use a LATA register to create more robust code.

        movlw   tasks               ; Store the address of tasks, which is the start address of our task "array".
        addwf   currentTask, w      ; Add current task's index to the start address.

        movwf   FSR                 ; We have the index of current task in W. Copy it to FSR
        movf    PCLATH, w           ; Copy PCLATH register's contents to W register.
        movwf   INDF                ; Copy W to current task's first item. We now store PCLATH of the current state of the task.

        incf    FSR,f               ; Increment index, so that we will point to the next item of current task.
        movlw   low($+3)            ; Copy PCL+3 to W register. This will let us save the PCL of the current state of the task.
        movwf   INDF                ; Copy W to task's next item. With that, we will initialize the current task.

        goto    taskswitcher        ; Yield the CPU to the awaiting task by going to task switcher.

        banksel PORTA               ; Select the appropriate bank for PORTA
        movlw   0x55                ; Move literal to W so that W = 0x55
        movwf   PORTA               ; PORTA = 0xAA. Use a LATA register to create more robust code.

        goto    task1main           ; Loop by going back to "task1main". We will continuously toggle PORTA.

        END                         ; END of the program.
\$\endgroup\$
2
  • \$\begingroup\$ Your first program in assembly is a multi-tasking RTOS? Wow. Most people are doing really good if they can get an LED to blink. :-). \$\endgroup\$
    – davidcary
    Commented Jul 10, 2012 at 2:59
  • \$\begingroup\$ Well, actually this is my first assembly program in the PIC architecture. Before that, in the university, I have taken 8086 classes but they were not practical and I had to learn by myself because the lecturer was a substitute and didn't know anything, yet asking hard questions in the exams.. \$\endgroup\$ Commented Jul 10, 2012 at 6:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.