มีวิธีที่ฉันสามารถมีหลายส่วนของโปรแกรมทำงานร่วมกันโดยไม่ต้องทำหลายสิ่งในบล็อกรหัสเดียวกันหรือไม่
หนึ่งเธรดกำลังรออุปกรณ์ภายนอกในขณะที่ยังกะพริบไฟ LED ในเธรดอื่น
มีวิธีที่ฉันสามารถมีหลายส่วนของโปรแกรมทำงานร่วมกันโดยไม่ต้องทำหลายสิ่งในบล็อกรหัสเดียวกันหรือไม่
หนึ่งเธรดกำลังรออุปกรณ์ภายนอกในขณะที่ยังกะพริบไฟ LED ในเธรดอื่น
คำตอบ:
ไม่มีหลายกระบวนการหรือสนับสนุนหลายเธรดใน Arduino คุณสามารถทำบางสิ่งบางอย่างที่ใกล้เคียงกับหลายเธรดด้วยซอฟต์แวร์บางอย่าง
คุณต้องการดูProtothreads :
Protothreads เป็นเธรดที่ไม่มีน้ำหนักเบามากที่ออกแบบมาสำหรับระบบที่ จำกัด หน่วยความจำอย่างรุนแรงเช่นระบบฝังตัวขนาดเล็กหรือโหนดเครือข่ายเซ็นเซอร์ไร้สาย Protothreads จัดเตรียมการประมวลผลโค้ดเชิงเส้นสำหรับระบบที่ขับเคลื่อนด้วยเหตุการณ์ที่นำมาใช้ใน C. Protothreads สามารถใช้โดยมีหรือไม่มีระบบปฏิบัติการพื้นฐานเพื่อจัดเตรียมการบล็อกตัวจัดการเหตุการณ์ Protothreads จัดเตรียมการไหลต่อเนื่องของการควบคุมโดยไม่ต้องใช้เครื่องจักรรัฐที่ซับซ้อนหรือมัลติเธรดแบบเต็ม
แน่นอนว่ายังเป็นตัวอย่างที่ Arduino ที่นี่มีโค้ดตัวอย่าง คำถาม SOนี้อาจมีประโยชน์เช่นกัน
Arduino ด้ายก็เป็นสิ่งที่ดีเช่นกัน
Arduino ของ Arduino ไม่สนับสนุน (ฮาร์ดแวร์) เธรดฉันไม่คุ้นเคยกับ ARM ที่ใช้ Arduino วิธีหนึ่งในข้อ จำกัด นี้คือการใช้อินเทอร์รัปต์ คุณสามารถตั้งโปรแกรมจับเวลาเพื่อขัดจังหวะรูทีนหลักทุก ๆ ไมโครวินาทีเพื่อเรียกใช้รูทีนอื่นที่เฉพาะเจาะจง
เป็นไปได้ที่จะทำซอฟต์แวร์หลายเธรดใน Uno ไม่รองรับเธรดระดับฮาร์ดแวร์
เพื่อให้บรรลุ multithreading มันจะต้องมีการใช้งานของตัวจัดตารางเวลาขั้นพื้นฐานและการบำรุงรักษากระบวนการหรือรายการงานเพื่อติดตามงานต่าง ๆ ที่จำเป็นต้องเรียกใช้
โครงสร้างของตัวกำหนดตารางเวลาแบบไม่มีการยึดครองง่ายมากจะเป็นเช่น:
//Pseudocode
void loop()
{
for(i=o; i<n; i++)
run(tasklist[i] for timelimit):
}
ที่นี่tasklist
สามารถเป็นอาร์เรย์ของตัวชี้ฟังก์ชัน
tasklist [] = {function1, function2, function3, ...}
ด้วยฟังก์ชั่นแต่ละรูปแบบ:
int function1(long time_available)
{
top:
//Do short task
if (run_time<time_available)
goto top;
}
แต่ละฟังก์ชั่นสามารถทำงานแยกต่างหากเช่นfunction1
ทำการจัดการ LED และfunction2
ทำการคำนวณแบบลอย มันจะเป็นความรับผิดชอบของแต่ละงาน (ฟังก์ชั่น) ให้เป็นไปตามเวลาที่จัดสรรให้
หวังว่านี่จะเพียงพอสำหรับคุณในการเริ่มต้น
ตามคำอธิบายของความต้องการของคุณ:
ดูเหมือนว่าคุณสามารถใช้ Arduino หนึ่งอินเทอร์รัปต์สำหรับ "เธรด" แรก (ฉันอยากจะเรียกมันว่า "งาน" ในความเป็นจริง)
Arduino ขัดจังหวะสามารถเรียกใช้ฟังก์ชั่นหนึ่ง (รหัสของคุณ) ขึ้นอยู่กับเหตุการณ์ภายนอก (ระดับแรงดันไฟฟ้าหรือการเปลี่ยนแปลงระดับบนพินอินพุตดิจิตอล) ที่จะเรียกใช้ฟังก์ชั่นของคุณทันที
อย่างไรก็ตามจุดสำคัญอย่างหนึ่งที่ควรคำนึงถึงในการขัดจังหวะคือฟังก์ชั่นที่เรียกควรเร็วที่สุด (โดยทั่วไปไม่ควรมีการdelay()
เรียกหรือ API อื่นใดที่ขึ้นอยู่กับdelay()
)
หากคุณมีงานยาวที่จะเปิดใช้งานเมื่อทริกเกอร์เหตุการณ์ภายนอกคุณอาจใช้ตัวจัดตารางเวลาแบบมีส่วนร่วมและเพิ่มงานใหม่จากงานอินเตอร์รัปต์ของคุณ
จุดสำคัญที่สองเกี่ยวกับการขัดจังหวะคือจำนวนของพวกเขาถูก จำกัด (เช่น 2 ใน UNO เท่านั้น) ดังนั้นหากคุณเริ่มมีเหตุการณ์ภายนอกมากขึ้นคุณจะต้องใช้มัลติเพล็กซ์ทุกชนิดในอินพุตและให้ฟังก์ชันอินเทอร์รัปต์ตัดสินว่าอินพุทมัลติเพล็กใดเป็นทริกเกอร์จริง
วิธีง่ายๆคือการใช้การจัดตารางเวลา มีการใช้งานหลายอย่าง อธิบายสั้น ๆ ว่าบอร์ดนี้มีให้ใช้สำหรับบอร์ดที่ใช้ AVR และ SAM โดยพื้นฐานแล้วการโทรครั้งเดียวจะเริ่มงาน "ร่างภายในร่าง"
#include <Scheduler.h>
....
void setup()
{
...
Scheduler.start(taskSetup, taskLoop);
}
Scheduler.start () จะเพิ่มงานใหม่ที่จะเรียกใช้ taskSetup หนึ่งครั้งจากนั้นเรียก taskLoop ซ้ำ ๆ เช่นเดียวกับที่ร่าง Arduino ทำงาน ภารกิจมีสแต็กของตนเอง ขนาดของสแต็กเป็นพารามิเตอร์ที่เป็นทางเลือก ขนาดสแต็กเริ่มต้นคือ 128 ไบต์
ในการอนุญาตให้เปลี่ยนบริบทงานที่ต้องการโทรหาอัตราผลตอบแทน ()หรือล่าช้า () นอกจากนี้ยังมีแมโครสนับสนุนการรอเงื่อนไข
await(Serial.available());
แมโครคือน้ำตาลซินแทคติกสำหรับต่อไปนี้:
while (!(Serial.available())) yield();
รอยังสามารถใช้ในการประสานงาน ด้านล่างนี้เป็นตัวอย่างข้อมูล:
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);
}
สำหรับรายละเอียดเพิ่มเติมดูตัวอย่าง มีตัวอย่างจากการกะพริบหลาย LED ถึงปุ่ม debounce และเชลล์แบบง่ายพร้อมบรรทัดคำสั่งที่ไม่บล็อก เทมเพลตและเนมสเปซสามารถใช้เพื่อช่วยโครงสร้างและลดซอร์สโค้ด ด้านล่างภาพร่างแสดงวิธีการใช้ฟังก์ชั่นเทมเพลตสำหรับการกะพริบหลายจุด เพียงพอสำหรับ 64 ไบต์สำหรับสแต็ก
#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();
}
นอกจากนี้ยังมีเกณฑ์มาตรฐานเพื่อให้ความคิดเกี่ยวกับประสิทธิภาพเช่นเวลาเริ่มงานสลับบริบท ฯลฯ
สุดท้ายมีคลาสสนับสนุนเล็กน้อยสำหรับการซิงโครไนซ์ระดับงานและการสื่อสาร คิวและสัญญาณ
จากคาถาก่อนหน้าของฟอรั่มนี้คำถาม / คำตอบต่อไปนี้ถูกย้ายไปยังวิศวกรรมไฟฟ้า มันมีตัวอย่างรหัส Arduino เพื่อกระพริบไฟ LED โดยใช้ตัวจับเวลาขัดจังหวะในขณะที่ใช้วงหลักที่จะทำอนุกรม IO
repost:
การขัดจังหวะเป็นวิธีการทั่วไปในการทำสิ่งต่างๆในขณะที่สิ่งอื่นกำลังดำเนินอยู่ ในตัวอย่างด้านล่าง, LED delay()
กะพริบโดยไม่ต้องใช้ เมื่อใดก็ตามที่Timer1
เกิดไฟisrBlinker()
จะเรียกรูทีนการบริการขัดจังหวะ (ISR) มันสลับไฟ LED เปิด / ปิด
หากต้องการแสดงให้เห็นว่าสิ่งอื่น ๆ สามารถเกิดขึ้นพร้อมกันได้ให้loop()
เขียน foo / bar ไปยังพอร์ตอนุกรมที่เป็นอิสระจากไฟ LED กะพริบ
#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);
}
นี่เป็นตัวอย่างที่ง่ายมาก ISR นั้นซับซ้อนกว่ามากและสามารถถูกเรียกใช้โดยตัวจับเวลาและเหตุการณ์ภายนอก (หมุด) ไลบรารีทั่วไปจำนวนมากถูกนำไปใช้งานโดยใช้ ISR
ฉันมาที่หัวข้อนี้ในขณะที่ติดตั้งจอแสดงผล LED แบบเมทริกซ์
ในหนึ่งคำคุณอาจสร้างตัวจัดตารางเวลาการทำโพลโดยใช้ฟังก์ชั่น millis () และตัวขัดจังหวะตัวจับเวลาใน Arduino
ฉันแนะนำบทความต่อไปนี้จาก 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
คุณสามารถลองใช้ ThreadHandler library ของฉันได้
https://bitbucket.org/adamb3_14/threadhandler/src/master/
มันใช้ตัวกำหนดตารางเวลาการขัดจังหวะเพื่อให้การสลับบริบทโดยไม่มีการส่งต่อผลผลิต () หรือความล่าช้า ()
ฉันสร้างห้องสมุดเพราะฉันต้องการสามเธรดและฉันต้องการสองเธรดในเวลาที่แม่นยำไม่ว่าคนอื่น ๆ จะทำอะไร เธรดแรกจัดการการสื่อสารแบบอนุกรม อย่างที่สองคือเรียกใช้ตัวกรองคาลมานโดยใช้การคูณเมทริกซ์ลอยกับห้องสมุด Eigen และอันที่สามคือเธรดลูปควบคุมปัจจุบันที่รวดเร็วซึ่งจะต้องสามารถขัดจังหวะการคำนวณเมทริกซ์
แต่ละรอบด้ายมีความสำคัญและระยะเวลา หากเธรดที่มีลำดับความสำคัญสูงกว่าเธรดที่กำลังดำเนินการอยู่ในปัจจุบันถึงเวลาดำเนินการถัดไปของตัวกำหนดเวลาจะหยุดเธรดปัจจุบันและสลับไปที่ลำดับความสำคัญที่สูงกว่า เมื่อเธรดที่มีลำดับความสำคัญสูงเสร็จสิ้นการดำเนินการตัวกำหนดตารางเวลาจะสลับกลับไปที่เธรดก่อนหน้า
โครงร่างการกำหนดตารางเวลาของไลบรารี ThreadHandler มีดังนี้:
สามารถสร้างเธรดผ่านการสืบทอด c ++
class MyThread : public Thread
{
public:
MyThread() : Thread(priority, period, offset){}
virtual ~MyThread(){}
virtual void run()
{
//code to run
}
};
MyThread* threadObj = new MyThread();
หรือผ่าน createThread และฟังก์ชั่นแลมบ์ดา
Thread* myThread = createThread(priority, period, offset,
[]()
{
//code to run
});
วัตถุของเธรดจะเชื่อมต่อกับ ThreadHandler โดยอัตโนมัติเมื่อสร้างขึ้น
ในการเริ่มต้นการดำเนินการของการเรียกวัตถุเธรดที่สร้างขึ้น:
ThreadHandler::getInstance()->enableThreadExecution();
และนี่ก็เป็นอีกหนึ่งไลบรารีมัลติทาสกิ้งที่ทำงานร่วมกันหลายตัวประมวลผลร่วม - PQRST: คิวลำดับความสำคัญสำหรับการเรียกใช้งานง่าย
ในรูปแบบนี้ด้ายจะดำเนินการเป็น subclass ของที่Task
ซึ่งมีกำหนดสำหรับบางเวลาในอนาคต (และอาจจะนัดในช่วงเวลาปกติถ้าราวกับเป็นเรื่องธรรมดามันคลาสย่อยLoopTask
แทน) run()
วิธีการของวัตถุที่เรียกว่าเมื่องานกลายเป็นเนื่องจาก run()
วิธีการทำงานเนื่องจากบางส่วนและจากนั้นส่งกลับ (นี้เป็นบิตสหกรณ์); โดยทั่วไปมันจะบำรุงรักษาเครื่องสถานะบางอย่างเพื่อจัดการการกระทำของตนในการเรียกใช้แบบต่อเนื่อง (ตัวอย่างเล็กน้อยคือlight_on_p_
ตัวแปรในตัวอย่างด้านล่าง) มันต้องคิดใหม่เล็กน้อยเกี่ยวกับวิธีที่คุณจัดระเบียบรหัสของคุณ แต่ได้พิสูจน์แล้วว่ามีความยืดหยุ่นและทนทานในการใช้งานที่ค่อนข้างเข้มข้น
มันไม่เชื่อเรื่องพระเจ้าเกี่ยวกับหน่วยเวลาดังนั้นมันจึงมีความสุขในการวิ่งในหน่วยที่millis()
เป็นmicros()
หรือเห็บอื่น ๆ ที่สะดวก
นี่คือโปรแกรม 'กะพริบ' ที่ใช้งานโดยใช้ไลบรารีนี้ นี้แสดงให้เห็นเพียงเป็นงานที่ทำงานเดียว: งานอื่น ๆ 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()
เรียกใช้วิธีการดังกล่าวจะไม่ถูกขัดจังหวะดังนั้นจึงมีความรับผิดชอบในการทำให้เสร็จอย่างสมเหตุสมผลโดยทันที อย่างไรก็ตามโดยทั่วไปแล้วมันจะทำงานตามกำหนดเวลาใหม่อีกครั้ง (อาจเป็นโดยอัตโนมัติในกรณีของคลาสย่อยLoopTask
) ในอนาคต รูปแบบทั่วไปสำหรับงานเพื่อรักษาเครื่องสถานะภายในบางอย่าง (ตัวอย่างเล็ก ๆ น้อย ๆ คือlight_on_p_
สถานะด้านบน) เพื่อให้ทำงานได้อย่างเหมาะสมเมื่อถึงกำหนดถัดไป
run()
งานสามารถเรียกใช้ก่อนที่หนึ่งในปัจจุบันไม่มีการดำเนินการเสร็จสมบูรณ์โดยการกลับมาจาก นี้เป็นในทางตรงกันข้ามกับหัวข้อสหกรณ์ที่สามารถให้ผลผลิตของ CPU โดยการเช่นการเรียกหรือyield()
delay()
หรือกระทู้ยึดเอาเสียก่อนที่สามารถกำหนดออกได้ตลอดเวลา ฉันรู้สึกแตกต่างเป็นสิ่งสำคัญเนื่องจากฉันเห็นว่ามีหลายคนที่มาที่นี่เพื่อค้นหาเธรดทำเช่นนั้นเพราะพวกเขาชอบเขียนโค้ดบล็อกมากกว่าเครื่องของรัฐ การบล็อกเธรดจริงที่ให้ CPU ทำงานได้ดี การบล็อกงาน RtC ไม่ใช่