Làm thế nào tôi có thể tạo nhiều chủ đề đang chạy?


60

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.


3
Trước tiên bạn có thể nên tự hỏi nếu bạn thực sự cần chủ đề. Bộ hẹn giờ có thể ổn với nhu cầu của bạn và chúng được hỗ trợ trên Arduino.
jfpoilpret

1
Bạn có thể muốn kiểm tra Uzebox quá. Đó là một giao diện điều khiển trò chơi video hai chip homebrew. Vì vậy, trong khi nó không chính xác là một Arduino, toàn bộ hệ thống được xây dựng trên các ngắt. Vì vậy, âm thanh, video, điều khiển, v.v ... đều bị gián đoạn điều khiển trong khi chương trình chính không phải lo lắng về bất kỳ vấn đề nào. Có thể là tài liệu tham khảo tốt.
cbmeek

Câu trả lời:


50

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.


Lưu ý rằng Arduino DUE có một ngoại lệ cho điều này, với nhiều vòng điều khiển: arduino.cc/en/Tutorial/Mult MônBlinks
tuskiomi

18

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.

http://arduino.cc/en/Reference/Interrupts


15

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, tasklistcó 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ư function1thực hiện các thao tác LED và function2thự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.


2
Tôi không chắc chắn tôi sẽ nói về "chủ đề" khi sử dụng một trình lập lịch không ưu tiên. Nhân tiện, một trình lập lịch như vậy đã tồn tại như một thư viện arduino: arduino.cc/en/Reference/Scheduler
jfpoilpret 18/214

5
@jfpoilpret - Đa luồng hợp tác là một điều có thật.
Sói Connor

Vâng bạn đã đúng! Lỗi của tôi; đã rất lâu rồi tôi không phải đối mặt với đa luồng hợp tác mà trong tâm trí tôi, đa luồng phải được ưu tiên.
jfpoilpret

9

Theo mô tả các yêu cầu của bạn:

  • một luồng đang chờ thiết bị bên ngoài
  • một chủ đề nhấp nháy đèn LED

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ế.


6

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àngSemaphore .


3

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.

https://electronics.stackexchange.com/questions/67089/how-can-i-control-things-without-USE-delay/67091#67091

Đă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 Timer1chá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.


3

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


2

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.

Làm thế nào nó hoạt động

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 đó.

Quy tắc lập lịch

Sơ đồ lập lịch của thư viện ThreadHandler như sau:

  1. Ưu tiên cao nhất trước tiên.
  2. Nếu mức độ ưu tiên là như nhau thì luồng có thời hạn sớm nhất sẽ được thực hiện trước.
  3. Nếu hai luồng có cùng thời hạn thì luồng được tạo đầu tiên sẽ thực thi trước.
  4. Một chủ đề chỉ có thể bị gián đoạn bởi các chủ đề có mức độ ưu tiên cao hơn.
  5. Khi một luồng đang thực thi, nó sẽ chặn thực thi đối với tất cả các luồng có mức ưu tiên thấp hơn cho đến khi hàm chạy trở lại.
  6. Hàm loop có mức độ ưu tiên -128 so với các luồng của ThreadHandler.

Cách sử dụng

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();

1

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ó LoopTaskthay 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());
}

Đây là những nhiệm vụ của run-to-hoàn thành, phải không?
Edgar Bonet

@EdgarBonet Tôi không chắc ý của bạn là gì. Sau khi 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.
Norman Gray

Vì vậy, có, đó là những nhiệm vụ chạy đến hoàn thành (RtC): không có tác vụ nào có thể chạy trước khi tác vụ hiện tại hoàn thành việc thực hiện bằng cách quay trở lại 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.
Edgar Bonet

@EdgarBonet Đó là một sự khác biệt hữu ích, vâng. Tôi sẽ coi cả hai kiểu này và các luồng kiểu năng suất, đơn giản là các kiểu khác nhau của luồng hợp tác, trái ngược với các luồng được ưu tiên, nhưng đúng là chúng yêu cầu một cách tiếp cận khác để mã hóa chúng. Sẽ rất thú vị khi thấy một so sánh sâu sắc và sâu sắc về các phương pháp khác nhau được đề cập ở đây; một thư viện đẹp không được đề cập ở trên là protothreads . Tôi tìm thấy những điều để chỉ trích trong cả hai, nhưng cũng để khen ngợi. Tôi (tất nhiên) thích cách tiếp cận của tôi, bởi vì nó có vẻ rõ ràng nhất và không cần thêm ngăn xếp.
Norman Gray

(chỉnh sửa: protothreads đã được đề cập, trong câu trả lời của @ sachleen )
Norman Gray
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.