Có cách nào để tôi có thể có nhiều phần của chương trình chạy cùng nhau mà không phải thực hiện nhiều thứ trong cùng một khối mã không?
Một luồng đang chờ một thiết bị bên ngoài đồng thời nhấp nháy đèn LED trong một luồng khác.
Có cách nào để tôi có thể có nhiều phần của chương trình chạy cùng nhau mà không phải thực hiện nhiều thứ trong cùng một khối mã không?
Một luồng đang chờ một thiết bị bên ngoài đồng thời nhấp nháy đèn LED trong một luồng khác.
Câu trả lời:
Không có đa tiến trình, cũng không đa luồng, hỗ trợ trên Arduino. Bạn có thể làm một cái gì đó gần với nhiều chủ đề với một số phần mềm.
Bạn muốn xem Protothreads :
Protothreads là các luồng không xếp chồng cực kỳ nhẹ được thiết kế cho các hệ thống bị hạn chế bộ nhớ nghiêm trọng, chẳng hạn như các hệ thống nhúng nhỏ hoặc các nút mạng cảm biến không dây. Protothreads cung cấp thực thi mã tuyến tính cho các hệ thống hướng sự kiện được triển khai trong C. Protothreads có thể được sử dụng có hoặc không có hệ điều hành bên dưới để cung cấp các trình xử lý sự kiện. Protothreads cung cấp luồng điều khiển tuần tự mà không cần máy trạng thái phức tạp hoặc đa luồng đầy đủ.
Tất nhiên, có một ví dụ Arduino ở đây với mã ví dụ . Câu hỏi SO này cũng có thể hữu ích.
ArduinoThread cũng là một trong những tốt.
Arduino dựa trên Arduino không hỗ trợ luồng (phần cứng), tôi không quen với Arduino dựa trên ARM. Một cách xung quanh giới hạn này là việc sử dụng các ngắt, đặc biệt là các ngắt theo thời gian. Bạn có thể lập trình một bộ đếm thời gian để làm gián đoạn thói quen chính cứ sau rất nhiều micro giây, để chạy một thói quen cụ thể khác.
Có thể thực hiện đa luồng bên phần mềm trên Uno. Luồng cấp độ phần cứng không được hỗ trợ.
Để đạt được đa luồng, nó sẽ yêu cầu thực hiện một bộ lập lịch cơ bản và duy trì một danh sách quy trình hoặc nhiệm vụ để theo dõi các nhiệm vụ khác nhau cần được chạy.
Cấu trúc của một trình lập lịch không ưu tiên rất đơn giản sẽ như sau:
//Pseudocode
void loop()
{
for(i=o; i<n; i++)
run(tasklist[i] for timelimit):
}
Ở đây, tasklist
có thể là một mảng các con trỏ hàm.
tasklist [] = {function1, function2, function3, ...}
Với mỗi chức năng của mẫu:
int function1(long time_available)
{
top:
//Do short task
if (run_time<time_available)
goto top;
}
Mỗi chức năng có thể thực hiện một nhiệm vụ riêng biệt như function1
thực hiện các thao tác LED và function2
thực hiện các phép tính nổi. Trách nhiệm của mỗi nhiệm vụ (chức năng) là tuân thủ thời gian được phân bổ cho nó.
Hy vọng, điều này là đủ để bạn bắt đầu.
Theo mô tả các yêu cầu của bạn:
Có vẻ như bạn có thể sử dụng một ngắt Arduino cho "luồng" đầu tiên (thực tế tôi muốn gọi nó là "nhiệm vụ").
Ngắt Arduino có thể gọi một chức năng (mã của bạn) dựa trên một sự kiện bên ngoài (mức điện áp hoặc thay đổi cấp độ trên chân đầu vào kỹ thuật số), điều này sẽ kích hoạt chức năng của bạn ngay lập tức.
Tuy nhiên, một điểm quan trọng cần lưu ý với các ngắt là hàm được gọi phải càng nhanh càng tốt (thông thường, sẽ không có delay()
cuộc gọi hoặc bất kỳ API nào khác phụ thuộc vào delay()
).
Nếu bạn có một nhiệm vụ dài để kích hoạt khi kích hoạt sự kiện bên ngoài, thì bạn có khả năng có thể sử dụng một lịch trình hợp tác và thêm một nhiệm vụ mới từ chức năng ngắt của bạn.
Một điểm quan trọng thứ hai về các ngắt là số lượng của chúng bị giới hạn (ví dụ: chỉ có 2 trên UNO). Vì vậy, nếu bạn bắt đầu có nhiều sự kiện bên ngoài hơn, bạn sẽ cần phải thực hiện một số loại ghép kênh tất cả các đầu vào thành một, và có chức năng ngắt của bạn xác định inut đa kênh nào là kích hoạt thực tế.
Một giải pháp đơn giản là sử dụng Trình lập lịch biểu . Có một số triển khai. Điều này mô tả ngắn gọn một cái có sẵn cho các bảng dựa trên AVR và SAM. Về cơ bản một cuộc gọi sẽ bắt đầu một nhiệm vụ; "phác họa trong một bản phác thảo".
#include <Scheduler.h>
....
void setup()
{
...
Scheduler.start(taskSetup, taskLoop);
}
Routuler.start () sẽ thêm một tác vụ mới sẽ chạy tasksetup một lần và sau đó liên tục gọi taskLoop giống như bản phác thảo Arduino hoạt động. Nhiệm vụ có ngăn xếp riêng của nó. Kích thước của ngăn xếp là một tham số tùy chọn. Kích thước ngăn xếp mặc định là 128 byte.
Để cho phép chuyển đổi ngữ cảnh, các tác vụ cần gọi hàm () hoặc delay () . Ngoài ra còn có một macro hỗ trợ để chờ đợi một điều kiện.
await(Serial.available());
Macro là cú pháp đường cho sau:
while (!(Serial.available())) yield();
Chờ đợi cũng có thể được sử dụng để đồng bộ hóa các nhiệm vụ. Dưới đây là một đoạn ví dụ:
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);
}
Để biết thêm chi tiết xem các ví dụ . Có các ví dụ từ nhiều đèn LED nhấp nháy đến nút gỡ lỗi và một vỏ đơn giản với dòng lệnh không chặn được đọc. Mẫu và không gian tên có thể được sử dụng để giúp cấu trúc và giảm mã nguồn. Dưới đây phác họa cho thấy làm thế nào để sử dụng các chức năng mẫu cho nhiều nháy mắt. Nó là đủ với 64 byte cho ngăn xếp.
#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();
}
Ngoài ra còn có một điểm chuẩn để đưa ra một số ý tưởng về hiệu suất, tức là thời gian để bắt đầu nhiệm vụ, chuyển đổi ngữ cảnh, v.v.
Cuối cùng, có một vài lớp hỗ trợ để đồng bộ hóa và giao tiếp ở cấp độ nhiệm vụ; Xếp hàng và Semaphore .
Từ một câu thần chú trước đây của diễn đàn này, câu hỏi / câu trả lời sau đã được chuyển sang Kỹ thuật điện. Nó có mã arduino mẫu để nhấp nháy đèn LED bằng cách sử dụng ngắt hẹn giờ trong khi sử dụng vòng lặp chính để thực hiện IO nối tiếp.
Đăng lại:
Ngắt là một cách phổ biến để hoàn thành công việc trong khi một cái gì đó khác đang diễn ra. Trong ví dụ dưới đây, đèn LED nhấp nháy mà không sử dụng delay()
. Bất cứ khi nào Timer1
cháy, thói quen dịch vụ ngắt (ISR) isrBlinker()
được gọi. Nó bật / tắt đèn LED.
Để chỉ ra rằng những thứ khác có thể xảy ra đồng thời, loop()
liên tục ghi foo / bar vào cổng nối tiếp độc lập với đèn LED nhấp nháy.
#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);
}
Đây là một bản demo rất đơn giản. ISR có thể phức tạp hơn nhiều và có thể được kích hoạt bởi bộ định thời và các sự kiện bên ngoài (chân). Nhiều thư viện phổ biến được triển khai bằng ISR.
Tôi cũng đã đến chủ đề này trong khi thực hiện một màn hình LED ma trận.
Trong một từ, bạn có thể xây dựng một lịch trình bỏ phiếu bằng cách sử dụng hàm millis () và ngắt hẹn giờ trong Arduino.
Tôi đề nghị các bài viết sau từ Bill Earl:
https://learn.adafbean.com/multi-tasking-the-arduino-part-1/overview
https://learn.adafbean.com/multi-tasking-the-arduino-part-2/overview
https://learn.adafbean.com/multi-tasking-the-arduino-part-3/overview
Bạn cũng có thể thử thư viện ThreadHandler của tôi
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Nó sử dụng một bộ lập lịch gián đoạn để cho phép chuyển đổi ngữ cảnh mà không cần chuyển tiếp trênield () hoặc delay ().
Tôi đã tạo thư viện vì tôi cần ba luồng và tôi cần hai trong số chúng chạy vào một thời điểm chính xác cho dù những người khác đang làm gì. Các chủ đề đầu tiên xử lý giao tiếp nối tiếp. Thứ hai là chạy bộ lọc Kalman bằng cách nhân ma trận float với thư viện Eigen. Và thứ ba là một chuỗi vòng điều khiển hiện tại nhanh, có thể làm gián đoạn các phép tính ma trận.
Mỗi luồng tuần hoàn có một ưu tiên và một khoảng thời gian. Nếu một luồng, với mức độ ưu tiên cao hơn luồng thực thi hiện tại, đạt đến thời gian thực hiện tiếp theo của nó, bộ lập lịch sẽ tạm dừng luồng hiện tại và chuyển sang mức ưu tiên cao hơn. Khi luồng ưu tiên cao hoàn thành việc thực hiện, trình lập lịch sẽ quay trở lại luồng trước đó.
Sơ đồ lập lịch của thư viện ThreadHandler như sau:
Chủ đề có thể được tạo thông qua thừa kế c ++
class MyThread : public Thread
{
public:
MyThread() : Thread(priority, period, offset){}
virtual ~MyThread(){}
virtual void run()
{
//code to run
}
};
MyThread* threadObj = new MyThread();
Hoặc thông qua createThread và chức năng lambda
Thread* myThread = createThread(priority, period, offset,
[]()
{
//code to run
});
Các đối tượng luồng tự động kết nối với ThreadHandler khi chúng được tạo.
Để bắt đầu thực hiện các đối tượng luồng đã tạo, gọi:
ThreadHandler::getInstance()->enableThreadExecution();
Và đây là một thư viện đa nhiệm hợp tác vi xử lý khác - PQRST: Hàng đợi ưu tiên để chạy các tác vụ đơn giản.
Trong mô hình này, một luồng được triển khai như là một lớp con của a Task
, được lên kế hoạch cho một thời gian trong tương lai (và có thể được lên lịch lại theo các khoảng thời gian đều đặn, nếu, như thường thấy, nó LoopTask
thay vào đó là các lớp con ). Các run()
phương pháp của đối tượng được gọi khi nhiệm vụ trở nên do. Các run()
phương pháp thực hiện một số công việc do, và sau đó trả về (đây là chút hợp tác xã); thông thường nó sẽ duy trì một số loại máy trạng thái để quản lý các hành động của nó trên các lệnh liên tiếp (một ví dụ tầm thường là light_on_p_
biến trong ví dụ bên dưới). Nó đòi hỏi phải xem xét lại một chút về cách bạn tổ chức mã của mình, nhưng đã được chứng minh rất linh hoạt và mạnh mẽ trong việc sử dụng khá chuyên sâu.
Đó là bất khả tri về các đơn vị thời gian, vì vậy thật hạnh phúc khi chạy theo các đơn vị millis()
như micros()
, hoặc bất kỳ đánh dấu nào thuận tiện.
Đây là chương trình 'nháy mắt' được triển khai bằng thư viện này. Điều này chỉ hiển thị một tác vụ duy nhất đang chạy: các tác vụ khác thường được tạo và bắt đầu bên trong 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()
phương thức được gọi, nó không bị gián đoạn, vì vậy nó có trách nhiệm hoàn thành hợp lý kịp thời. Thông thường, tuy nhiên, nó sẽ thực hiện công việc của mình sau đó tự lên lịch lại (có thể tự động, trong trường hợp là một lớp con LoopTask
) trong một thời gian trong tương lai. Một mô hình phổ biến là cho nhiệm vụ duy trì một số máy trạng thái bên trong (một ví dụ tầm thường là light_on_p_
trạng thái ở trên) để nó hoạt động phù hợp khi đến hạn.
run()
. Điều này trái ngược với các luồng hợp tác, có thể mang lại CPU bằng cách, ví dụ như gọi yield()
hoặc delay()
. Hoặc chủ đề ưu tiên, có thể được lên lịch bất cứ lúc nào. Tôi cảm thấy sự khác biệt rất quan trọng, vì tôi đã thấy rằng nhiều người đến đây tìm kiếm các chủ đề làm như vậy bởi vì họ thích viết mã chặn hơn là các máy trạng thái. Chặn các luồng thực sự mang lại CPU là tốt. Chặn các nhiệm vụ RtC thì không.