2 Comentários

Gerenciador de tarefas em um PIC 8-bits

Gerenciador de tarefas

Este artigo mostra como implementar um gerenciador de tarefas (orientado à temporização) em um PIC 8-bits, objetivando mostrar que os bons e velhos microcontroladores 8-bits ainda têm (muito) uso, sobretudo em tarefas críticas em tempo.

Tal gerenciador é o embrião de um sistema operacional de tempo real (RTOS). Para mais informações sobre o que é e como é composto um RTOS, leia esta excelente série de artigos do articulista do Embarcados Rodrigo Almeida.

O PIC utilizado para o desenvolvimento deste gerenciador é o PIC 16F628A. O motivo desta escolha dá-se pelo fato de que este microcontrolador é muito popular entre os adeptos do PIC, é um microcontrolador de 8-bits já bem consolidado no mercado e atende perfeitamente às necessidades do gerenciador aqui desenvolvido. Os IDE e compilador utilizados são, respectivamente, MPLAB IDE X e compilador XC8, ambos da própria Microchip.

É importante ressaltar que o gerenciador de tarefas aqui mostrado permite executar somente uma tarefa por vez.

Pré-requisitos

Para acompanhar o conteúdo do artigo, há os seguintes pré-requisitos técnicos:

  • Ter conhecimento intermediário ou avançado de linguagem C;
  • Ter conhecimento básico de microcontroladores PIC (operação de timers e registradores).

Gerenciador de tarefas orientado à temporização - o que é?

Um gerenciador de tarefas orientado à temporização denomina um software embarcado capaz de executar tarefas periodicamente. Ou seja, é um software embarcado aplicável a situações onde o período de execução de uma ou mais tarefas é crucial para o funcionamento de um determinado sistema.

Logo, aparece o questionamento: se há um software embarcado que executa tarefas, o que seria este software embarcado principal e o que seriam as tarefas? Há assim dois ou mais softwares embarcados em execução em um mesmo microcontrolador?

O gerenciador de tarefas propriamente dito e cada uma das funções são partes distintas de um mesmo firmware. Ou seja, um mesmo programa em execução em um microcontrolador conterá o gerenciador de tarefas (análogo a uma central, um núcleo) e as N tarefas. Observe o diagrama da figura 1:

Diagrama geral do gerenciador de tarefas
Figura 1 - Diagrama geral do gerenciador de tarefas

Como o gerenciador de tarefas é a rotina principal deste software embarcado final, ele deve estar contido na função principal, main(). Cada tarefa é isolada em uma função distinta, com sua própria lógica e variáveis locais.

Controle da temporização - como é feito?

Para se ter o controle por temporização, fez-se uso do Timer0 do PIC.

Com ele é feita a técnica chamada timer tick. Nesta técnica, faz-se com que cada interrupção de estouro do timer seja gerada a cada unidade mínima de temporização desejada, logo é possível se tomar ações a cada X interrupções / estouros de tempo.

No gerenciador deste artigo, a unidade mínima de temporização de uma tarefa foi adotada como 1 ms. Portanto, o timer (Timer0 do PIC) foi configurado para gerar um estouro a cada 1 ms, cabendo à rotina de tratamento de interrupção de Timer atualizar o tempo restante, em ms, para a execução das tarefas. Veja a rotina de tratamento da interrupção do Timer0 utilizada:

Conforme pode ser visto na rotina acima, a cada "tick" (equivalente a cada 1ms), o tempo restante para a execução de cada tarefa é atualizado, dado que será utilizado posteriormente para decidir se uma determinada tarefa deve entrar em execução ou não.

Note que, além do controle de temporização por tarefa, há o controle de Timeout de realização de tarefa. Este consiste no seguinte: se há qualquer tarefa sendo executada e se esta demorar mais que o tempo máximo estipulado para execução, o microcontrolador reseta.

Este tipo de controle deve existir no gerenciador de tarefas deste artigo por duas razões:

  1. Este gerenciador de tarefas executa somente uma tarefa por vez;
  2. Se este se trata de um gerenciamento de tarefas voltado a aplicações onde o tempo para executar é o primordial, se uma das tarefas travar (ou ficar executando por muito tempo), todo o software embarcado (e aplicação) será comprometido.

Outro ponto interessante no quesito temporização é que, como o tempo é algo crítico aqui, a fonte de clock do sistema deve vir de um oscilador à cristal. O motivo é que, desta maneira, garante-se mais precisão na frequência do microcontrolador se comparado ao seu oscilador interno. Portanto, neste gerenciador, foi utilizando um cristal de 4MHz como oscilador.

É importante observar também a variável FlagGerouInterrupcaoTimer0. Ela é muito importante para o gerenciador como um todo, pois informará ao programa principal que já se passou o "tick" e que deve ser verificado se alguma tarefa deve ser executada.

Agendamento das tarefas

O agendamento de tarefas/inicialização das tarefas é uma das primeiras coisas a serem feitas, pois ele determina quais tarefas serão executadas e de quanto em quanto tempo. Observe o código abaixo, que corresponde à rotina de agendamento de tarefas do gerenciador deste artigo:

Note que as tarefas (no programa C, são funções) estão sendo referenciadas ao array TarefasAgendadas[]. Este array consiste em um array de ponteiros de função, ou seja, ele armazena os endereços das funções a serem executadas. Quanto a limites, este array pode ser tão maior quanto a memória RAM do microcontrolador suportar.

Além disso, há outros dois arrays em questão: PeriodosTarefasAgendadas[]TemporizacaoAtualTarefas[]. Estes tem a seguinte utilidade:

  1. PeriodosTarefasAgendadas: armazena de quanto em quanto tempo as tarefas devem ser executadas. Seus valores são utilizados na atualização da temporização de cada tarefa (para permitir que ela seja executada novamente no futuro);
  2. TemporizacaoAtualTarefas: contém quanto tempo (em ms) falta pra executar cada tarefa. Seus dados são utilizados na camada executora de tarefas, assim é possível decidir se uma tarefa deve ser executada ou não.

Observe, ainda, a variável HaTarefaEmExecucao. Ela é usada para indicar se há qualquer tarefa em execução no momento e, consequentemente, permite que seja contabilizado o timeout de execução de uma determinada tarefa (na interrupção do timer).

É importante ressaltar que, por ser usado ponteiro de função para armazenar o endereço das tarefas, cada tarefa (em C, trata-se de uma função) deve ser declarada com os mesmos parâmetros de entrada e retorno (no caso das tarefas deste gerenciador, não há parâmetros nem retorno, ou seja, ambos são void).

Execução das tarefas

Uma vez que a parte do controle de temporização está sendo resolvida por interrupção do Timer0 do PIC, a camada de execução de tarefas deverá simplesmente decidir se já está na hora de executar uma determinada tarefa ou não. Observe o código da execução de tarefas a seguir:

Conforme pode ser notado, tudo que o executor de tarefas faz é varrer a temporização de todas as tarefas e verificar quem deve ser executada ou não e atualizar a temporização das mesmas (para permitir execução posterior).

Loop principal e programa principal

O programa principal do gerenciador é bem simples e compacto, conforme pode ser observado a seguir:

Nota-se que ele é composto de duas partes:

  • inicializações gerais e;
  • loop principal ( while(1) ).

No loop principal, é feito um controle de quando se deve chamar a execução de tarefas.

Conforme dito no tópico de temporização, a variável FlagGerouInterrupcaoTimer0 serve para indicar que já houve um "tick" e que o loop principal deve utilizar esta variável para decidir se a checagem de tarefas a serem executadas deve ser chamada. Isto é feito desta forma para evitar chamadas desnecessárias à função ExecutaTarefa(), o que poderia ocasionar perda de tempo no software como um todo.

Gerenciador de tarefas - código completo

A seguir, o código na íntegra do gerenciador de tarefas (núcleo):

Conforme informado, este é apenas o núcleo do gerenciador de tarefas com as tarefas "vazias" (as funções das tarefas não contêm códigos). Portanto, este código sozinho não realiza nenhuma operação. Para realizar operações, basta implementar as rotinas desejadas nas funções Tarefa1, Tarefa2, Tarefa3 e Tarefa4, bem como configurar suas temporizações conforme desejar.

Para melhor ilustrar o funcionamento deste gerenciador, foi preparado um exemplo de utilização, onde as tarefas são:

  1. Tarefa 1: escrever um valor de 3 dígitos em 3 displays de 7 segmentos. Nesta tarefa, está inclusa a multiplexação dos displays;
  2. Tarefa 2: controlar uma breathing light (luz que pisca indicando que o software embarcado está sendo executado). No caso, a breathing light pisca a uma frequência de 0,5Hz (1 segundo aceso e 1 segundo apagado);
  3. Tarefa 3: armazena o tempo decorrido de execução (em segundos) e disponibiliza os valores para a tarefa 2;
  4. Tarefa 4: verifica a entrada RA0. Se esta estiver em nível alto, a tarefa 3 contabiliza o tempo normalmente. Caso contrário, o tempo não é contabilizado.

Cada camada (controle de temporização para display, controle do display e breathing light) está isolada (ou seja, tem os seus próprios arquivos de código e headers, .c e .h respectivamente). Logo, preservou-se o máximo possível do gerenciador original.

No exemplo, consta também uma simulação no Proteus do exemplo. Para baixar o projeto e simulação, visite o GitHub do projeto clicando aqui.

Agradecimentos

Agradecimento ao Fábio Souza por todo o apoio no projeto desde gerenciador de tarefas e no exemplo desenvolvido.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Software » Gerenciador de tarefas em um PIC 8-bits
Comentários:
Notificações
Notificar
guest
2 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Vinicius
Vinicius
10/05/2016 02:31

Olá, gostei muito da explicação... Trabalho semelhante a essa forma em processadores de 8, 16 e 32 bits da família Microchip e atualmente estou desenvolvendo meu TCC sobre um processador NXP 32 bits. Bom, acredito ter uma melhoria para não gerar erros de timeout indevidos... Seria: carregar o timeout antes de setar o flag de HaTarefaEmExecucao, pois, caso o último timeout carregado tenha atingido o valor 1, porém, a tarefa terminou antes do timeout... ao setar o flag e antes de carregar o próximo timeout ocorrer uma interrupção, o timeout vem a 0 e o processador será reiniciado. A solução… Leia mais »

trackback
21/03/2016 14:47

[…] Esta multiplexação pode ser dividida em outras duas categorias: a multiplexação cooperativa e a multiplexação preemptiva. Na multiplexação ou multitarefa cooperativa, cada tarefa tem de provocar a alteração do fluxo de código para a tarefa seguinte. Isto significa que depende de cada tarefa individualmente (e da cooperação entre elas) a eficiência da multiplexação. Um artigo muito interessante sobre multitarefa cooperativa foi publicado pelo Pedro Bertoleti no portal Embarcados. […]

Talvez você goste:

Séries

Menu

WEBINAR
 

Soluções inteligentes para acionamento de MOSFETs/IGBTs com família STDRIVE

Data: 08/10 às 15:00h - Apoio: STMicroelectronics
 
INSCREVA-SE AGORA »



 
close-link