Aynı kod bloğunda birden fazla şey yapmadan programın birden fazla bölümünü birlikte çalıştırmamın bir yolu var mı?
Bir iş parçacığı, başka bir iş parçacığında bir LED'i yanıp sönerken harici bir aygıtı beklerken.
Aynı kod bloğunda birden fazla şey yapmadan programın birden fazla bölümünü birlikte çalıştırmamın bir yolu var mı?
Bir iş parçacığı, başka bir iş parçacığında bir LED'i yanıp sönerken harici bir aygıtı beklerken.
Yanıtlar:
Arduino'da çoklu süreç ya da çoklu iş parçacığı desteği yoktur. Yine de bazı yazılımlarla birden fazla iş parçacığına yakın bir şey yapabilirsiniz.
Protothreads'e bakmak istiyorsunuz :
Protothreads, küçük gömülü sistemler veya kablosuz sensör ağ düğümleri gibi ciddi oranda bellek kısıtlamalı sistemler için tasarlanmış son derece hafif yığınsız ipliklerdir. Protothreads, C'de uygulanan olaya dayalı sistemler için doğrusal kod yürütme sağlar. Protothreads, engelleyici olay işleyicileri sağlamak için temel bir işletim sistemiyle birlikte veya bunlar olmadan kullanılabilir. Protothreads karmaşık durum makineleri veya tam çoklu iş parçacığı olmadan art arda kontrol akışı sağlar.
Tabii ki, bir Arduino örnek var burada ile örnek kodu . Bu SO sorusu da faydalı olabilir.
ArduinoThread de iyi bir tanesidir.
AVR tabanlı Arduino (donanım) iş parçacığını desteklemiyor, ben ARM tabanlı Arduino'yu tanımıyorum. Bu sınırlamanın bir yolu, kesintilerin, özellikle zamanlanmış kesintilerin kullanılmasıdır. Belirli bir başka rutini çalıştırmak için her bir mikrosaniyede ana rutini kesmek için bir zamanlayıcı programlayabilirsiniz.
Uno'da yazılım tarafı çoklu iş parçacığı yapmak mümkündür. Donanım düzeyinde iş parçacığı desteklenmiyor.
Çok iş parçacığı elde etmek için, temel bir zamanlayıcının uygulanmasını ve çalıştırılması gereken farklı görevleri izlemek için bir işlem veya görev listesinin sürdürülmesini gerektirir.
Çok basit, preemptif olmayan bir programlayıcının yapısı şöyle olacaktır:
//Pseudocode
void loop()
{
for(i=o; i<n; i++)
run(tasklist[i] for timelimit):
}
Burada, tasklist
bir dizi işlev işaretçisi olabilir.
tasklist [] = {function1, function2, function3, ...}
Formun her işlevi ile:
int function1(long time_available)
{
top:
//Do short task
if (run_time<time_available)
goto top;
}
Her fonksiyon, function1
LED manipülasyonları function2
yapmak ve şamandıra hesaplamaları yapmak gibi ayrı bir görevi gerçekleştirebilir . Ayrılan zamana bağlı kalmak her bir görevin (işlevin) sorumluluğu olacaktır.
Umarım, başlamanız için bu yeterli olacaktır.
Gereksinimlerinizin tanımına göre:
İlk "thread" için bir Arduino kesmesi kullanabileceğinizi görünüyor (aslında "görev" demeyi tercih ederim).
Arduino kesmeleri, harici bir olaya (bir dijital giriş pimindeki voltaj seviyesi veya seviye değişimi) dayalı olarak bir işlevi (kodunuz) çağırabilir; bu, işlevinizi derhal tetikler.
Bununla birlikte, kesinti ile ilgili akılda tutulması gereken önemli bir nokta, çağrılan işlevin olabildiğince hızlı olması gerektiğidir (tipik olarak, herhangi bir delay()
çağrı ya da bağımlı olabilecek herhangi bir API olmamalıdır delay()
).
Harici olay tetikleyicide etkinleştirmek için uzun bir göreviniz varsa, o zaman potansiyel olarak bir işbirlikçi zamanlayıcı kullanabilir ve kesme işlevinizden yeni bir görev ekleyebilirsiniz.
Kesintilerle ilgili ikinci önemli nokta, sayılarının sınırlı olmasıdır (örneğin UNO'da sadece 2). Bu nedenle, daha fazla harici olay yaşamaya başlarsanız, tüm girdileri bir çoğullama ile bir çeşit çoğaltmaya uygulamanız ve kesme işlevinizin gerçek tetikleyicinin hangi çoğaltılmış inut olduğunu belirlemesini sağlamanız gerekir.
Basit bir çözüm bir Zamanlayıcı kullanmaktır . Birkaç uygulama var. Bu kısaca AVR ve SAM tabanlı kartlar için mevcut olanı açıklar. Temel olarak, tek bir çağrı bir görevi başlatacak; msgstr "eskiz içinde eskiz".
#include <Scheduler.h>
....
void setup()
{
...
Scheduler.start(taskSetup, taskLoop);
}
Scheduler.start (), bir kez taskSetup komutunu çalıştıracak yeni bir görev ekler ve Arduino eskizinin çalıştığı gibi tekrar tekrar taskLoop'u çağırır. Görevin kendi yığını var. Yığın boyutu isteğe bağlı bir parametredir. Varsayılan yığın boyutu 128 bayttır.
Bağlam geçişi sağlamak için görev geçişini verim () veya gecikme () olarak yapmanız gerekir . Bir koşulu beklemek için bir destek makrosu da vardır.
await(Serial.available());
Makro, aşağıdakiler için sözdizimsel şekerdir:
while (!(Serial.available())) yield();
Bekleme, görevleri senkronize etmek için de kullanılabilir. Aşağıda bir örnek pasaj var:
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);
}
Daha fazla ayrıntı için örneklere bakınız . Birden fazla LED yanıp sönme modundan çıkma düğmesine ve bloklamayan komut satırı okunan basit bir kabuğa örnekler vardır. Şablonlar ve ad alanları, kaynak kodunu yapılandırmaya ve azaltmaya yardımcı olmak için kullanılabilir. Aşağıdaki çizim , çoklu göz kırpma için şablon işlevlerinin nasıl kullanılacağını gösterir. Yığın için 64 bayt yeterlidir.
#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();
}
Bir de bulunmaktadır kıyaslama performansının bir fikir vermek için, yani zaman görevi, bağlam anahtarı, vb başlatmak için
Son olarak, görev düzeyinde senkronizasyon ve iletişim için birkaç destek sınıfı vardır; Sıra ve Semafor .
Bu forumun daha önceki tanıtımlarından, aşağıdaki soru / cevap Elektrik Mühendisliğine taşındı. Seri IO yapmak için ana döngüyü kullanırken bir zamanlayıcı kesmesi kullanarak bir LED'i yanıp sönen örnek arduino koduna sahiptir.
repost:
Kesintiler, başka bir şey devam ederken işleri halletmenin yaygın bir yoludur. Aşağıdaki örnekte, LED kullanmadan yanıp sönüyor delay()
. Ne zaman Timer1
ateş ederse, kesme servis rutini (ISR) isrBlinker()
denir. LED'i açar / kapatır.
Aynı anda başka şeylerin de olabileceğini göstermek için, loop()
art arda yanıp sönen ışıktan bağımsız olarak seri bağlantı noktasına foo / bar yazar.
#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);
}
Bu çok basit bir demo. ISR'ler çok daha karmaşık olabilir ve zamanlayıcılar ve dış olaylar (pimler) tarafından tetiklenebilir. Ortak kütüphanelerin çoğu ISR'ler kullanılarak uygulanır.
Ayrıca matris LED ekran uygularken bu konuya geldim.
Bir kelimeyle, Arduino'da millis () işlevini ve zamanlayıcı kesmesini kullanarak bir yoklama zamanlayıcısı oluşturabilirsiniz.
Bill Earl'den şu makaleleri öneririm:
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
ThreadHandler kütüphaneme de bir denemek
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Aktarım () veya gecikme () üzerine geçiş yapmadan bağlam geçişine izin vermek için bir kesme zamanlayıcısı kullanır.
Kütüphaneyi yarattım çünkü üç konuya ihtiyacım vardı ve diğerlerinin ne yaptığı önemli değil. İlk iş parçacığı seri iletişim ele aldı. İkincisi, Eigen kütüphanesi ile kayan matris çarpımını kullanan bir Kalman filtresi kullanıyordu. Üçüncüsü, matris hesaplamalarını durdurabilen hızlı bir akım kontrol döngüsü ipliği idi.
Her döngüsel iş parçacığının bir önceliği ve bir dönemi vardır. Geçerli yürütme iş parçacığından daha yüksek önceliğe sahip bir iş parçacığı bir sonraki yürütme süresine ulaşırsa, zamanlayıcı geçerli iş parçacığını duraklatır ve daha yüksek önceliğe geçer. Yüksek öncelikli iş parçacığı yürütmesini tamamladığında, zamanlayıcı önceki iş parçasına geri döner.
ThreadHandler kütüphanesinin zamanlama şeması aşağıdaki gibidir:
Konular c ++ mirası yoluyla oluşturulabilir
class MyThread : public Thread
{
public:
MyThread() : Thread(priority, period, offset){}
virtual ~MyThread(){}
virtual void run()
{
//code to run
}
};
MyThread* threadObj = new MyThread();
Veya createThread ve bir lambda işlevi aracılığıyla
Thread* myThread = createThread(priority, period, offset,
[]()
{
//code to run
});
Thread nesneleri, yaratıldıklarında ThreadHandler'a otomatik olarak bağlanır.
Oluşturulan thread nesnelerinin yürütülmesini başlatmak için:
ThreadHandler::getInstance()->enableThreadExecution();
Ve işte bir başka mikroişlemci işbirlikçi çoklu görev kütüphanesi - PQRST: Basit Görevler Çalıştırmak için bir Öncelik Kuyruğu.
Bu modelde, bir iş parçacığı, Task
ileride bir süre için programlanmış bir a'nın bir alt sınıfı olarak uygulanır (ve eğer yaygınsa, bunun LoopTask
yerine alt sınıflar varsa düzenli aralıklarla yeniden planlanır ). run()
Görev nedeniyle olduğunda nesne yöntemi olarak adlandırılır. run()
Yöntem, bazıları iş yapar ve daha sonra (bu ortak bit) döndürür; Genelde art arda yapılan çağrılardaki eylemlerini yönetmek için bir tür durum makinesini koruyacaktır (önemsiz bir light_on_p_
örnek aşağıdaki örnekteki değişkendir). Kodunuzu nasıl düzenlediğiniz konusunda biraz düşünmenizi gerektirir, ancak oldukça yoğun kullanımda çok esnek ve sağlam olduğu kanıtlanmıştır.
Zaman birimleri hakkında agnostik, bu yüzden birimlerinde çalışan olarak mutlu millis()
olarak micros()
veya uygun herhangi bir başka kene.
İşte bu kütüphaneyi kullanarak uygulanan 'göz kırpma' programı. Bu, çalışan tek bir görevi gösterir: diğer görevler genellikle oluşturulur ve içinde başlatılır 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()
Yöntem çağrıldıktan sonra kesintiye uğramaz, bu yüzden makul bir şekilde derhal bitirmek sorumluluğu vardır. Bununla birlikte, tipik olarak, işini yapacak ve daha sonra LoopTask
gelecek bir süre için kendini (muhtemelen bir alt sınıf durumunda otomatik olarak) yeniden planlayacaktır . Ortak bir düzen, görevin bazı iç durum makinelerini sürdürmesidir (önemsiz bir örnek light_on_p_
yukarıdaki durumdur), böylece bir sonraki durumdayken uygun davranır.
run()
. Bu, CPU'yu örneğin arayarak yield()
ya da arayarak verebilecek olan ortak iş parçacıklarının aksinedir delay()
. Veya herhangi bir zamanda planlanabilecek önleyici iplikler. Buradaki farkın önemli olduğunu düşünüyorum, çünkü buralarda iş arayan birçok insanın bunu yapmakta olduğunu gördüm, çünkü devlet makineleri yerine blok kod yazmayı tercih ediyorlar. İşlemciyi sağlayan gerçek dişlileri engellemek iyidir. RtC görevlerini engelleme değildir.