Como posso criar vários threads em execução?


60

Existe uma maneira de ter várias partes do programa funcionando juntas sem fazer várias coisas no mesmo bloco de código?

Um segmento aguardando um dispositivo externo e também piscando um LED em outro segmento.


3
Você provavelmente deve primeiro se perguntar se realmente precisa de tópicos. Os temporizadores podem já estar OK para as suas necessidades e são suportados nativamente no Arduino.
Jfpoilpret

11
Você também pode conferir o Uzebox. É um console de videogame homebrew de dois chips. Portanto, embora não seja exatamente um Arduino, todo o sistema é baseado em interrupções. Portanto, áudio, vídeo, controles etc. são todos acionados por interrupção, enquanto o programa principal não precisa se preocupar com nada disso. Pode ser uma boa referência.
Cbmeeks 14/07/2015

Respostas:


50

Não há suporte para multiprocessos nem multithreading no Arduino. Você pode fazer algo próximo a vários threads com algum software.

Você quer olhar para Protothreads :

Os prototreads são threads sem pilha extremamente leves, projetados para sistemas com muita restrição de memória, como pequenos sistemas incorporados ou nós de rede de sensores sem fio. Os protothreads fornecem execução linear de código para sistemas controlados por eventos implementados em C. Os protothreads podem ser usados ​​com ou sem um sistema operacional subjacente para fornecer manipuladores de eventos de bloqueio. Os protothreads fornecem fluxo de controle seqüencial sem máquinas de estado complexas ou multithread completo.

Obviamente, há um exemplo do Arduino aqui com código de exemplo . Esta questão SO também pode ser útil.

O ArduinoThread também é bom.


Note que o Arduino DUE tem uma exceção a esta, com múltiplas malhas de controle: arduino.cc/en/Tutorial/MultipleBlinks
tuskiomi

18

O Arduino baseado em AVR não suporta segmentação (hardware), não estou familiarizado com o Arduino baseado em ARM. Uma maneira de contornar essa limitação é o uso de interrupções, especialmente interrupções cronometradas. Você pode programar um timer para interromper a rotina principal a cada tantos microssegundos, para executar uma outra rotina específica.

http://arduino.cc/en/Reference/Interrupts


15

É possível fazer multi-threading do lado do software no Uno. A segmentação no nível do hardware não é suportada.

Para obter multithreading, será necessária a implementação de um planejador básico e a manutenção de um processo ou lista de tarefas para rastrear as diferentes tarefas que precisam ser executadas.

A estrutura de um planejador não preventivo muito simples seria como:

//Pseudocode
void loop()
{

for(i=o; i<n; i++) 
run(tasklist[i] for timelimit):

}

Aqui, tasklistpode haver uma matriz de ponteiros de função.

tasklist [] = {function1, function2, function3, ...}

Com cada função do formulário:

int function1(long time_available)
{
   top:
   //Do short task
   if (run_time<time_available)
   goto top;
}

Cada função pode executar uma tarefa separada, como function1manipulações de LED e function2cálculos de flutuação. Será responsabilidade de cada tarefa (função) aderir ao tempo alocado a ela.

Felizmente, isso deve ser suficiente para você começar.


2
Não tenho certeza se falaria sobre "threads" ao usar um agendador não preventivo. By the way, tal programador já existe como uma biblioteca arduino: arduino.cc/en/Reference/Scheduler
jfpoilpret

5
@jfpoilpret - Multithreading cooperativo é uma coisa real.
Connor Lobo

Sim, você está certo! Meu erro; Fazia tanto tempo que eu não havia enfrentado multithreading cooperativo que, em minha opinião, o multithreading tinha que ser preventivo.
Jfpoilpret

9

Conforme a descrição de seus requisitos:

  • um encadeamento aguardando um dispositivo externo
  • uma linha piscando um LED

Parece que você pode usar uma interrupção do Arduino para o primeiro "encadeamento" (prefiro chamá-lo de "tarefa", de fato).

As interrupções do Arduino podem chamar uma função (seu código) com base em um evento externo (nível de tensão ou alteração de nível em um pino de entrada digital), que acionará sua função imediatamente.

No entanto, um ponto importante a ser lembrado com as interrupções é que a função chamada deve ser o mais rápida possível (normalmente, não deve haver delay()chamada ou qualquer outra API que dependa delay()).

Se você tiver uma tarefa longa para ativar no acionador de evento externo, poderá usar um agendador cooperativo e adicionar uma nova tarefa a partir da sua função de interrupção.

Um segundo ponto importante sobre interrupções é que seu número é limitado (por exemplo, apenas 2 na ONU). Portanto, se você começar a ter mais eventos externos, precisará implementar algum tipo de multiplexação de todas as entradas em uma, e sua função de interrupção determinará qual entrada de multiplexação foi o acionador real.


6

Uma solução simples é usar um agendador . Existem várias implementações. Isso descreve em breve um que está disponível para placas baseadas em AVR e SAM. Basicamente, uma única chamada iniciará uma tarefa; "esboço dentro de um esboço".

#include <Scheduler.h>
....
void setup()
{
  ...
  Scheduler.start(taskSetup, taskLoop);
}

Scheduler.start () adicionará uma nova tarefa que executará o taskSetup uma vez e depois chamará repetidamente taskLoop, assim como o esboço do Arduino funciona. A tarefa tem sua própria pilha. O tamanho da pilha é um parâmetro opcional. O tamanho da pilha padrão é 128 bytes.

Para permitir a alternância de contexto, as tarefas precisam chamar yield () ou delay () . Há também uma macro de suporte para aguardar uma condição.

await(Serial.available());

A macro é açúcar sintático para o seguinte:

while (!(Serial.available())) yield();

Await também pode ser usada para sincronizar tarefas. Abaixo está um exemplo de trecho:

volatile int taskEvent = 0;
#define signal(evt) do { await(taskEvent == 0); taskEvent = evt; } while (0)
...
void taskLoop()
{
  await(taskEvent);
  switch (taskEvent) {
  case 1: 
  ...
  }
  taskEvent = 0;
}
...
void loop()
{
  ...
  signal(1);
}

Para mais detalhes, veja os exemplos . Existem exemplos de vários LEDs piscando para botão de retorno e um shell simples com leitura de linha de comando sem bloqueio. Modelos e espaços para nome podem ser usados ​​para ajudar a estruturar e reduzir o código fonte. O esboço abaixo mostra como usar as funções de modelo para multi-piscar. É suficiente com 64 bytes para a pilha.

#include <Scheduler.h>

template<int pin> void setupBlink()
{
  pinMode(pin, OUTPUT);
}

template<int pin, unsigned int ms> void loopBlink()
{
  digitalWrite(pin, HIGH);
  delay(ms);
  digitalWrite(pin, LOW);
  delay(ms);
}

void setup()
{
  Scheduler.start(setupBlink<11>, loopBlink<11,500>, 64);
  Scheduler.start(setupBlink<12>, loopBlink<12,250>, 64);
  Scheduler.start(setupBlink<13>, loopBlink<13,1000>, 64);
}

void loop()
{
  yield();
}

Há também uma referência para dar uma idéia do desempenho, ou seja, tempo para iniciar a tarefa, alternar o contexto etc.

Por último, existem algumas classes de suporte para sincronização e comunicação em nível de tarefa; Fila e semáforo .


3

De um encantamento anterior deste fórum, a seguinte pergunta / resposta foi movida para Engenharia Elétrica. Ele possui um código de arduino de amostra para piscar um LED usando uma interrupção do timer enquanto usa o loop principal para executar E / S serial.

https://electronics.stackexchange.com/questions/67089/how-can-i-control-things-without-using-delay/67091#67091

Repost:

As interrupções são uma maneira comum de fazer as coisas enquanto outra coisa está acontecendo. No exemplo abaixo, o LED está piscando sem usar delay(). Sempre que é Timer1acionado, a rotina de serviço de interrupção (ISR) isrBlinker()é chamada. Liga / desliga o LED.

Para mostrar que outras coisas podem acontecer simultaneamente, loop()grava foo / bar repetidamente na porta serial, independentemente do LED piscar.

#include "TimerOne.h"

int led = 13;

void isrBlinker()
{
  static bool on = false;
  digitalWrite( led, on ? HIGH : LOW );
  on = !on;
}

void setup() {                
  Serial.begin(9600);
  Serial.flush();
  Serial.println("Serial initialized");

  pinMode(led, OUTPUT);

  // initialize the ISR blinker
  Timer1.initialize(1000000);
  Timer1.attachInterrupt( isrBlinker );
}

void loop() {
  Serial.println("foo");
  delay(1000);
  Serial.println("bar");
  delay(1000);
}

Esta é uma demonstração muito simples. Os ISRs podem ser muito mais complexos e podem ser acionados por temporizadores e eventos externos (pinos). Muitas das bibliotecas comuns são implementadas usando ISRs.



2

Você também pode tentar minha biblioteca ThreadHandler

https://bitbucket.org/adamb3_14/threadhandler/src/master/

Ele usa um agendador de interrupção para permitir a alternância de contexto sem retransmitir yield () ou delay ().

Criei a biblioteca porque precisava de três threads e dois deles para rodar em um momento preciso, independentemente do que os outros estavam fazendo. O primeiro thread tratou da comunicação serial. O segundo estava executando um filtro Kalman usando multiplicação de matriz flutuante com a biblioteca Eigen. E o terceiro era um segmento de loop de controle de corrente rápido que precisava ser capaz de interromper os cálculos da matriz.

Como funciona

Cada encadeamento cíclico tem uma prioridade e um período. Se um encadeamento, com prioridade mais alta que o encadeamento em execução atual, atingir o próximo tempo de execução, o planejador pausará o encadeamento atual e passará para o de maior prioridade. Depois que o encadeamento de alta prioridade conclui sua execução, o planejador volta para o encadeamento anterior.

Regras de agendamento

O esquema de agendamento da biblioteca ThreadHandler é o seguinte:

  1. Prioridade mais alta primeiro.
  2. Se a prioridade for a mesma, o encadeamento com o primeiro prazo final será executado primeiro.
  3. Se dois threads tiverem o mesmo prazo, o primeiro thread criado será executado primeiro.
  4. Um encadeamento só pode ser interrompido por encadeamentos com maior prioridade.
  5. Após a execução de um encadeamento, ele bloqueará a execução de todos os encadeamentos com menor prioridade até que a função de execução retorne.
  6. A função de loop tem prioridade -128 em comparação com os threads ThreadHandler.

Como usar

Threads podem ser criados via herança c ++

class MyThread : public Thread
{
public:
    MyThread() : Thread(priority, period, offset){}

    virtual ~MyThread(){}

    virtual void run()
    {
        //code to run
    }
};

MyThread* threadObj = new MyThread();

Ou via createThread e uma função lambda

Thread* myThread = createThread(priority, period, offset,
    []()
    {
        //code to run
    });

Os objetos de thread se conectam automaticamente ao ThreadHandler quando são criados.

Para iniciar a execução dos objetos de encadeamento criados, chame:

ThreadHandler::getInstance()->enableThreadExecution();

1

E aqui está mais uma biblioteca multitarefa cooperativa de microprocessadores - PQRST: uma fila de prioridade para executar tarefas simples.

Nesse modelo, um encadeamento é implementado como uma subclasse de a Task, programada para algum tempo futuro (e possivelmente remarcada em intervalos regulares, se, como é comum, ela subclasses LoopTask). O run()método do objeto é chamado quando a tarefa vence. O run()método faz algum trabalho devido e, em seguida, retorna (este é o bit cooperativo); normalmente manterá algum tipo de máquina de estado para gerenciar suas ações em invocações sucessivas (um exemplo trivial é a light_on_p_variável no exemplo abaixo). Isso requer uma pequena reflexão sobre como você organiza seu código, mas provou ser muito flexível e robusto em uso bastante intensivo.

É agnóstico quanto às unidades de tempo, por isso é tão bom correr em unidades de millis()quanto micros()ou em qualquer outro tick que seja conveniente.

Aqui está o programa 'piscar' implementado usando esta biblioteca. Isso mostra apenas uma única tarefa em execução: outras tarefas normalmente seriam criadas e iniciadas dentro setup().

#include "pqrst.h"

class BlinkTask : public LoopTask {
private:
    int my_pin_;
    bool light_on_p_;
public:
    BlinkTask(int pin, ms_t cadence);
    void run(ms_t) override;
};

BlinkTask::BlinkTask(int pin, ms_t cadence)
    : LoopTask(cadence),
      my_pin_(pin),
      light_on_p_(false)
{
    // empty
}
void BlinkTask::run(ms_t t)
{
    // toggle the LED state every time we are called
    light_on_p_ = !light_on_p_;
    digitalWrite(my_pin_, light_on_p_);
}

// flash the built-in LED at a 500ms cadence
BlinkTask flasher(LED_BUILTIN, 500);

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    flasher.start(2000);  // start after 2000ms (=2s)
}

void loop()
{
    Queue.run_ready(millis());
}

Essas são tarefas de execução até a conclusão, certo?
Edgar Bonet

@ EdgarBonet Não sei bem o que você quer dizer. Depois que o run()método é chamado, ele não é interrompido; portanto, ele tem a responsabilidade de concluir razoavelmente prontamente. Normalmente, no entanto, ele faz o seu trabalho e se remarca (possivelmente automaticamente, no caso de uma subclasse de LoopTask) por algum tempo futuro. Um padrão comum é que a tarefa mantenha alguma máquina de estado interna (um exemplo trivial é o light_on_p_estado acima), para que ela se comporte adequadamente no próximo dia de vencimento.
Norman Gray

Portanto, sim, essas são tarefas de execução até a conclusão (RtC): nenhuma tarefa pode ser executada antes que a atual conclua sua execução retornando de run(). Isso contrasta com os threads cooperativos, que podem render a CPU, por exemplo, chamando yield()ou delay(). Ou encadeamentos preventivos, que podem ser agendados a qualquer momento. Eu sinto que a distinção é importante, pois vi que muitas pessoas que andam por aqui procurando por threads o fazem porque preferem escrever código de bloqueio em vez de máquinas de estado. Bloquear threads reais que produzem a CPU é bom. Bloquear tarefas RtC não é.
Edgar Bonet

@ EdgarBonet É uma distinção útil, sim. Eu consideraria tanto esse estilo quanto os threads no estilo yield, simplesmente como estilos diferentes de threads cooperativos, em oposição aos threads preventivos, mas é verdade que eles exigem uma abordagem diferente para codificá-los. Seria interessante ver uma comparação cuidadosa e aprofundada das várias abordagens mencionadas aqui; uma boa biblioteca não mencionada acima é a protothreads . Acho coisas para criticar em ambos, mas também para elogiar. Eu (é claro) prefiro minha abordagem, porque parece mais explícita e não precisa de pilhas extras.
Norman Gray

(correção: protothreads foi mencionado, na resposta de @ sachleen )
Norman Gray
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.