¿Cuál es la razón por la que mi núcleo RTOS multitarea PIC16 no funciona?

11

Estoy tratando de crear un RTOS semi-preventivo (cooperativo) para microcontroladores PIC x16. En mi pregunta anterior , aprendí que el acceso al puntero de la pila de hardware no es posible en estos núcleos. He mirado esta página en PIClist, y esto es lo que estoy tratando de implementar usando C.

Mi compilador es Microchip XC8 y actualmente estoy trabajando en un PIC16F616 con un oscilador RC interno de 4MHz seleccionado en los bits de configuración.

Aprendí que puedo acceder a los registros PCLATH y PCL con C, mirando el archivo de encabezado de mi compilador. Entonces, traté de implementar un conmutador de tareas simple.

Funciona como se desea en el depurador si detengo el depurador después de reiniciar, restablecer y configurar la PC en el cursor cuando el cursor no está en la primera línea ( TRISA=0;) sino en otra línea (por ejemplo ANSEL=0;). En el primer inicio del depurador recibo estos mensajes en Debugger Console:

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

Editar: No sé qué lo hizo funcionar, pero el depurador ahora funciona perfectamente. Por lo tanto, omita el resultado y el párrafo anteriores.

Editar: cambiar la definición principal de esta manera hace que el siguiente código funcione. Esto inicia la función principal en la dirección del programa 0x0099. No sé qué causa esto. Esta no es una solución real. Ahora supongo que hay un error específico del compilador.

void main(void) @ 0x0099
{

Aquí está mi código C:

/* 
 * 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
    }
}

Y aquí está el archivo de listado de desmontaje que creó mi compilador. Empieza a las line 74.

He programado el chip real, y ningún cambio en PORTA; No funciona.

¿Cuál es la razón por la que mi programa no funciona?

abdullah kahraman
fuente

Respuestas:

10

Lo que intenta hacer es complicado, pero muy educativo (si está preparado para gastar mucho esfuerzo).

Primero, debe darse cuenta de que este tipo de cambio de tareas solo para PC (a diferencia de PC + SP) (que es lo único que puede hacer en un núcleo PIC de 12 o 14 bits) solo funcionará cuando todo el rendimiento ( ) las declaraciones en una tarea están en la misma función: no pueden estar en una función llamada, y el compilador no debe haberse metido con la estructura de la función (como podría hacer la optimización).

Próximo:

currentTask->pch = PCLATH;\
currentTask->pcl = PCL + 8;\
asm("goto _taskswitcher");
  • Parece suponer que PCLATH son los bits superiores del contador del programa, ya que PCL es los bits inferiores. Este no es el caso. Cuando escribe en PCL, los bits de PCLATH se escriben en la PC, pero los bits superiores de la PC nunca (implícitamente) se escriben en PCLATH. Vuelva a leer la sección relevante de la hoja de datos.
  • Incluso si PCLATH fuera la parte superior de la PC, esto te metería en problemas cuando la instrucción después del goto no esté en la misma 'página' de 256 instrucciones que la primera instrucción.
  • el goto simple no funcionará cuando _taskswitcher no esté en la página PCLATH actual, necesitará un LGOTO o equivalente.

Una solución a su problema PCLATH es declarar una etiqueta después del goto y escribir los bits inferior y superior de esa etiqueta en sus ubicaciones pch y pcl. Pero no estoy seguro de que pueda declarar una etiqueta 'local' en el ensamblaje en línea. Seguro que puedes en MPASM simple (Olin sonreirá).

Por último, para este tipo de cambio de contexto, debe guardar y restaurar TODO el contexto del que dependa el compilador, que puede incluir

  • registro (s) de indirección
  • banderas de estado
  • ubicaciones de memoria de memoria virtual
  • variables locales que pueden superponerse en la memoria porque el compilador no se da cuenta de que sus tareas deben ser independientes
  • otras cosas que no puedo imaginar en este momento, pero el autor del compilador podría usar en la próxima versión del compilador (tienden a ser muy imaginativas)

La arquitectura PIC es más problemática a este respecto porque muchos recursos están ubicados en todo el mapa de memoria, donde las arquitecturas más tradicionales los tienen en registros o en la pila. Como consecuencia, los compiladores PIC a menudo no generan código reentrante, que es lo que definitivamente necesita para hacer las cosas que desea (nuevamente, Olin probablemente sonreirá y se reunirá).

Si le gusta esto por el placer de escribir un conmutador de tareas, le sugiero que cambie a una CPU que tenga una organización más tradicional, como un ARM o Cortex. Si está atrapado con los pies en una placa de PIC concreta, estudie los conmutadores PIC existentes (por ejemplo, ¿salvo / pumkin?).

Wouter van Ooijen
fuente
Gracias por la gran información! Estoy decidido a crear un conmutador de tareas cooperativo. XC8 y PIC no están de mi lado en esto, soy consciente de eso :) Sí, como puede ver, es posible crear etiquetas como lo hice en una de mis respuestas a esta pregunta.
abdullah kahraman
Además, para mi suerte, no hay paginación de la memoria del programa para el PIC16F616 en el que estoy trabajando, eso es una gran ventaja en este momento, ¿verdad?
Abdullah Kahraman
¿Podría explicar más cómo se superponen las variables locales en la memoria y también las "ubicaciones de memoria de memoria virtual"?
abdullah kahraman
Si se limita a chips con código 2K o menos, puede olvidarse del lgoto, pero no de las 'páginas' de 256 instrucciones. Scratch: un compilador puede asumir que todo lo que hace en la memoria permanece en su lugar a menos que sea "volátil". Por lo tanto, podría colocar cálculos parciales en alguna ubicación que puedan compartir diferentes funciones . Superposición: si main () llama a f () yg () (y no hay otras llamadas), las variables locales de f () y g () pueden asignarse a las mismas ubicaciones de memoria.
Wouter van Ooijen
Bueno, parece que es casi imposible alcanzar esas variables y almacenarlas, debido a su lugar aleatorio en la memoria, ¿verdad?
abdullah kahraman
7

Eché un vistazo a la lista de ensamblados que proporcionó, y nada sobresale como obviamente roto.

Si yo fuera tú, mis próximos pasos serían:

(1) Elegiría algún otro método para parpadear los LED. El notorio "problema de lectura-modificación-escritura" puede (o no) ser provocado por el "XORWF PORTA, F" en la lista de ensamblados.

Quizás algo como:

// 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
    }
}

(Si realmente desea ver explicaciones detalladas acerca de por qué "XORWF PORTA, F" a menudo causa problemas, consulte " ¿Qué causa que el encendido de un pin de salida en Microchip PIC16F690 apague espontáneamente otro pin en el mismo puerto? "; " ¿Qué sucede? cuando los datos se escriben en LATCH? ";" El problema de lectura-modificación-escritura ";" Leer antes de escribir ")

(2) Pasaría un solo paso por el código, asegurándome de que las variables se establezcan en los valores esperados y en la secuencia esperada. No estoy seguro de si existe un depurador de hardware de un solo paso para el PIC16F616, pero hay muchos excelentes simuladores de microcontroladores PIC como PICsim que pueden simular chips de la serie PIC16.

El código de un solo paso (en un simulador o con un depurador de hardware de un solo paso) es una buena manera de comprender los detalles de lo que realmente está sucediendo, confirmar que las cosas están sucediendo de la manera que pretendía y le permite ver las cosas que están sucediendo. Prácticamente imposible de ver cuando se ejecuta el programa a toda velocidad.

(3) Si todavía estoy perplejo, intentaría traducir el código para usar matrices en lugar de punteros. Algunas personas encuentran que usar punteros es un poco complicado y difícil de depurar. A menudo encuentro que, en el proceso de traducir código de puntero complicado en código orientado a matrices, descubro cuál es el error. Incluso si termino volviendo al código de puntero original y descartando la versión de matriz, el ejercicio es útil porque me ayudó a encontrar y corregir el error. (A veces, el compilador puede generar código más corto y más rápido a partir del código orientado a la matriz, por lo que hay veces que descarto el código de puntero original y mantengo la versión de la matriz)

Quizás algo como

// 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");
davidcary
fuente
Estoy implementando matrices ahora. Gracias por la recomendación.
Abdullah Kahraman
3

Básicamente estaría de acuerdo con davidcary. Parece que podría funcionar.

No sé qué lo hizo funcionar, pero el depurador ahora funciona perfectamente.

Supongo que con esto quieres decir que funciona perfectamente en el simulador .

1) Verifique que sus tareas funcionen solas, en un entorno que no sea RTOS en el chip real.

2) Realizar depuración en circuito. Avance por el programa en el chip real y observe todas las variables relevantes para asegurarse de que todo vaya según lo planeado.

Rocketmagnet
fuente
Sí, me refería al depurador, ese es el simulador de MPLABX. Las tareas funcionan por su cuenta, en un entorno no RTOS. No tengo un ICD. Solo tengo mikroElektronika easyPIC5 con ICD, sin embargo, solo funciona con el compilador mikroC. Ahora, cambiar compiladores no me permitirá encontrar el problema, ¿o sí?
Abdullah Kahraman
1

Solo miré tu código brevemente, pero no tiene sentido. En varios lugares está escribiendo a PCL y luego espera que ejecute otras instrucciones después de eso.

Como también dije antes, C es inapropiado para este tipo de acceso de bajo nivel de registros de hardware fundamentales. Realmente necesitas usar el ensamblaje para esto. Intentar descubrir por qué el código C no funciona es una pérdida de tiempo inútil.

Olin Lathrop
fuente
No pude combinar ensamblaje y C. Tuve que hacer mucho trabajo. Tanto el desmontaje como el código C me parecen lógicos. ¿Dónde se refiere que espero ejecutar instrucciones que siguen a una escritura en PCL? He visto el depurador tanto para el ensamblado como para C, y funciona como lo deseo.
abdullah kahraman
Perdón por el -1. Debería haber presionado accidentalmente y lo he notado ahora.
Abdullah Kahraman
@abdullah: En la máquina en la que estoy ahora, no puedo ver el código fuente. Está permanentemente colapsado en el navegador. Recuerdo que asignaste cosas a PCLATH, luego a PCL, luego creo que en un caso intenté hacer un RETORNO. Tan pronto como escriba a PCL, la ejecución saltará a la dirección que ingresó en PCLATH: PCL, por lo que las siguientes instrucciones son irrelevantes. Realmente no es bueno hacer esto en C porque está jugando con los recursos administrados por el compilador y posiblemente invalidando los supuestos del compilador. Use ensamblaje real ya. Me estoy cansando de tener que repetir esto.
Olin Lathrop
1
Mirando el código, no hay ningún lugar donde PCL se modifique justo antes de otra declaración. El único lugar donde parece estar modificado es al final de main (). Pero es un buen punto que debes estar muy seguro de que no estás luchando contra el compilador por sus recursos. Ambos perderán.
Rocketmagnet
3
C es perfectamente aceptable para este tipo de trabajo, y de hecho es preferible escribir en un lenguaje de nivel medio como C en lugar del lenguaje ensamblador porque es más fácil de leer y mantener. Un compilador decente generará código no muy alejado de lo que la persona promedio escribirá de todos modos. Por lo general, solo escribo ensamblador para el código de inicio muy básico, áreas específicas donde puedo optimizar mejor que el compilador o para interrupciones rápidas, o si las restricciones de tamaño del código lo dictan. No hay mucha necesidad de montaje puro en estos días.
akohlsmith
1

A continuación se muestra la forma de hacerlo con el ensamblaje en línea utilizando el compilador XC8, ¡y funciona ahora! Sin embargo, necesito agregar desarrollar más código para guardar y restaurar el STATUSregistro, lo que parece un poco más complicado de lo que es para un registro normal.

Editar: el código ha cambiado. Consulte las versiones anteriores de esta publicación para el código anterior.

/*
 * 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.
    }
}

Y aquí está el archivo de encabezado 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 */
abdullah kahraman
fuente
Parece que vas a ganar tu propia recompensa. ¡Felicidades! :-)
stevenvh
@stevenvh Ah, ¿eso pasa, no lo sabía? Gracias :)
abdullah kahraman
¡Felicitaciones por hacer que funcione!
davidcary
Gracias @davidcary! Realmente aprecio sus felicitaciones chicos.
abdullah kahraman
1
¿Realmente necesitas restaurar el ESTADO? Si es así, deberá utilizar la instrucción "swapf", por razones documentadas en otro lugar: " P. Anderson ", " Microchip Mid-range family manual: sección 8.5 Context Saving ", " PIC saving W and STATUS "
davidcary
0

A continuación se muestra cómo implementar esto usando el ensamblaje. Acceda al mismo código con formato (enlaces a Pastebin) . ¿Cómo puede ser mejorado? Este es mi primer programa en la asamblea PIC, cualquier comentario es apreciado.

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.
abdullah kahraman
fuente
¿Su primer programa en ensamblaje es un RTOS multitarea? Guau. A la mayoría de las personas les va muy bien si pueden hacer que parpadee un LED. :-).
davidcary
Bueno, en realidad este es mi primer programa de ensamblaje en la arquitectura PIC . Antes de eso, en la universidad, tomé 8086 clases, pero no eran prácticas y tuve que aprender solo porque el profesor era un sustituto y no sabía nada, pero hacía preguntas difíciles en los exámenes ...
Abdullah Kahraman