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

Estoy tratando de crear un RTOS semi-preventivo (cooperativo) para microcontroladores PIC x16. En mi pregunta anterior , aprendí que acceder al puntero de la pila de hardware no es posible en estos núcleos. Miré 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 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, reiniciar 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é hizo que funcionara, 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 código a continuación 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 lista de desmontaje que creó mi compilador. Comienza en 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?

Respuestas (6)

Lo que está tratando de 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 simple de 12 o 14 bits) solo funcionará cuando todo el rendimiento ( ) las declaraciones en una tarea tienen la misma función: no pueden estar en una función llamada, y el compilador no debe haber interferido con la estructura de la función (como podría ocurrir con 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 son 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 se escriben (implícitamente) en PCLATH. Vuelva a leer la sección correspondiente de la hoja de datos.
  • Incluso si PCLATH fueran los bits superiores de la PC, esto podría generarle problemas cuando la instrucción posterior al goto no está activada en la misma 'página' de 256 instrucciones que la primera instrucción.
  • el simple goto no funcionará cuando _taskswitcher no esté en la página actual de PCLATH, necesitará un LGOTO o equivalente.

Una solución a su problema de 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 podría depender el compilador, que podría incluir

  • registro(s) de indirección
  • banderas de estado
  • cero ubicaciones de memoria
  • 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 que el autor del compilador podría usar en la próxima versión del compilador (suelen ser muy imaginativas)

La arquitectura PIC es más problemática a este respecto porque muchos recursos se ubican en todo el mapa de memoria, mientras que las arquitecturas más tradicionales los tienen en registros o en la pila. Como consecuencia, los compiladores de 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 ensamblará).

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

¡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.
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 punto, ¿no?
¿Podría explicar más cómo se superpondrán las variables locales en la memoria y también "ubicaciones de memoria temporal"?
Si se limita a chips con código de 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 algún lugar que puedan ser compartidos por diferentes funciones . Superposición: si main() llama tanto a f() como a g() (y no hay otras llamadas), las variables locales de f() y g() se pueden asignar a las mismas ubicaciones de memoria.
Bueno, parece que es casi imposible llegar a esas variables y almacenarlas, debido a su lugar aleatorio en la memoria, ¿verdad?
Eso es de hecho un problema potencial. Puede consultar otros conmutadores de tareas para el núcleo de 14 bits para ver cómo manejan esto.

Examiné la lista de ensamblaje que proporcionó, y nada salta como obviamente roto.

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

(1) Elegiría algún otro método para hacer 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 ensamblaje.

Tal vez 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 sobre por qué "XORWF PORTA, F" a menudo causa problemas, consulte " ¿Qué causa que al encender un solo pin de salida en Microchip PIC16F690 se apague espontáneamente otro pin en el mismo puerto? "; " Qué sucede cuándo se escriben los datos en LATCH? "; " El problema de lectura, modificación y 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 simuladores de microcontroladores PIC excelentes , 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 prevista y le permite ver las cosas que son 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 el uso de punteros es un poco complicado y difícil de depurar. A menudo me doy cuenta de que, en el proceso de traducir el código de puntero engañoso en código orientado a matrices, descubro cuál es el error. Incluso si termino volviendo al código de puntero original y descarto la versión de matriz, el ejercicio es útil porque me ayudó a encontrar y corregir el error. (A veces, el compilador puede generar un código más corto y más rápido a partir de un código orientado a matrices, por lo que hay ocasiones en las que desecho el código de puntero original y me quedo con la versión de matriz).

Tal vez 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");
Estoy implementando arreglos ahora. Gracias por la recomendación.

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

No sé qué hizo que funcionara, pero el depurador ahora funciona perfectamente.

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

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

2) Hacer depuración en circuito. Recorra el programa en el chip real y observe todas las variables relevantes para asegurarse de que todo va según lo planeado.

Sí, me refiero al depurador, que es el simulador de MPLABX. Las tareas funcionan por sí solas, en un entorno que no es RTOS. No tengo DAI. Solo tengo mikroElektronika easyPIC5 con ICD, sin embargo, solo funciona con el compilador mikroC. Ahora, cambiar los compiladores no me permitirá encontrar el problema, ¿o sí?

Solo miré tu código brevemente, pero no tiene sentido. En varios lugares, está escribiendo en 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 a los registros fundamentales del hardware. Realmente necesitas usar ensamblador para esto. Tratar de descubrir por qué el código C no funciona es solo una pérdida de tiempo sin sentido.

No pude combinar ensamblaje y C. Tuve que hacer mucho trabajo. Tanto el desmontaje como el código C me parecen lógicos. ¿A dónde se refiere que espero ejecutar instrucciones que siguen a una escritura en PCL? He visto el depurador tanto para ensamblador como para C, y funciona como se desea.
Perdón por el -1. Debería haber presionado accidentalmente y lo he notado ahora.
@abdullah: En la máquina en la que estoy ahora, no puedo ver el código fuente. Está colapsado permanentemente en el navegador. Recuerdo que asignó cosas a PCLATH, luego a PCL, luego creo que en un caso intentó hacer un RETORNO. Tan pronto como escriba en 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, por lo tanto, posiblemente invalide las suposiciones del compilador. Utilice el montaje real ya. Me estoy cansando de tener que repetir esto.
Te refieres a "OS_initializeTask();" parte. No estoy asignando cosas a PCLATH y PCL, estoy asignando PCLATH y PCL a algunos registros, para guardar su valor, en el bloque de control de la tarea actual. Después de un año en C, si vuelvo a ensamblar, perderé mucho tiempo practicando ensamblador. Si quiere decir "incluir el cambio de tareas en el ensamblaje", bueno, eso será muy complicado para el lado C. Si tiene alguna sugerencia sobre cómo hacerlo, le estaría muy agradecido.
Mirando el código, no hay ninguna parte en la que 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 debe estar muy seguro de que no está luchando contra el compilador por sus recursos. Ambos perderán.
@Rocket: Como dije, no puedo ver el código en este momento, así que lo hice de memoria. Sin embargo, incluso si logra configurar PCLATH y PCL como desea desde C, no hay garantía de que otras cosas no se arruinen, ya que C no sabe que está saliendo de la rutina. Podría haber varias cosas todavía en la pila de datos. De nuevo, REALMENTE NECESITAS HACER ESTO DESDE EL MONTAJE. No sé por qué la gente hace todo lo posible para tratar de usar un método que puede no funcionar. Se supone que Abdullah es un EE, por lo que debe sentirse cómodo con el montaje si no lo es, y más temprano que tarde.
@OlinLathrop Tienes razón. Me encanta el montaje por cierto. Te da mucho control. Ahora, lo que voy a tratar de hacer es crear una matriz multidimensional en lugar de una estructura y administrar esa matriz usando solo ensamblador. Por lo tanto, todo el rendimiento, la inicialización y el cambio de tareas se realizarán en ensamblaje, con suerte.
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 de lenguaje ensamblador porque es más fácil de leer y mantener. Un compilador decente generará un 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 ensamblaje puro en estos días.
bah, acabo de darme cuenta de que esencialmente estás diciendo lo mismo. Debo estar teniendo un mal día. Tendría muchas dificultades para escribir lo que es esencialmente una tabla de salto programable como esa en C.

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 ver 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 */
Parece que vas a ganar tu propia recompensa. ¡Felicidades! :-)
@stevenvh Ah, ¿sucede eso, no lo sabía? Gracias :)
¡Felicitaciones por hacerlo funcionar!
Gracias @davidcary! Realmente aprecio sus felicitaciones chicos.
¿Realmente necesita restaurar STATUS? Si es así, deberá utilizar la instrucción "swapf", por razones documentadas en otro lugar: " P. Anderson ", " Manual de la familia de gama media de Microchip: sección 8.5 Guardado de contexto ", " PIC guardando W y ESTADO "
@davidcary parece que no necesito restaurar el registro de ESTADO. XC8 no parece depender de eso. Gracias por los enlaces. En el primer problema con el RTOS, culparé a los bits de selección de banco en el registro de ESTADO e implementaré el guardado y la restauración.

A continuación se muestra cómo implementar esto usando ensamblador. Acceda al mismo código con formato (enlaces a Pastebin) . ¿Cómo puede ser mejorado? Este es mi primer programa en ensamblador de 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.
¿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 un LED parpadee. :-).
Bueno, en realidad este es mi primer programa ensamblador en la arquitectura PIC . Antes de eso, en la universidad, he tomado 8086 clases pero no eran prácticas y tuve que aprender solo porque el profesor era un suplente y no sabía nada, pero hacía preguntas difíciles en los exámenes.