Existe uma maneira de ter várias partes do programa rodando juntas sem fazer várias coisas no mesmo bloco de código?
Um thread esperando por um dispositivo externo enquanto também pisca um LED em outro thread.
Comentários
- Você provavelmente deve primeiro se perguntar se realmente precisa de threads. Os temporizadores podem ser adequados para as suas necessidades e eles são nativamente suportados no Arduino.
- Você também pode verificar o Uzebox. É ‘ é um console de videogame caseiro de dois chips. Portanto, embora não seja ‘ exatamente um Arduino, todo o sistema é construído em interrupções. Portanto, áudio, vídeo, controles, etc. são todos controlados por interrupções, enquanto o programa principal não ‘ precisa se preocupar com nada disso. Pode ser uma boa referência.
Resposta
Não há suporte para multiprocesso, nem multi-threading o Arduino. Você pode fazer algo próximo a vários threads com algum software.
Você quer dar uma olhada em Protothreads :
Protothreads são threads sem pilha extremamente leves, projetados para sistemas com grande restrição de memória, como pequenos sistemas embarcados ou nós de rede de sensores sem fio. Protothreads fornecem execução de código linear para sistemas orientados a eventos implementados em C. Protothreads podem ser usados com ou sem um sistema operacional subjacente para fornecer manipuladores de eventos de bloqueio. Protothreads fornecem fluxo sequencial de controle sem máquinas de estado complexas ou multi-threading completo.
Claro, há um exemplo de Arduino aqui com exemplo de código . Esta pergunta do SO pode ser útil também.
ArduinoThread é um bom também.
Comentários
- Observe que o Arduino DUE tem uma exceção a isso, com vários loops de controle: arduino.cc/en/Tutorial/MultipleBlinks
Resposta
Arduino baseado em AVR não suporta threading (hardware), eu não estou familiarizado com Arduino baseado em ARM. Uma maneira de contornar essa limitação é o uso de interrupções, especialmente interrupções temporizadas. Você pode programar um temporizador para interromper a rotina principal a cada tantos microssegundos, para executar uma outra rotina específica.
Resposta
É possível fazer multi-threading do lado do software no Uno. O threading em nível de hardware não é suportado.
Para atingir o multithreading, será necessário implementar um agendador básico e manter um processo ou lista de tarefas para rastrear as diferentes tarefas que precisam ser executadas.
A estrutura de um agendador não preemptivo muito simples seria como:
//Pseudocode void loop() { for(i=o; i<n; i++) run(tasklist[i] for timelimit): }
Aqui, tasklist
pode ser 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 realizar uma tarefa separada, como function1
realizar manipulações de LED e function2
fazer cálculos flutuantes. Será responsabilidade de cada tarefa (função) cumprir o tempo alocado a ela.
Esperançosamente, isso deve ser o suficiente para você começar.
Comentários
- Não tenho certeza se gostaria de falar sobre ” threads ” ao usar um agendador não preemptivo. A propósito, esse agendador já existe como uma biblioteca arduino: arduino.cc/en/Reference/Scheduler
- @jfpoilpret – Cooperative multithreading é uma coisa real.
- Sim, você ‘ está certo! Meu erro; fazia muito tempo que eu não enfrentava o multithreading cooperativo que, em minha mente, o multithreading tinha que ser preventivo.
Resposta
De acordo com a descrição de seus requisitos:
- um thread esperando por um dispositivo externo
- um thread piscando um LED
Parece que você poderia usar uma interrupção do Arduino para o primeiro “thread” (na verdade, prefiro chamá-la de “tarefa”).
As interrupções do Arduino podem chamar uma função (seu código) com base em um código externo evento (nível de tensão ou mudança de nível em um pino de entrada digital), que irá acionar sua função imediatamente.
No entanto, um ponto importante a se ter em mente com as interrupções é que a função chamada deve ser o mais rápido possível (normalmente, não deve haver delay()
chamada ou qualquer outra API que dependa de delay()
).
Se você tem uma tarefa longa para ativar após o gatilho de evento externo, então você poderia usar um agendador cooperativo e adicionar uma nova tarefa a ele a partir de sua função de interrupção.
Um segundo importante O ponto sobre as interrupções é que seu número é limitado (por exemplo, apenas 2 no UNO). Portanto, se você começar a ter mais eventos externos, precisará implementar algum tipo de multiplexação de todas as entradas em uma, e ter sua função de interrupção determinar qual inut multiplexado foi o gatilho real.
Resposta
Uma solução simples é usar um Scheduler . Existem várias implementações. Isso descreve brevemente aquele 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 () irá adicionar uma nova tarefa que irá executar o taskSetup uma vez e, em seguida, chamar repetidamente taskLoop assim como o O esboço do Arduino funciona. A tarefa tem sua própria pilha. O tamanho da pilha é um parâmetro opcional. O tamanho padrão da pilha é 128 bytes.
Para permitir a troca de contexto, as tarefas precisam chamar yield () ou delay ( ) . Também existe uma macro de suporte para aguardar uma condição.
await(Serial.available());
A macro é um açúcar sintático para o seguinte:
while (!(Serial.available())) yield();
O Await também pode ser usado para sincronizar tarefas. Abaixo está um snippet de exemplo:
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 obter mais detalhes, consulte os exemplos . Existem exemplos de vários LED piscando para botão de depuração e um shell simples com leitura de linha de comando sem bloqueio. Modelos e namespaces podem ser usados para ajudar a estruturar e reduzir o código-fonte. Abaixo do esboço , mostra como usar as funções de modelo para piscada múltipla. É 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(); }
Também existe um benchmark para dar uma ideia do desempenho, ou seja, tempo para iniciar tarefa, alternar contexto, etc.
Por último, existem algumas classes de suporte para sincronização e comunicação no nível de tarefa; Fila e Semáforo .
Resposta
Também cheguei a este tópico durante a implementação de um display LED de matriz.
Em uma palavra , você pode construir um agendador de votação usando a função millis () e a interrupção do temporizador no Arduino.
Sugiro os seguintes artigos de Bill Earl:
https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-2/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
Resposta
De um encantamento anterior deste fórum, a seguinte pergunta / resposta foi movida para Engenharia Elétrica. Ele tem um código de Arduino de amostra para piscar um LED usando uma interrupção do temporizador enquanto usa o loop principal para fazer IO serial.
Repostagem:
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 Timer1
dispara, a rotina de serviço de interrupção (ISR) isrBlinker()
é chamada. Ele liga / desliga o LED.
Para mostrar que outras coisas podem acontecer simultaneamente, loop()
grava repetidamente foo / bar na porta serial independente do LED piscando .
#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. 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.
Resposta
Você também pode dar uma chance à minha biblioteca ThreadHandler
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Ele usa um programador de interrupção para permitir a troca de contexto sem retransmissão yield () ou delay ().
Eu criei a biblioteca porque precisava de três threads e dois deles para rodar em um momento preciso, não importa o que os outros estivessem fazendo. O primeiro thread tratou da comunicação serial. O segundo foi rodar um filtro de Kalman usando multiplicação de matriz flutuante com a biblioteca Eigen. E o terceiro era um thread de loop de controle de corrente rápido que deveria ser capaz de interromper os cálculos da matriz.
Como funciona
Cada thread cíclico tem uma prioridade e um período. Se um thread, com prioridade mais alta do que o thread em execução atual, atinge seu próximo tempo de execução, o agendador irá pausar o thread atual e alternar para o de prioridade mais alta. Assim que o thread de alta prioridade conclui sua execução, o agendador volta para o thread anterior.
Regras de agendamento
O esquema de agendamento da biblioteca ThreadHandler é o seguinte:
- Prioridade mais alta primeiro.
- Se a prioridade for a mesma, o encadeamento com o prazo mais antigo é executado primeiro.
- Se dois encadeamentos têm o mesmo prazo, o primeiro encadeamento criado será executado primeiro.
- Um encadeamento só pode ser interrompido por encadeamentos com prioridade mais alta.
- Uma vez que um encadeamento está sendo executado, ele bloqueará a execução de todos os encadeamentos com prioridade mais baixa até que a função de execução retorne.
- A função de loop tem prioridade -128 em comparação com threads ThreadHandler.
Como usar
Threads podem ser criados via herança de 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 });
Objetos de thread se conectam automaticamente ao ThreadHandler quando são criados.
Para iniciar a execução de objetos de thread criados, chame:
ThreadHandler::getInstance()->enableThreadExecution();
Um swer
E aqui está outra biblioteca multitarefa cooperativa de microprocessador – PQRST: uma fila de prioridade para executar tarefas simples.
- Página inicial
- Documentação em nível de aula
- Repositório , com downloads e lista de problemas
Neste modelo, um thread é implementado como uma subclasse de um Task
, que é agendado para algum tempo futuro (e possivelmente reprogramado em intervalos regulares, se, como é comum, ele subclasses LoopTask
em vez). O método run()
do objeto é chamado quando a tarefa se torna devida. O método run()
faz o devido trabalho e retorna (este é o bit cooperativo); ele “manterá normalmente algum tipo de máquina de estado para gerenciar suas ações em invocações sucessivas (um exemplo trivial é a variável light_on_p_
no exemplo abaixo). Requer um ligeiro repensar de como você organize seu código, mas provou ser muito flexível e robusto em uso bastante intensivo.
É agnóstico quanto às unidades de tempo, portanto, é muito bom executar em unidades de millis()
como micros()
, ou qualquer outro tique que seja conveniente.
Aqui está o programa blink implementado usando esta biblioteca. Isso mostra apenas uma única tarefa em execução: outras tarefas normalmente seriam criadas e iniciadas em 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()); }
Comentários
- Essas são tarefas “executar até a conclusão”, certo?
- @EdgarBonet I ‘ Não tenho certeza do que você quer dizer. Depois que o método
run()
é chamado, ele não é interrompido, então tem a responsabilidade de terminar razoavelmente prontamente. Normalmente, no entanto, ele ‘ fará seu trabalho e se reprogramará (possivelmente automaticamente, no caso de uma subclasse deLoopTask
) para alguns tempo futuro. Um padrão comum é a tarefa de manter alguma máquina de estado interna (um exemplo trivial é o estadolight_on_p_
acima) para que se comporte adequadamente na próxima data prevista. - Então, sim, essas são tarefas executadas até a conclusão (RtC): nenhuma tarefa pode ser executada antes que a atual conclua sua execução retornando de
run()
. Isso está em contraste com threads cooperativos, que podem render a CPU, por exemplo, chamandoyield()
oudelay()
. Ou threads preemptivos, que podem ser programados a qualquer momento. Acho que a distinção é importante, pois tenho visto que muitas pessoas que vêm aqui procurando threads o fazem porque preferem escrever código de bloqueio em vez de máquinas de estado. Bloquear threads reais que geram a CPU não tem problema. Bloquear tarefas RtC não é. - @EdgarBonet É ‘ uma distinção útil, sim. Eu consideraria esse estilo e os fios de estilo de rendimento simplesmente como estilos diferentes de fio cooperativo, em oposição aos fios preemptivos, mas ‘ é verdade que eles exigem uma abordagem diferente para codificando-os. Seria interessante ver uma comparação cuidadosa e aprofundada das várias abordagens mencionadas aqui; uma boa biblioteca não mencionada acima é protothreads . Em ambos encontro coisas para criticar, mas também para elogiar. Eu (é claro) prefiro minha abordagem, porque parece mais explícita e não precisa de pilhas extras.
- (correção: protothreads foi mencionado, em @sachleen ‘ s resposta )