ULWOS – Multitarefa no RL78: Criando tarefas

ULWOS Multitarefa Multitarefa no RL78
Este post faz parte da série ULWOS - Multitarefa no RL78

Nos primeiros artigos desta série sobre o ULWOS, nos preocupamos em definir o que é multitarefa e como o comutador de tarefas do ULWOS foi implementado para se ter essa característica. Agora precisa-se oferecer um jeito de se criar tarefas no sistema por meio da API do ULWOS.

Apesar de isso parecer trivial, na verdade não é, pois criar uma nova tarefa implica em preparar a pilha da mesma, ou seja, armazenar o PSW e o endereço de entrada da tarefa na pilha da mesma, de forma que quando o agendador recuperar o contexto da tarefa a execução seja iniciada do ponto de entrada da tarefa. O ULWOS utiliza o valor 0x86 para inicializar o PSW de uma nova tarefa, este valor habilita as interrupções (IE=1) e seleciona o banco 0 (RP0=RP1=0) e prioridade baixa de interrupções (ISP0=ISP1=1).

ULWOS_TASKHANDLER ulwos_create_task(void * task_address){
    if (ulwos_num_tasks>=ULWOS_NUM_TASKS) return -1;
    ulwos_tempSP = (int) task_address;
    ulwos_current_task = ulwos_num_tasks;
    ulwos_num_tasks++;
    asm volatile (
        "sel RB3\n\t"
        "movw HL,SP\n\t"    // HL guarda o SP atual
        "clrw AX\n\t"        // AX = 0
        "movw DE,%0\n\t"    // DE = ULWOS_TASK_STACK_SIZE
        "clrb B\n\t"
        "mov C,%1\n\t"    // BC = ulwos_current_task
        "inc C\n\t"
        // multiplicação simples de 16 bits
        "LOOP:"
        "addw AX,DE\n\t"    // AX = AX + ULWOS_TASK_STACK_SIZE
        "decw BC\n\t"        // BC = BC-1
        "cmp0 B\n\t"        // compara B com 0
        "bnz $LOOP\n\t"    // desvia para loop se não for zero
        "cmp0 C\n\t"        // compara C com 0
        "bnz $LOOP\n\t"    // desvia para loop se não for zero
        // AX = ULWOS_TASK_STACK_SIZE * (ulwos_current_task+1)
        "movw BC,AX\n\t"
        "movw AX,%2\n\t"
        // AX aponta para ulwos_task_stack[ulwos_current_stack]
        "addw AX,BC\n\t"    
        "movw SP,AX\n\t"    // configura o SP para o topo da nova pilha da tarefa
        "movw DE,AX\n\t"    // DE aponta o topo da pilha da tarefa
        "movw AX,#0x8600\n\t"
        "push AX\n\t"        // salva o PSW e a parte alta do PC (=0)
        "movw AX,%3\n\t"    // AX contém o endereço da função (16 bits)
        "push AX\n\t"        // salva o endereço na pilha
        :
        :"i"(ULWOS_TASK_STACK_SIZE),"m"(ulwos_current_task),"i"(ulwos_task_stack),"m"(ulwos_tempSP)
    );
    asm volatile (
        "mov X,%1\n\t"    // X = ulwos_current_task
        "clrb A\n\t"        // A = 0 (AX = ulwos_current_task)
        "shlw AX,1\n\t"        // AX = ulwos_current_task*2
        "movw BC,%0\n\t"    // BC = ulwos_taskSP
        "addw AX,BC\n\t"    // AX = ulwos_taskSP+(ulwos_current_task*2) => ulwos_taskSP[ulwos_current_task]
        "movw DE,AX\n\t"    // DE = ulwos_taskSP[ulwos_current_task]
        "movw AX,SP\n\t"    // AX = SP da tarefa
        "movw [DE],AX\n\t"    // ulwos_task_SP[ulwos_current_task] = SP da tarefa
        "movw AX,HL\n\t"
        "movw SP,AX\n\t"    // restaura o antigo SP
        "sel RB0\n\t"
        :
        :"i"(ulwos_taskSP),"m"(ulwos_current_task)
    );
    return ulwos_current_task;
}

Ufa! Agora que já temos um agendador de tarefas funcional e uma função para criar uma nova tarefa, tudo o que é necessário é iniciar o sistema!

Isto significa que devemos configurar o timer de intervalo (IT), habilitar a sua interrupção, inicializar o SP de forma que ele aponte para o topo da pilha da primeira tarefa e em seguida executar uma instrução RETI (retorno de interrupção)!

Aí o leitor pode questionar: mas como assim? Como vamos retornar de uma interrupção se não estamos em uma ISR? A verdade meu caro leitor é que ao utilizar a RETI fazemos com que a CPU desempilhe o PSW e o PC da pilha da tarefa (aqueles que tivemos o trabalho de configurar quando criamos a tarefa), fazendo com que o fluxo do programa seja desviado para o ponto de entrada da tarefa e automaticamente habilitando as interrupções! Ou você não reparou que não há uma instrução EI no código? As interrupções são habilitadas dentro da primeira tarefa pois o PSW que é retirado da pilha (por RETI) possui o bit IE setado!

void inline ulwos_start(void){
    ulwos_current_task = 0;
    OSMC = bWUTMMCK0;    // seta o LOCO (15kHz) como fonte de clock do IT/RTC
    RTCEN = 1;            // habilita o RTC e o IT
    ITMC = bRINTE | 14;        // IT habilitado, intervalo = 1ms
    ITMK = 0;            // interrupção do IT habilitada
    asm volatile (
        "sel RB3\n\t"        // seleciona o banco 3
        "movw AX,%0\n\t"    // AX = ulwos_taskSP[0]
        "movw SP,AX\n\t"    // SP = AX = ulwos_taskSP[0]
        "reti\n\t"        // restaura PSW e desvia para a tarefa
        "nop\n\t"        // apenas para alinhamento do simulador
        :
        :"m"(ulwos_taskSP)
    );
}

Todas estas funções estão escritas no arquivo ulwos.c. O arquivo ulwos.h, por sua vez, contém as definições básicas de configuração do nosso agendador: o símbolo ULWOS_TASK_STACK_SIZE permite definir o tamanho da pilha de memória reservada para cada tarefa (o valor padrão é 128 bytes) e o símbolo ULWOS_NUM_TASKS determina o número máximo de tarefas admitidas no sistema. É importante que se configure estes parâmetros de forma compatível com a aplicação, em especial o número máximo de tarefas, que deve ser igual ao número de tarefas que serão efetivamente criadas na aplicação.

O tamanho da pilha das tarefas é substancialmente mais complexo de ser definido. Ele vai depender especialmente da complexidade do código da tarefa, da quantidade de chamadas de funções e do número de variáveis locais dentro de todas as funções chamadas pela tarefa. Futuramente poderemos adicionar ao agendador um pequeno sistema de detecção de estouro de pilha, de forma a auxiliar o desenvolvedor.

Bom, agora que já vimos todo o código desta etapa do ULWOS, vamos ver dois exemplos simples da utilização e teste do nosso pequeno agendador de tarefas e aspirante a sistema operacional!

O primeiro é um simples pisca-pisca com dois leds, cada led é comandado por uma tarefa independente. Este exemplo pode ser testado no simulador ou na placa YRPBRL78G13 (ou qualquer outro hardware, desde que se conecte leds aos pinos P76 e P77 ou se altere o programa).

Repare que as tarefas (task1 e task2) são funções declaradas com o atributo noreturn, isto faz com que o compilador não gere código de retorno para a função, reduzindo ligeiramente o o tamanho das mesmas!

O projeto completo para o E2Studio com GCC está disponível no GITHUB.

#include "iodefine.h"
#include "iodefine_ext.h"
#include "myrl78.h"
#include "interrupt_handlers.h"
#include "ulwos.h"

#define LED P7_bit.no7
#define LED2 P7_bit.no6

void task1(void)
{
    volatile int count;
    while (1)
    {
        LED = 0;
        for (count=0; count<1000;count++);
        LED = 1;
        for (count=0; count<1000;count++);
    }
}

void task2(void)
{
    volatile int count;
    while (1)
    {
        LED2 = 0;
        for (count=0; count<2000;count++);
        LED2 = 1;
        for (count=0; count<2000;count++);
    }
}

int main(void)
{
    PM7_bit.no7 = 0;
    PM7_bit.no6 = 0;
    LED = 0;
    ULWOS_TASKHANDLER tk1 = ulwos_create_task(&task1);
    ULWOS_TASKHANDLER tk2 = ulwos_create_task(&task2);
    ulwos_start();
    // o código a seguir nunca será executado
    while (1);
}

O segundo exemplo é mais elaborado e foi projetado para o starter kit do RG78/G13 (RSK). Ele cria duas tarefas independentes, uma pisca o led conectado ao pino P63 e a outra escreve uma contagem progressiva no display da placa. O projeto completo para o E2Studio com GCC está disponível no GITHUB.

#include "iodefine.h"
#include "iodefine_ext.h"
#include "myrl78.h"
#include "interrupt_handlers.h"
#include "ulwos.h"
#include "lcd_8x2.h"

#define LED     P6_bit.no3
#define LED_DIR PM6_bit.no3

void task1(void)
{
    volatile unsigned long count;
    LED_DIR = 0;
    while (1)
    {
        LED = 0;
        for (count=0; count<100000;count++);
        LED = 1;
        for (count=0; count<100000;count++);
    }
}

void task2(void){
    volatile unsigned char aux;
    volatile unsigned long temp;
    LCD_init(DISPLAY_8x5|_2LINES,DISPLAY_ON|CURSOR_OFF|CURSOR_FIXED);
    while (1)
    {
        LCD_write_char('\f');   // apaga o display
        LCD_pos_xy(0,0);
        LCD_write_string("Testing");
        for (aux=0;aux<100;aux++)
        {
            LCD_pos_xy(0,1);
            LCD_print_char(aux);
            for (temp=0;temp<500000;temp++);
        }
    }
}

void main(void)
{
    ULWOS_TASKHANDLER tk1 = ulwos_create_task(&task1);
    ULWOS_TASKHANDLER tk2 = ulwos_create_task(&task2);
    ulwos_start();
    // o código a seguir nunca será executado
    while (1);
}

Antes de encerrar este artigo, é importante fazer algumas observações relevantes:

  1. Num sistema multitarefas existe o problema do compartilhamento de recursos: imagine que a tarefa 1 esteja fazendo a leitura de uma variável de 32 bits chamada X, durante este processo a tarefa é interrompida e a tarefa 2 assume e altera o conteúdo de X. Qual seria o resultado disso? Provavelmente o valor lido para X na tarefa 1 será corrompido e provocará uma falha na execução. A solução para o compartilhamento de recursos nestes casos é a utilização de semáforos ou simplesmente desabilitar temporariamente as interrupções durante a execução deste tipo de operação. Obviamente que este tipo de expediente deve ser utilizado com bastante precaução;
  2. Não devem ser utilizadas interrupções de múltiplas prioridades ou, no mínimo, o seu uso deve ser bastante cauteloso, pois elas podem causar o estouro da pilha de memória da tarefa;
  3. Na forma atual do ULWOS, uma tarefa NUNCA deve encerrar, ou seja, ela não deve atingir o seu ponto final de execução e retornar. Lembre-se: as tarefas do ULWOS são funções mas elas não são chamadas pelo código C, logo, se uma destas tarefas tentar retornar, ocorrerá um erro de stack underflow, já que ela desempilhará um endereço de retorno que efetivamente não existe na pilha! Via de regra cada tarefa deve estar codificada dentro do corpo de um loop como while(1) ou similar.

Por hora é isso, espero que este artigo seja útil a todos os leitores e quem sabe num futuro próximo o ULWOS ganha novas e empolgantes funcionalidades!

ULWOS - Multitarefa no RL78

ULWOS – Multitarefa no RL78: Comutador de tarefas
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Software » Firmware » ULWOS – Multitarefa no RL78: Criando tarefas

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: