В чем причина того, что мое многозадачное ядро ​​RTOS PIC16 не работает?

Я пытаюсь создать полуупреждающую (кооперативную) RTOS для микроконтроллеров PIC x16. В моем предыдущем вопросе я узнал, что доступ к указателю аппаратного стека невозможен в этих ядрах. Я просмотрел эту страницу в PIClist, и это то, что я пытаюсь реализовать с помощью C.

Мой компилятор - Microchip XC8, и в настоящее время я работаю над PIC16F616 с внутренним RC-генератором 4 МГц, выбранным в битах конфигурации.

Я узнал, что я могу получить доступ к регистрам PCLATH и PCL с помощью C, просматривая заголовочный файл моего компилятора. Итак, я попытался реализовать простой переключатель задач.

Он работает так, как нужно в отладчике, если я приостанавливаю отладчик после перезапуска, сброса и установки ПК на курсор, когда курсор находится не в первой строке ( TRISA=0;), а в другой строке (например ANSEL=0;). При первом запуске отладчика я получаю следующие сообщения Debugger Console:

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

Редактировать: я не знаю, что заставило его работать, но теперь отладчик работает отлично. Итак, опустите приведенный выше вывод и абзац.

Изменить: такое изменение основного определения приводит к тому, что приведенный ниже код работает. Это запускает основную функцию по адресу программы 0x0099. Я не знаю, чем это вызвано. Это не настоящее решение. Теперь я предполагаю, что есть конкретная ошибка компилятора.

void main(void) @ 0x0099
{

Вот мой код 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
    }
}

А вот файл листинга дизассемблирования, который создал мой компилятор. Начинается в line 74.

Я запрограммировал настоящий чип, и в PORTA вообще никаких изменений; это не работает.

По какой причине моя программа не работает?

Ответы (6)

То, что вы пытаетесь сделать, сложно, но очень поучительно (если вы готовы потратить много усилий).

Во-первых, вы должны понимать, что такое переключение задач только для ПК (в отличие от ПК+SP) (это единственное, что вы можете сделать на простом 12- или 14-битном PIC-ядре) будет работать только тогда, когда все yield( ) в задаче находятся в одной и той же функции: они не могут быть в вызываемой функции, и компилятор не должен вмешиваться в структуру функции (как может сделать оптимизация).

Следующий:

currentTask->pch = PCLATH;\
currentTask->pcl = PCL + 8;\
asm("goto _taskswitcher");
  • Вы, кажется, предполагаете, что PCLATH - это старшие биты счетчика программ, а PCL - младшие биты. Это не вариант. Когда вы записываете в PCL, биты PCLATH записываются в PC, но старшие биты PC никогда (неявно) не записываются в PCLATH. Перечитайте соответствующий раздел таблицы данных.
  • Даже если бы PCLATH был старшим битом PC, у вас были бы проблемы, когда инструкция после goto находится не на той же «странице» из 256 инструкций, что и первая инструкция.
  • простой переход не будет работать, если _taskswitcher не находится на текущей странице PCLATH, вам понадобится LGOTO или аналогичный.

Решение вашей проблемы PCLATH состоит в том, чтобы объявить метку после goto и записать младшие и старшие биты этой метки в ваши местоположения pch и pcl. Но я не уверен, что вы можете объявить «локальную» метку во встроенной сборке. Вы, конечно, можете в простом MPASM (Олин улыбнется).

Наконец, для такого переключения контекста вы должны сохранить и восстановить ВЕСЬ контекст, от которого может зависеть компилятор, который может включать

  • косвенный регистр(ы)
  • флаги состояния
  • временные ячейки памяти
  • локальные переменные, которые могут перекрываться в памяти, потому что компилятор не понимает, что ваши задачи должны быть независимыми
  • другие вещи, которые я не могу себе представить прямо сейчас, но автор компилятора может использовать в следующей версии компилятора (они, как правило, очень изобретательны)

Архитектура PIC более проблематична в этом отношении, потому что многие ресурсы размещаются по всей карте памяти, тогда как в более традиционных архитектурах они находятся в регистрах или в стеке. Как следствие, компиляторы PIC часто не генерируют реентерабельный код, который вам определенно нужен, чтобы делать то, что вы хотите (опять же, Олин, вероятно, улыбнется и соберет вместе).

Если вы занимаетесь этим для удовольствия от написания переключателя задач, я предлагаю вам переключиться на процессор с более традиционной организацией, такой как ARM или Cortex. Если вы застряли ногами в бетонной плите PIC, изучите существующие переключатели PIC (например, залп/тыква?).

Спасибо за отличную информацию! Я полон решимости создать кооперативный переключатель задач. XC8 и PIC не на моей стороне в этом, я знаю об этом :) Да, как видите, можно создавать метки, как я сделал в одном из своих ответов на этот вопрос.
Кроме того, к моему счастью, для PIC16F616, над которым я работаю, нет подкачки памяти программ, что на данный момент является большим преимуществом, верно?
Не могли бы вы подробнее объяснить, как локальные переменные будут перекрываться в памяти, а также «стирать ячейки памяти»?
Если вы ограничитесь чипами с кодом 2K или меньше, вы действительно можете забыть о lgoto, но не о «страницах» с 256 инструкциями. Scratch: компилятор может предположить, что все, что он делает в памяти, остается на месте, если только оно не является «изменчивым». Таким образом, частичные вычисления могут быть помещены в какое-то место , которое может использоваться разными функциями . Перекрытие: если main() вызывает и f(), и g() (и других вызовов нет), локальные переменные f() и g() могут быть сопоставлены с одними и теми же ячейками памяти.
Что ж, кажется, что почти невозможно получить доступ к этим переменным и сохранить их из-за их случайного расположения в памяти, верно?
Это действительно потенциальная проблема. Вы можете проверить другие переключатели задач для 14-битного ядра, чтобы увидеть, как они справляются с этим.

Я просмотрел предоставленный вами лист сборки, и ничего не выскакивает из-за того, что оно явно сломано.

Если бы я был вами, мои следующие шаги были бы такими:

(1) Я бы выбрал другой способ мигания светодиодов. Пресловутая проблема «чтение-изменение-запись» может (а может и не быть) вызвана «XORWF PORTA, F» в листинге сборки.

Возможно, что-то вроде:

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

(Если вы действительно хотите увидеть подробные объяснения того, почему «XORWF PORTA, F» часто вызывает проблемы, см. « Что приводит к тому, что включение одного выходного контакта на Microchip PIC16F690 самопроизвольно выключает другой контакт на том же порту? »; « Что происходит ? когда данные записываются в LATCH? »; « Проблема чтения-модификации-записи »; « Чтение перед записью »)

(2) Я бы пошагово выполнил код, убедившись, что переменные устанавливаются в ожидаемые значения и в ожидаемой последовательности. Я не уверен, существует ли одношаговый аппаратный отладчик для PIC16F616, но есть много отличных симуляторов микроконтроллеров PIC , таких как PICsim , которые могут имитировать чипы серии PIC16.

Одношаговый код (в симуляторе или с одношаговым аппаратным отладчиком) — это хороший способ понять детали того, что происходит на самом деле, убедиться, что все происходит так, как вы задумали, и позволяет увидеть то, что происходит на самом деле. практически невозможно увидеть при запуске программы на полной скорости.

(3) Если я все еще в тупике, я бы попытался перевести код для использования массивов, а не указателей. Некоторые люди находят использование указателей немного сложным и трудным для отладки. Я часто обнаруживаю, что в процессе перевода хитроумного кода с указателями в код, ориентированный на массивы, я выясняю, в чем заключается ошибка. Даже если я в конечном итоге вернусь к исходному коду указателя и выброслю версию массива, это упражнение полезно, поскольку оно помогло мне найти и исправить ошибку. (Иногда компилятор может генерировать более короткий и быстрый код из кода, ориентированного на массив, поэтому иногда я выбрасываю исходный код указателя и сохраняю версию массива).

Возможно, что-то вроде

// 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");
Сейчас я реализую массивы. Спасибо за рекомендацию.

В основном я согласен с Дэвидкари. Похоже, это может сработать.

Я не знаю, что заставило его работать, но теперь отладчик работает отлично.

Я предполагаю, что вы имеете в виду, что он отлично работает в симуляторе .

1) Проверьте, что ваши задачи работают сами по себе, в не-RTOS среде в реальном чипе.

2) Выполните внутрисхемную отладку. Пройдитесь по программе на реальном чипе и просмотрите все соответствующие переменные, чтобы убедиться, что все идет по плану.

Да, я имел в виду отладчик, то есть симулятор MPLABX. Задачи работают сами по себе, в среде, отличной от RTOS. У меня нет ИКД. У меня только mikroElektronika easyPIC5 с ICD, правда, работает только с компилятором mikroC. Теперь смена компилятора не позволит мне найти проблему, или не позволит?

Я лишь бегло взглянул на ваш код, но это не имеет смысла. В некоторых местах вы пишете в PCL, а затем ожидаете, что он выполнит другие инструкции, следующие за этим.

Как я уже говорил, C не подходит для такого низкоуровневого доступа к фундаментальным аппаратным регистрам. Вам действительно нужно использовать сборку для этого. Попытка выяснить, почему код C не работает, — просто бессмысленная трата времени.

Я не мог совмещать ассемблер и Си. Приходилось много работать. И дизассемблирование, и код на C кажутся мне логичными. Где вы имеете в виду, что я ожидаю выполнения инструкций, следующих за записью в PCL? Я наблюдал за отладчиком как для сборки, так и для C, и он работает так, как хотелось бы.
Извините за -1. Я должен был нажать случайно, и я заметил это сейчас.
@abdullah: На машине, на которой я сейчас нахожусь, я не вижу исходный код. Он постоянно свернут в браузере. Я помню, что вы назначили материал PCLATH, затем PCL, а затем, кажется, в одном случае попытались выполнить RETURN. Как только вы начнете писать в PCL, выполнение перейдет к адресу, который вы вставили в PCLATH:PCL, поэтому любые следующие инструкции не имеют значения. Это действительно нехорошо делать это в C, потому что вы возитесь с ресурсами, управляемыми компилятором , и тем самым, возможно, делаете недействительными предположения компилятора. Используйте уже настоящую сборку. Мне надоело это повторять.
Вы имеете в виду "OS_initializeTask();" часть. Я не назначаю вещи PCLATH и PCL, я назначаю PCLATH и PCL некоторым регистрам, чтобы сохранить их значение в блоке управления текущей задачей. После года на C, если я вернусь к ассемблеру, я потеряю так много времени, практикуясь в ассемблере. Если вы имеете в виду «включить переключение задач в сборку», тогда это будет очень запутанно для стороны C. Если у вас есть предложение о том, как это сделать, я был бы очень признателен.
Глядя на код, нигде не видно, чтобы PCL модифицировался непосредственно перед другим оператором. Единственное место, где он, по-видимому, изменен, находится в самом конце main(). Но хорошо, что вы должны быть уверены, что не боретесь с компилятором за его ресурсы. Вы оба проиграете.
@Rocket: Как я уже сказал, я не вижу код прямо сейчас, поэтому я исходил из памяти. Однако, даже если вам удастся установить PCLATH и PCL по желанию из C, нет гарантии, что другие вещи не будут испорчены, поскольку C не знает, что вы выходите из подпрограммы. В стеке данных могут быть разные вещи. Опять же, ВАМ ДЕЙСТВИТЕЛЬНО НУЖНО СДЕЛАТЬ ЭТО ИЗ СБОРКИ. Я не знаю, почему люди идут на такие большие усилия, пытаясь использовать метод, который может не сработать. Абдулла предположительно является EE, поэтому ему нужно освоиться со сборкой, если это не так, и скорее раньше, чем позже.
@OlinLathrop Вы правы. Кстати, я люблю сборку. Это дает вам так много контроля. Теперь я попытаюсь создать многомерный массив вместо структуры и управлять этим массивом, используя только ассемблер. Таким образом, надеюсь, что вся выдача, инициализация и переключение задач будут выполняться на ассемблере.
C вполне приемлем для такой работы, и на самом деле предпочтительнее писать на языке среднего уровня, таком как C, а не на ассемблере, потому что его легче читать и поддерживать. Приличный компилятор будет генерировать код, не слишком отличающийся от того, что напишет обычный человек. Обычно я пишу на ассемблере только для самого простого кода запуска, определенных областей, где я могу оптимизировать лучше, чем компилятор, или для быстрых прерываний, или если это диктуют ограничения размера кода. В наши дни нет особой необходимости в чистой сборке.
ба, я только что понял, что вы, по сути, говорите то же самое. Должно быть, у меня выходной. Мне было бы очень трудно написать то, что по сути является программируемой таблицей переходов, подобной этой на C.

Ниже приведен способ сделать это с помощью встроенной сборки с использованием компилятора XC8, и теперь он работает ! Однако мне нужно добавить больше кода для сохранения и восстановления STATUSреестра, что кажется немного сложнее, чем для обычного реестра.

Изменить: код изменен. Пожалуйста, обратитесь к более старым версиям этого сообщения для предыдущего кода.

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

А вот и заголовочный файл 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 */
Похоже, ты собираешься выиграть свою собственную награду. Поздравляем! :-)
@stevenvh А, такое бывает, я не знал? Спасибо :)
Поздравляем!
Спасибо @davidcary! Я очень ценю ваши поздравления, ребята.
Вам действительно нужно восстановить СТАТУС? Если это так, вам нужно будет использовать инструкцию «swapf» по причинам, задокументированным в другом месте: « P. Anderson », « Руководство по семейству Microchip Mid-range: раздел 8.5 Сохранение контекста », « Сохранение PIC W и STATUS » .
@davidcary кажется, что мне не нужно восстанавливать регистр STATUS. XC8, кажется, не зависит от этого. Спасибо за ссылки. В первой проблеме с RTOS я буду винить биты выбора банка в регистре STATUS и реализовывать его сохранение и восстановление.

Ниже показано, как реализовать это с помощью сборки. Доступ к тому же коду с форматированием (ссылки на Pastebin) . как это может быть улучшено? Это моя первая программа на ассемблере PIC, буду рад любым комментариям.

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.
Ваша первая программа на ассемблере — это многозадачная RTOS? Ух ты. У большинства людей действительно хорошо получается, если они могут заставить мигать светодиод. :-).
Ну собственно это моя первая сборка программы на архитектуре PIC . До этого в университете я прошел 8086 занятий, но они не были практическими, и мне приходилось учиться самому, потому что лектор был заместителем и ничего не знал, но задавал трудные вопросы на экзаменах.