Windows Embedded Compact e Tempo Real

Uma das principais características que tornam diferente o Windows Embedded Compact de todas as outras versões de Windows é a capacidade de desenvolver aplicativos de tempo real para ele. Nunca é demais lembrar aqui o significado que tempo real tem no contexto computacional, segue abaixo uma definição encontrada no livro: Real-Time System Design and Analysis de autoria de Phillip A. Laplante.

A real-time system is a system that must satisfy explicit (bounded) response-time constraints or risk severe consequences, including failure.

Real-Time System Design and Analysis, Phillip A. Laplante

Ou seja, um sistema de tempo real deve executar um código produzindo uma resposta precisa e também deve fazer isso dentro de um tempo previamente estabelecido, caso contrário isso pode levar a falha do sistema. Portanto, tempo real não quer dizer necessariamente um processamento, ou S.O., muito rápido.

O Windows Embedded Compact é capaz de se comportar de acordo com a definição acima. Para isso uma das primeiras coisas que devemos entender é o escalonador de tarefas do WEC. Basicamente o WEC obedece duas regras simples para agendar as tarefas, para avaliar cada uma destas regras o WEC faz uma avaliação do que ocorre no sistema a cada 1ms. As regras são:

  1. Tarefas que possuem maior prioridade devem ser executadas primeiro.
  2. Tarefas com o mesmo nível de prioridade devem ser executadas segundo um Round Robin com um Timeslice de 100ms.

A respeito da regra número um, o WEC possui 256 níveis de prioridade, valores mais altos significam prioridade mais baixa, valores mais baixos são prioridades mais baixa. Uma orientação para o uso das prioridades pode ser encontrada no seguinte artigo: Real-Time Priority System Levels (Windows Embedded CE 6.0).

Com relação a regra número dois, quando diversas tarefas compartilham a mesma prioridade basicamente o sistema ira rodar cada uma das tarefas por no máximo 100ms (este tempo pode ser configurado entre 40ms – 100ms). Uma vez que este tempo, chamado de Quantun, termina, o Kernel passara a executar a outra tarefa de mesma prioridade até que o Quantun dela também termine. Neste momento o Kernel irá “passar a bola” para uma terceira tarefa de mesma prioridade se existir ou caso contrário retomar a execução da primeira tarefa e assim sucessivamente até que todas as tarefas estejam finalizadas.

OLYMPUS DIGITAL CAMERA

 

Figura 1: Hardware utilizado foi um computador em módulo Toradex Colibri VF61 junto com uma placa de suporte Iris.

Para acrescentar uma abordagem mais prática a este Post, vou propor dois exemplos onde poderemos explorar de forma simples as regras descritas acima. No primeiro exemplo vou instanciar duas tarefas diferentes onde uma delas coloca uma saída digital do meu Hardware em nível lógico 1 e a outra tarefa em nível lógico zero. Mais uma vez, vou utilizar um computador em módulo Toradex Colibri VF61 junto com uma placa de suporte Iris, figura 1. O Colibri VF61 é um ARM Cortex-A5 de 500MHz com 256MB de RAM e 512 MB de Flash já embarcados no módulo. Estou rodando neles um Windows Embedded Compact 7.0 fornecido pelo fabricante do módulo. Segue o código fonte abaixo.

#include  
#include "gpioLib.h"
#include "CoProcLib.h"

#define PIN_OUT 133

          HANDLE hThreadON, hThreadOFF;

          DWORD WINAPI ThreadON(void)
          { 
              while( 1 )
              {
                  //Aciona GPIO para nível lógico alto
                  SetPinLevel(PIN_OUT, 1);
              }
              return 0; 
          }

          DWORD WINAPI ThreadOFF(void)
              { 
              while( 1 )
              {
                  //Aciona GPIO para nível lógico baixo
                  SetPinLevel(PIN_OUT, 0);
              }
              return 0; 
          }

int WINAPI WinMain(HINSTANCE hInstance, // handle to current instance 

HINSTANCE hPrevInstance, // handle to previous instance 

LPWSTR lpCmdLine, // pointer to command line 

int nCmdShow) // show state of window 

     { 

           SetPinAltFn(PIN_OUT,-1,DIR_OUT); //Set Pin functonality to GPIO and set to Input

          //Aumentando a prioridade da Thread que chama as demais Threads
           CeSetThreadPriority(GetCurrentThread(), 200);

           //Criando as duas Threads apontando para diferentes funções
           hThreadON = CreateThread(0,0,ThreadON,0,0,0); 
           hThreadOFF = CreateThread(0,0,ThreadOFF,0,0,0);

           //Configurando a mesma prioridade para as duas Threads
           CeSetThreadPriority(hThreadON, 255);
           CeSetThreadPriority(hThreadOFF, 255);

           //Tempo de execução até a finalização do programa. A Thread principal ficará suspensa
           Sleep(30000);
           DeInitGPIOLib();
 
           return(TRUE); 

     }

Note que existem duas diferentes funções ThreadON e ThreadOFF, elas não fazem nada mais que ligar ou desligar respectivamente um pino que foi configurado como saída digital. Note também que cada função irá rodar indefinidamente não liberando recursos para outras Threads ou programas.

No código principal as funções são instanciadas uma após a outra e podemos ver no Osciloscópio ligado em nossa saída digital o resultado da execução do programa. Conforme esperado, uma vez que as tarefas são criadas com o mesmo nível de prioridade, elas irão rodar de acordo com um agendamento Round Robin com um “timeslice” de 100ms para cada tarefa.

TESTE1

 

 Figura 2: Execução de duas Threads com o mesmo nível de prioridade, a primeira liga uma saída digital e a segunda desliga.

Para testar a regra número 1, vamos fazer as seguintes alterações em nosso código, primeiramente na função ThreadON colocando um comando Sleep(30), que fará a Thread que invoca e função suspender sua execução e solicitar para o Kernel seu regresso em 30 milisegundos.

DWORD WINAPI ThreadON(void)
{
    while( 1 )
    {
        //Aciona GPIO para nível lógico alto
        SetPinLevel(PIN_OUT, 1);
        Sleep(30);
    } 
    return 0; 
}

 

Na chamadas das Threads, vamos agora também aumentar a prioridade da Thread que liga a saída digital conforme abaixo:

CeSetThreadPriority(hThreadON, 254);

CeSetThreadPriority(hThreadOFF, 255);

Desta forma a ThreadON terá maior prioridade que a ThreadOFF. O resultado mais uma vez pode ser observado no Osciloscópio. Cada vez que a Thread de maior prioridade “acorda” o Kernel interrompe a Thread de menor prioridade para dar lugar a Thread de maior prioridade.

TESTE2

Figura 3: Execução de Threads com prioridades diferentes. A Thread de maior prioridade interrompe a de menor caso seja agendada.

Trabalhando com as Threads e conhecendo o comportamento do Kernel ao agendar as mesmas você consegue uma execução determinística do seu sistema e pode criar sistemas de tempo real utilizando o Windows Embedded Compact!

Até a próxima

Guilherme

Guilherme Fernandes

Mestre em Engenharia Mecatrônica pela Escola de Engenharia de São Carlos (USP) atua como diretor da Toradex Brasil. Foi responsável pela implantação do escritório de vendas e suporte da Toradex no Brasil. Trabalhou 7 anos como gerente de engenharia de sistemas na área de automação industrial desenvolvendo mais de 300 projetos de máquinas para linhas de montagem e teste de produção para o setor de autopeças.

Deixe uma resposta

O seu endereço de email não será publicado Campos obrigatórios são marcados *

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>