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.
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.
Respostas:
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.
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.
É 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, tasklist
pode 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 function1
manipulações de LED e function2
cá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.
Conforme a descrição de seus requisitos:
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.
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 .
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.
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 é Timer1
acionado, 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.
Eu também vim para esse tópico ao implementar um display LED de matriz.
Em uma palavra, você pode criar um agendador de polling usando a função millis () e a interrupção do timer 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
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.
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.
O esquema de agendamento da biblioteca ThreadHandler é o seguinte:
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();
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());
}
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.
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 é.