Sistemas de Tempo Real – Parte 2
- por Sergio Prado
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:
- Inicializa o sistema operacional chamando a função OSInit()
- Cria a tarefa TaskRegras chamando a função OSTaskCreate()
- 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:
- Inicializar variáveis utilizadas em seu processamento.
- Aguardar um evento, que pode ser o recebimento de uma mensagem, o timeout de um pedido de delay, etc.
- 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.
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