Por que meu kernel RTOS multitarefa PIC16 não funciona?


11

Estou tentando criar um RTOS semi-preventivo (cooperativo) para microcontroladores PIC x16. Na minha pergunta anterior , aprendi que acessar o ponteiro da pilha de hardware não é possível nesses núcleos. Eu olhei para esta página no PIClist, e é isso que estou tentando implementar usando C.

Meu compilador é o Microchip XC8 e atualmente estou trabalhando em um PIC16F616 com oscilador RC interno de 4MHz selecionado nos bits de configuração.

Aprendi que posso acessar os registros PCLATH e PCL com C, olhando o arquivo de cabeçalho do meu compilador. Então, tentei implementar um alternador de tarefas simples.

Funciona como desejado no depurador se eu pausar o depurador após reiniciar, redefinir e definir o PC no cursor quando o cursor não estiver na primeira linha ( TRISA=0;), mas em outra linha (por exemplo ANSEL=0;). No primeiro início do depurador, recebo essas mensagens no Debugger Console:

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

Edit: Eu não sei o que fez funcionar, mas o depurador agora funciona perfeitamente. Portanto, omita a saída e o parágrafo acima.

Edit: Alterar a definição principal como esta faz o código abaixo funcionar. Isso inicia a função principal no endereço do programa 0x0099. Não sei o que causa isso. Esta não é uma solução real. Agora, estou supondo que haja um erro específico do compilador.

void main(void) @ 0x0099
{

Aqui está o meu 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
    }
}

E aqui está o arquivo de listagem de desmontagem que meu compilador criou. Começa às line 74.

Programei o chip atual e nenhuma alteração no PORTA; isso não funciona.

Por que meu programa não funciona?

Respostas:


10

O que você está tentando fazer é complicado, mas muito educativo (se você estiver preparado para gastar muito esforço).

Primeiro, você deve perceber que esse tipo de alternância de tarefas somente para PC (ao contrário de PC + SP) (que é a única coisa que você pode fazer em um núcleo PIC simples de 12 ou 14 bits) funcionará apenas quando todo o rendimento ( ) em uma tarefa estão na mesma função: elas não podem estar em uma função chamada e o compilador não deve ter mexido com a estrutura da função (como a otimização pode fazer).

Próximo:

currentTask->pch = PCLATH;\
currentTask->pcl = PCL + 8;\
asm("goto _taskswitcher");
  • Você parece supor que PCLATH é os bits superiores do contador de programas, como PCL é os bits inferiores. Este não é o caso. Quando você escreve para PCL, os bits PCLATH são gravados no PC, mas os bits superiores do PC nunca são (implicitamente) gravados no PCLATH. Releia a seção relevante da folha de dados.
  • Mesmo se PCLATH fosse o bit superior do PC, isso causaria problemas quando a instrução após o goto não estiver na mesma 'página' de 256 instruções da primeira instrução.
  • o goto simples não funcionará quando _taskswitcher não estiver na página PCLATH atual, você precisará de um LGOTO ou equivalente.

Uma solução para o seu problema PCLATH é declarar um rótulo após o goto e gravar os bits inferior e superior desse rótulo nos locais pch e pcl. Mas não tenho certeza se você pode declarar um rótulo 'local' no assembly embutido. Você com certeza pode usar o MPASM simples (Olin sorrirá).

Por fim, para esse tipo de alternância de contexto, você deve salvar e restaurar TODO o contexto do qual o compilador possa depender, o que pode incluir

  • registro (s) indireto (s)
  • sinalizadores de status
  • locais de memória temporária
  • variáveis ​​locais que podem se sobrepor na memória porque o compilador não percebe que suas tarefas devem ser independentes
  • outras coisas que não consigo imaginar agora, mas o autor do compilador pode usar na próxima versão do compilador (elas tendem a ser muito imaginativas)

A arquitetura PIC é mais problemática nesse aspecto, porque muitos recursos estão localizados em todo o mapa de memória, onde arquiteturas mais tradicionais os têm em registros ou na pilha. Como conseqüência, os compiladores PIC geralmente não geram código reentrante, que é o que você definitivamente precisa fazer do que deseja (novamente, Olin provavelmente irá sorrir e montar).

Se você gosta disso ao escrever um alternador de tarefas, sugiro que mude para uma CPU que tenha uma organização mais tradicional, como um ARM ou Cortex. Se você estiver preso com os pés em uma placa de concreto de PICs, estude os comutadores PIC existentes (por exemplo, salvo / abóbora?).


Obrigado pela ótima informação! Estou determinado a criar um alternador de tarefas cooperativo. O XC8 e o PIC não estão do meu lado, estou ciente disso :) Sim, como você pode ver, é possível criar etiquetas como eu fiz em uma das minhas respostas a esta pergunta.
Abdullah kahraman

Além disso, para minha sorte, não há paginação da memória do programa para o PIC16F616 em que estou trabalhando, isso é uma grande vantagem neste momento, certo?
Abdullah kahraman

Você poderia explicar mais como as variáveis ​​locais se sobrepõem na memória e também "localizações de memória temporária"?
Abdullah kahraman

Se você se restringir a chips com código 2K ou menos, pode esquecer o lgoto, mas não as 'páginas' de 256 instruções. Arranhão: um compilador pode assumir que tudo o que faz na memória permanece no lugar, a menos que seja 'volátil'. Portanto, pode colocar cálculos parciais em algum local que possa ser compartilhado por diferentes funções . Ovelap: se main () chama f () eg () (e não há outras chamadas), as variáveis ​​locais de f () eg () podem ser mapeadas para os mesmos locais de memória.
Wouter van Ooijen

Bem, parece que é quase impossível alcançar essas variáveis ​​e armazenar, devido ao seu lugar aleatório na memória, certo?
Abdullah kahraman

7

Pesquisei a lista de montagem que você forneceu e nada salta como obviamente quebrado.

Se eu fosse você, meus próximos passos seriam:

(1) Eu escolheria outro método para piscar os LEDs. O notório "problema de leitura-modificação-gravação" pode (ou não) ser acionado pelo "XORWF PORTA, F" na lista de montagem.

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

(Se você realmente deseja obter explicações detalhadas sobre por que o "XORWF PORTA, F" geralmente causa problemas, consulte " O que faz com que LIGAR um único pino de saída no Microchip PIC16F690 desligue espontaneamente outro pino na mesma porta? "; " O que acontece quando os dados são gravados em LATCH? ";" O problema de leitura, modificação e gravação ";" Ler antes de gravar ")

(2) Eu passo um passo pelo código, certificando-me de que as variáveis ​​estejam sendo definidas com os valores esperados e na sequência esperada. Não tenho certeza se existe um depurador de hardware de etapa única para o PIC16F616, mas existem muitos excelentes simuladores de microcontroladores PIC , como o PICsim, que podem simular os chips da série PIC16.

O código de etapa única (em um simulador ou com um depurador de etapa única) é uma boa maneira de entender os detalhes do que realmente está acontecendo, confirmar se as coisas estão acontecendo do jeito que você pretendia e permitir que você veja as coisas que estão acontecendo. praticamente impossível de ver ao executar o programa em velocidade máxima.

(3) Se ainda estou perplexo, tentaria traduzir o código para usar matrizes em vez de ponteiros. Algumas pessoas acham o uso de ponteiros um pouco complicado e difícil de depurar. Costumo descobrir que, no processo de traduzir código de ponteiro complicado em código orientado a matriz, descubro qual é o erro. Mesmo se eu voltar ao código original do ponteiro e jogar fora a versão do array, o exercício é útil porque me ajudou a encontrar e corrigir o bug. (Às vezes, o compilador pode gerar código mais curto e mais rápido a partir do código orientado a array, então há momentos em que ligo o código do ponteiro original e mantenho a versão do array).

Talvez 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");

Estou implementando matrizes agora. Obrigado pela recomendação.
abdullah kahraman

3

Eu basicamente concordaria com davidcary. Parece que poderia funcionar.

Não sei o que o fez funcionar, mas o depurador agora funciona perfeitamente.

Acho que com isso você quer dizer que ele funciona perfeitamente no simulador .

1) Verifique se suas tarefas funcionam por conta própria, em um ambiente não RTOS no chip real.

2) Faça a depuração no circuito. Percorra o programa no chip real e observe todas as variáveis ​​relevantes para garantir que tudo esteja indo conforme o planejado.


Sim, eu quis dizer o depurador, que é o simulador do MPLABX. As tarefas funcionam por conta própria, em um ambiente não RTOS. Eu não tenho um CDI. Eu só tenho o mikroElektronika easyPIC5 com ICD, no entanto, ele só funciona com o compilador mikroC. Agora, alterar os compiladores não vai me deixar encontrar o problema, ou vai?
abdullah kahraman

1

Eu olhei apenas seu código brevemente, mas não faz sentido. Em vários lugares, você está escrevendo para o PCL e esperando que ele execute outras instruções a seguir.

Como eu também disse antes, C é inadequado para esse tipo de acesso de baixo nível de registros de hardware fundamentais. Você realmente precisa usar a montagem para isso. Tentar descobrir por que o código C não está funcionando é apenas uma perda de tempo inútil.


Não consegui combinar montagem e C. Tive que fazer muito trabalho. Tanto a desmontagem quanto o código C me parecem lógicos. Onde você está se referindo que espero executar instruções que seguem uma gravação para PCL? Eu assisti o depurador para montagem e C, e funciona como desejado.
abdullah kahraman

Desculpe pelo -1. Eu deveria ter pressionado acidentalmente e notei isso agora.
abdullah kahraman

@abdullah: Na máquina em que estou agora, não consigo ver o código fonte. Está permanentemente recolhido no navegador. Lembro que você atribuiu coisas ao PCLATH, depois ao PCL, então acho que em um caso tentei fazer um RETORNO. Assim que você escreve para PCL, a execução vai pular para o endereço que você inseriu em PCLATH: PCL, portanto, as instruções a seguir são irrelevantes. Realmente não é bom fazer isso em C porque você está mexendo com recursos gerenciados pelo compilador e, portanto, possivelmente invalidando suposições do compilador. Use montagem real já. Estou cansado de ter que repetir isso.
precisa

1
Olhando para o código, não há nenhum lugar em que o PCL seja modificado antes de outra instrução. O único lugar em que parece ser modificado é no final de main (). Mas é um bom argumento que você deve ter certeza de que não está lutando com o compilador por seus recursos. Vocês dois vão perder.
precisa

3
C é perfeitamente aceitável para esse tipo de trabalho e, de fato, é preferível escrever em uma linguagem de nível médio, como C, em oposição à linguagem assembly, porque é mais fácil de ler e manter. Um compilador decente irá gerar código não muito distante do que a pessoa comum escreverá de qualquer maneira. Normalmente, escrevo assembler apenas para o código de inicialização muito básico, áreas específicas nas quais posso otimizar melhor que o compilador ou para interrupções rápidas ou se as restrições de tamanho de código o exigirem. Atualmente, não há muita necessidade de montagem pura.
akohlsmith

1

Abaixo está a maneira de fazê-lo com montagem em linha usando o compilador XC8, e ele funciona agora! No entanto, preciso adicionar desenvolver mais código para salvar e restaurar o STATUSregistro, o que parece um pouco mais complicado do que para um registro normal.

Editar: o código foi alterado. Consulte as versões mais antigas desta postagem para obter o 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.
    }
}

E aqui está o arquivo de cabeçalho 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 você vai ganhar sua própria recompensa. Parabéns! :-)
stevenvh 26/06/12

@stevenvh Ah, isso acontece, eu não sabia? Obrigado :)
abdullah kahraman

Parabéns por fazê-lo funcionar!
Davidcary

Obrigado @davidcary! Eu realmente aprecio seus parabéns, pessoal.
abdullah kahraman

1
Você realmente precisa restaurar o STATUS? Nesse caso, você precisará usar a instrução "swapf", por razões documentadas em outras partes: " P. Anderson ", " Manual da família Microchip Mid-range: seção 8.5 Economia de Contexto ", " Economia de PIC W e STATUS "
davidcary

0

Abaixo está como implementar isso usando assembly. Acesse o mesmo código com formatação (links para Pastebin) . Como pode ser melhorado? Este é o meu primeiro programa na montagem PIC, qualquer comentário é 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.

Seu primeiro programa em montagem é um RTOS multitarefa? Uau. A maioria das pessoas está se saindo muito bem se conseguir que um LED pisque. :-).
Davidcary

Bem, na verdade, este é o meu primeiro programa de montagem na arquitetura PIC . Antes disso, na universidade, participei de 8086 aulas, mas elas não eram práticas e eu tive que aprender sozinha porque o professor era um substituto e não sabia de nada, mas ainda fazia perguntas difíceis nos exames.
abdullah kahraman
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.