Como fazer várias coisas ao mesmo tempo

24 Maio 2010 at 22:52 (Tutorial) (, , )

Não, não é um artigo sobre gestão de tempo :)! Às vezes há por aí pessoas a dar voltas à cabeça sobre “como fazer várias coisas ao mesmo tempo” num programa Arduino ou outra placa com microcontrolador. Às vezes começam a pensar em threads e sistemas operativos, mas em máquinas tão pequenas e com programadores iniciantes, o melhor mesmo é ficar bem longe da complexidade de sistemas multi-tarefa preemptivos/divisão de tempo e respectiva sincronização. Mas então, há alguma alternativa adequada a máquinas de poucos recursos e fácil de usar? Há. É ler o resto do artigo :)

Um sistema “multithreading” é complexo para máquinas de recursos limitados como um Arduino e complexo para iniciantes, e por isso eu recomendo (e pessoalmente é o que uso) o modelo “por eventos” (também chamado “multitarefa cooperativa”). O modelo por eventos funciona bem se não houver operações “atómicas” que demorem muito tempo.

No modelo por eventos, cada tarefa é executada numa função mas essa função não pode bloquear. O ciclo principal do programa chama repetidamente todas as tarefas sem parar, por exemplo em round-robin (em sequência, uma a seguir à outra, no fim volta à primeira e repete tudo). Depois é uma questão de “truques” para impedir que as funções bloqueiem, geralmente usando uma máquina de estados.

Vamos pegar num problema que vi no LusoRobotica.com e que deu origem a este meu artigo: ter 2 LEDs a piscar a velocidades diferentes e ao mesmo tempo enviar números para a consola. Isto são 3 tarefas.

O ciclo principal invoca as 3 tarefas em sequência, e no final faz uma pausa de 10ms. Esta pausa serve para termos uma unidade de tempo conhecida, caso contrário o ciclo é executado a toda a velocidade e não poderemos controlar a cadência a que, por exemplo, os LEDs piscam:

int loop ()
{
    piscaLed1();
    piscaLed2();
    enviaNumeros();
    delayMs(10);
}

Agora sabemos que cada tarefa é invocada a intervalos de 10ms. Se queremos o nosso LED1 a piscar 1 vez a cada segundo, temos que o inverter de estado a cada 500ms. Como sabemos que a tarefa é invocada de 10ms em 10ms, temos que inverter o estado do LED a cada 500ms / 10ms = 50, 50 invocações (50 x 10ms = 500ms), e para isso usamos uma variável para contar o número de invocações:

void piscaLed1 ()
{
    static int contador = 0;

    contador = contador + 1;
    if (contador == 50)
    {
        togglePin(...);
        contador = 0;
    }
}

Uma variável declarada com static quer dizer que é uma variável global mas que só é “visível” dentro do bloco onde está declarada (“bloco” é o conteúdo de um conjunto de chavetas). Eu podia tê-la declarado fora das funções, mas como ela só diz respeito e só é usada dentro da função, resolvi declará-la como static para a casa ficar mais organizada e arrumada. Ainda outra razão para declarar as variáveis como static sempre que possível é que desta forma podemos ter variáveis “globais” com o mesmo nome. A função da tarefa piscaLed2 pode usar exactamente o mesmo nome para a variável, “contador”, uma vez que as variáveis só são “visíveis” dentro do bloco onde são declaradas.
A variável tem que ser global pois a execução vai entrar e sair da função mas o valor da variável tem que ser preservado, pois vamos usá-la e queremos manter o seu valor de umas invocações para outras.

A tarefa piscaLed2() é similar à piscaLed1(), mudando apenas o número 50 para estar de acordo com a cadência que se desejar.

Agora vamos fazer a tarefa enviaNumeros(), que pode parecer complicada mas não é. Basta ter uma variável que guarda o último número que foi enviado, e enviar um número a cada invocação:

void enviaNumeros ()
{
    static int numero = 0;

    numero = numero + 1;
    Serial.write(numero);
}

Esta tarefa vai tentar enviar 1 número a cada 10ms, o que pode ser demasiado rápido para o que queremos ou até mesmo para a velocidade de transmissão que foi escolhida para a porta série (os bps). Nesse caso podiamos querer enviar apenas, vamos supôr, 1 número por segundo. Isto é fácil de fazer, pois é o que já fazem as tarefas dos LEDs e podemos usar o mesmo mecanismo! Para 1 segundo o nosso contador tem que contar 1000ms / 10ms = 100, 100 vezes antes de fazermos alguma coisa (enviar um numero).

void enviaNumeros ()
{
    static int contador =  0;

    contador = contador + 1;
    if (contador == 100)
    {
        static int numero = 0;

        numero = numero + 1;
        Serial.write(numero);

        contador = 0;
    }
}

Reparem que o código original da função está lá todo inalterado, mas agora dentro do “if (contador == …” de modo a que só é executado 1 vez de 100 em 100 invocações da função.

Há alguns detalhes que não mencionei mas em geral a técnica é esta, e isto permite fazer bastantes coisas “em simultâneo” de uma forma simples. O importante é não deixar que as tarefas demorem muito tempo ou bloqueiem. Por exemplo, não podem fazer pausas dentro das funções. Se precisam de uma pausa, têm que usar um contador para esperar o tempo desejado, tal como fiz nos exemplos. As funções têm que entrar, fazer qualquer coisa “rápida” e sair.

Esta estrutura já serve para começarem e fazer imensas coisas e é uma forma simples e segura de conseguir fazer alguma multi-tarefa.

Enjoy ;)

p.s. O código não compila, é essencialmente apenas ilustrativo.

About these ads

Deixar uma resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

WordPress.com Logo

Está a comentar usando a sua conta WordPress.com Log Out / Modificar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Log Out / Modificar )

Facebook photo

Está a comentar usando a sua conta Facebook Log Out / Modificar )

Google+ photo

Está a comentar usando a sua conta Google+ Log Out / Modificar )

Connecting to %s

Seguir

Get every new post delivered to your Inbox.

%d bloggers like this: