Sistemas de Tempo Real – Parte 2

- por Sergio Prado

Categorias: RTOS Tags: , , , ,

Na parte 1 deste artigo apresentei os principais conceitos e a arquitetura básica de um Sistema de Tempo Real. Neste artigo irei exemplificar alguns conceitos através do desenvolvimento de uma aplicação utilizando um RTOS disponível no mercado.

Projeto

O projeto que iremos desenvolver é um Sistema de Alarme com Monitoramento Remoto:

O Sistema é dividido em duas aplicações: Sistema de Alarme Local e Sistema de Monitoramento Remoto. O Sistema de Alarme Local é composto por um sensor de presença, um sinal luminoso e um display para exibição de informações. O Sistema de Monitoramento Remoto é composto apenas por um display para a exibição de informações recebidas remotamente.

O funcionamento do projeto é simples: o Sistema de Alarme monitora constantemente o sensor de presença, e a recepção de um sinal de detecção implica no acionamento do alarme. Uma vez acionado, o alarme liga o sinal luminoso, e envia uma mensagem para o Sistema de Monitoramento Remoto. Ambos os sistemas local e remoto mantém as informações atualizadas do status do alarme no display.

Pré-requisitos

Não será necessário nenhum hardware específico para a implementação deste projeto. Todas as entradas (sensores) e saídas (sinal luminoso e display) serão emuladas por software.

Todo o projeto será desenvolvido e executado em um PC de arquitetura x86 Intel, rodando o Sistema Operacional GNU/Linux. Para o desenvolvimento da aplicação será utilizado o uCOS-II – RTOS da Micrium. Uma versão de avaliação pode ser baixada diretamente do site da Micrium em www.micrium.com.

Arquitetura

O projeto está dividido em duas aplicações Linux, uma aplicação responsável pelo Sistema de Alarme Local, e a outra pelo Sistema de Monitoramento Remoto. As aplicações se comunicam através de uma rede TCP/IP.

O sensor será emulado através do mecanismo de envio de sinais do Linux. Para acionar o sensor, basta enviar o sinal USR1 para o processo do Sistema de Alarme Local. 

O display será representado pelo monitor, e as informações de status do alarme serão exibidas na tela, tanto na aplicação local, quanto na aplicação remota. O acionamento do sinal luminoso será exibido também na tela da aplicação.

UCOS-II

O uCOS-II, desenvolvido pela Micrium, possui código-fonte aberto e disponível gratuitamente para fins educacionais. É pequeno (o Kernel compilado ocupa em torno de 20K de memória), mas altamente funcional, sendo portanto indicado para sistemas com poucos recursos computacionais. O Kernel é preemptivo, e existem portes para diversas arquiteturas, dentre elas x86, 68K, ARM e PIC.

Modelagem da aplicação

Para modelar a aplicação, o primeiro passo é dividir o sistema em pequenas tarefas, com objetivos bem claros, e definir a prioridade de cada tarefa perante as demais.

A aplicação pode ser modelada conforme a figura abaixo:

O sistema foi dividido em quatro tarefas, e suas prioridades definidas conforme abaixo:

Prioridade 1 -> TaskRegras -> Lógica da aplicação

Prioridade 2 -> TaskSensores -> Leitura dos sensores

Prioridade 3 -> TaskComm -> Comunicação para o monitoramento remoto

Prioridade 4 -> TaskDisplay -> Exibição das informações no display

É de extrema importância a definição correta da prioridade de cada tarefa, já que o sistema operacional irá utilizar esta informação para realizar o escalonamento das tarefas da aplicação.

A implementação das quatro tarefas serão compartilhadas tanto pelo Sistema de Alarme Local quanto pelo Sistema de Monitoramento Remoto.

Sistema de Alarme Local

Este sistema utiliza as quatro tarefas. A tarefa TaskRegras é responsável pela lógica da aplicação, recebendo mensagens de acionamento de sensores da tarefa TaskSensores, atualizando o status do sistema, e enviando mensagens para a tarefa TaskComm, responsável pela comunicação com o Sistema Remoto. A tarefa TaskDisplay irá aguardar a atualização das informações do alarme e atualizar as informações no display.

Sistema de Monitoramento Remoto

Este sistema é mais simples e utiliza três tarefas. A tarefa TaskComm irá aguardar o recebimento de mensagens da rede de comunicação e enviar para a tarefa TaskRegras, que será responsável pela atualização do status do sistema. A tarefa TaskDisplay irá aguardar a atualização das informações do alarme e atualizar as informações no display.

Codificação

Nosso primeiro passo para a codificação da aplicação é criar a função principal do projeto – main() – que será responsável por iniciar o sistema operacional e criar as tarefas da aplicação.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(void)
{
    /* inicializa uCOS-II */
    OSInit();
 
    /* cria a tarefa principal */
    OSTaskCreate(TaskRegras, NULL, TaskRegrasStk[TASK_STK_SIZE],
                 PRIO_TSK_REGRASCENTRAL);
 
    /* inicializa sistema operacional */
    OSStart();
 
    /* nunca deve chegar aqui! */
    return 0;
}

Na listagem acima podemos ver o código da função main() da aplicação, que executa basicamente 3 operações:

  1. Inicializa o sistema operacional chamando a função OSInit()
  2. Cria a tarefa TaskRegras chamando a função OSTaskCreate()
  3. Inicia o escalonamento de tarefas chamando a função OSStart()

Após a chamada a OSStart(), o sistema operacional inicia o escalonamento de tarefas, sendo que até o momento apenas uma tarefa da aplicação para ser escalonada – a tarefa TaskRegras. Esta tarefa será a responsável pela criação das outras tarefas da aplicação.

Implementação das tarefas

Grande parte das tarefas é implementada através de um laço infinito, e possui um formato padrão, parecido com a listagem abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void TaskX(void *pdata)
{
    /* codigo de inicializacao */
    ...
 
    while (1) {
 
        /* aguarda algum evento */
        ...
 
        /* toma alguma acao */
        ...
    }
}

Conforme podemos ver, uma tarefa irá, na maioria das vezes, executar os três passos abaixo:

  1. Inicializar variáveis utilizadas em seu processamento.
  2. Aguardar um evento, que pode ser o recebimento de uma mensagem, o timeout de um pedido de delay, etc.
  3. Tomar uma ação em resposta ao evento recebido.

Podemos perceber este padrão analisando o código da tarefa TaskRegras na listagem abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void TaskRegras(void *pdata)
{
    INT8U err;
    void *pmsg;
 
    /* inicializa as informacoes do sistema */
    initSystemInfo();
 
    /* inicializa as tarefas e recursos utilizados */
    initTasksData();
 
    while (1) {
 
        /* aguarda uma mensagem para tratar */
        pmsg = OSQPend(msgq_regras, 0, &err);
 
        /* trata mensagem recebida */
        if (err == OS_ERR_NONE)
            trataMensagem(pmsg);
    }
}

O primeiro passo é inicializar as variáveis através das chamadas a initSystemInfo() e initTasksData(). O próximo passo é aguardar um evento, que neste caso é a recepção de uma mensagem, com a função OSQPend(), e logo após tratar a mensagem recebida através da função trataMensagem().

Analisando o código da tarefa TaskSensores, na listagem abaixo, também podemos perceber este mesmo padrão:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void TaskSensores(void *pdata)
{
    /* inicializa a leitura dos sensores */
    initSensores();
 
    while (1) {
 
        /* suspende a execucao ate receber sinal
           de acionamento de sensor */
        OSTaskSuspend(PRIO_TSK_SENSORES);
 
        /* envia a mensagem de acionamento dos
           sensores para a tarefa de regras */
        txMsgSetSensores();
    }
}

A tarefa inicializa suas variáveis de controle através da função initSensores(), logo após fica aguardando o acionamento do sensor (recepção do sinal USR1) através da chamada OSTaskSuspend(), e logo em seguida toma uma ação em resposta ao acionamento do sensor, neste caso chamando a função txMsgSetSensores() que será responsável por formatar e enviar uma mensagem de acionamento de sensor para a tarefa TaskRegras.

As outras duas tarefas, TaskComm e TaskDisplay, são implementadas com este mesmo padrão.

Outras funções do uCOS-II

Além das funções exemplificadas nas listagens, outras funções foram utilizadas no projeto, e devem ser mencionadas:

  • OSQPost(): envia uma mensagem para outra tarefa.
  • OSQPend(): aguarda o recebimento de uma mensagem de outra tarefa.
  • OSSemCreate(): cria e inicializa um semáforo para o compartilhamento de recursos.
  • OSSemPend() e OSSemPost(): funções usadas para o compartilhamento de recursos.

O código-fonte completo da aplicação pode ser baixado aqui.

Cobrimos aqui apenas uma pequena parte das funcionalidades do uCOS-II e, para fins didáticos, exemplificamos uma parte da aplicação desenvolvida. A documentação do sistema operacional é extensa e bem didática, e é altamente aconselhável uma leitura mais detalhada para entender todas as funções disponíveis. Bons estudos!

Um abraço,

Sergio Prado

  • Douglas Campos

    Olá Sergio!
    Mais uma vez gostaria de agradecer e o parabenizar pelo blog, a internet é uma ferramenta poderosa, com muita informacão disponível, porém muita coisa espalhada pela web. Aqui é um dos poucos lugares que consigo encontrar tudo o que me interessa em um lugar só!
    Bom tentei rodar seus exemplos porém não obtive sucesso. Não encontrei o port para x86 no site da micrium, porém encontrei o site de um professor ( http://www2.hs-esslingen.de/~zimmerma/software/index_uk.html ), que pelo que me parece ministra uma disciplina e utiliza este port para explicar os conceitos de RTOS para seus alunos.

    Tentei compilar os exemplos dele e também não consegui, você poderia me dar alguma dica de como proceder para conseguir compilar seus exemplos e o ucos II em x86 ou 64 ?
     
    Agradećo desde já a atencão
     
    Douglas

    • http://sergioprado.org sergioprado

      Olá Douglas,

      Você tem razão, a Micrium não esta mais disponibilizando o port do Ucos-II para x86/Linux…:(

      Vou ver se acho a versão que baixei e te envio.

      Abraços!

  • Carlos Sosa

    Muito bom trabalho!

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.