Bagaimana saya bisa membuat beberapa utas yang sedang berjalan?

60

Apakah ada cara saya dapat membuat beberapa bagian dari program berjalan bersama tanpa melakukan banyak hal dalam blok kode yang sama?

Satu utas menunggu perangkat eksternal sambil juga berkedip LED di utas lainnya.

Bja
sumber
3
Anda mungkin harus bertanya pada diri sendiri apakah Anda benar-benar membutuhkan utas. Pengatur waktu mungkin OK untuk kebutuhan Anda dan mereka didukung secara asli di Arduino.
jfpoilpret
1
Anda mungkin ingin memeriksa Uzebox juga. Ini adalah konsol video game homebrew dua chip. Jadi, meskipun itu bukan Arduino, seluruh sistem dibangun berdasarkan interupsi. Jadi audio, video, kontrol, dll. Semuanya didorong oleh interupsi sementara program utama tidak perlu khawatir tentang semua itu. Mungkin referensi yang bagus.
cbmeeks

Jawaban:

50

Tidak ada multi-proses, atau multi-threading, dukungan di Arduino. Anda dapat melakukan sesuatu yang dekat dengan banyak utas dengan beberapa perangkat lunak.

Anda ingin melihat Protothreads :

Protothreads adalah ulir tanpa tumpukan yang sangat ringan yang dirancang untuk sistem dengan keterbatasan memori, seperti sistem tertanam kecil atau node jaringan sensor nirkabel. Protothreads menyediakan eksekusi kode linier untuk sistem yang digerakkan oleh peristiwa yang diimplementasikan dalam C. Protothreads dapat digunakan dengan atau tanpa sistem operasi yang mendasarinya untuk menyediakan block-handler handler. Protothreads memberikan aliran kontrol berurutan tanpa mesin keadaan rumit atau multi-threading penuh.

Tentu saja, ada contoh Arduino di sini dengan kode contoh . Pertanyaan SO ini mungkin berguna juga.

ArduinoThread juga bagus.

sachleen
sumber
Perhatikan bahwa DUE Arduino memiliki pengecualian untuk ini, dengan beberapa loop kontrol: arduino.cc/en/Tutorial/MultipleBlinks
tuskiomi
18

Arduino berbasis AVR tidak mendukung threading (perangkat keras), saya tidak terbiasa dengan Arduino berbasis ARM. Salah satu cara mengatasi keterbatasan ini adalah penggunaan interupsi, terutama interupsi waktunya. Anda dapat memprogram penghitung waktu untuk menghentikan rutinitas utama setiap begitu banyak mikrodetik, untuk menjalankan rutinitas tertentu lainnya.

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

jippie
sumber
15

Dimungkinkan untuk melakukan sisi perangkat lunak multi-threading pada Uno. Threading tingkat perangkat keras tidak didukung.

Untuk mencapai multithreading, itu akan membutuhkan implementasi penjadwal dasar dan memelihara proses atau daftar tugas untuk melacak berbagai tugas yang perlu dijalankan.

Struktur penjadwal non-preemptive yang sangat sederhana adalah seperti:

//Pseudocode
void loop()
{

for(i=o; i<n; i++) 
run(tasklist[i] for timelimit):

}

Di sini, tasklistbisa menjadi array fungsi pointer.

tasklist [] = {function1, function2, function3, ...}

Dengan masing-masing fungsi formulir:

int function1(long time_available)
{
   top:
   //Do short task
   if (run_time<time_available)
   goto top;
}

Setiap fungsi dapat melakukan tugas terpisah seperti function1melakukan manipulasi LED, dan function2melakukan perhitungan float. Ini akan menjadi tanggung jawab setiap tugas (fungsi) untuk mematuhi waktu yang dialokasikan untuk itu.

Semoga ini cukup untuk Anda mulai.

asheeshr
sumber
2
Saya tidak yakin saya akan berbicara tentang "utas" ketika menggunakan penjadwal non preemptive. By the way, scheduler seperti sudah ada sebagai perpustakaan Arduino: arduino.cc/en/Reference/Scheduler
jfpoilpret
5
@ jfpoilpret - Multithreading kooperatif adalah hal yang nyata.
Connor Wolf
Ya kau benar! Kesalahanku; Sudah lama sekali saya tidak menghadapi multithreading kooperatif sehingga dalam pikiran saya, multithreading harus preemptive.
jfpoilpret
9

Sesuai deskripsi kebutuhan Anda:

  • satu utas menunggu perangkat eksternal
  • satu utas berkedip LED

Tampaknya Anda bisa menggunakan satu interupsi Arduino untuk "utas" pertama (saya lebih suka menyebutnya "tugas" sebenarnya).

Interupsi Arduino dapat memanggil satu fungsi (kode Anda) berdasarkan peristiwa eksternal (level tegangan atau perubahan level pada pin input digital), yang akan memicu fungsi Anda segera.

Namun, satu poin penting yang perlu diingat dengan interupsi adalah bahwa fungsi yang dipanggil harus secepat mungkin (biasanya, seharusnya tidak ada delay()panggilan atau API lain yang akan bergantung pada delay()).

Jika Anda memiliki tugas yang panjang untuk diaktifkan pada pemicu peristiwa eksternal, maka Anda berpotensi menggunakan penjadwal kooperatif dan menambahkan tugas baru ke sana dari fungsi interupsi Anda.

Poin penting kedua tentang interupsi adalah bahwa jumlahnya terbatas (misalnya hanya 2 pada UNO). Jadi, jika Anda mulai memiliki lebih banyak peristiwa eksternal, Anda perlu menerapkan beberapa jenis multiplexing semua input menjadi satu, dan memiliki fungsi interupsi Anda menentukan inut multiplexing apa yang merupakan pemicu yang sebenarnya.

jfpoilpret
sumber
6

Solusi sederhana adalah dengan menggunakan Penjadwal . Ada beberapa implementasi. Ini menjelaskan yang tersedia untuk papan berbasis AVR dan SAM. Pada dasarnya satu panggilan akan memulai tugas; + msgstr "sketsa di dalam sketsa".

#include <Scheduler.h>
....
void setup()
{
  ...
  Scheduler.start(taskSetup, taskLoop);
}

Scheduler.start () akan menambahkan tugas baru yang akan menjalankan taskSetup sekali dan kemudian berulang kali memanggil taskLoop seperti sketsa Arduino bekerja. Tugas ini memiliki tumpukan sendiri. Ukuran tumpukan adalah parameter opsional. Ukuran tumpukan standar adalah 128 byte.

Untuk memungkinkan pengalihan konteks tugas perlu memanggil yield () atau delay () . Ada juga makro dukungan untuk menunggu suatu kondisi.

await(Serial.available());

Makro adalah gula sintaksis untuk yang berikut:

while (!(Serial.available())) yield();

Menunggu juga dapat digunakan untuk menyinkronkan tugas. Di bawah ini adalah contoh cuplikan:

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

Untuk perincian lebih lanjut, lihat contoh - contohnya . Ada beberapa contoh dari beberapa LED berkedip ke tombol debounce dan shell sederhana dengan baris perintah non-blocking terbaca. Templat dan ruang nama dapat digunakan untuk membantu menyusun dan mengurangi kode sumber. Sketsa di bawah ini menunjukkan cara menggunakan fungsi templat untuk multi-kedip. Cukup dengan 64 byte untuk stack.

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

Ada juga tolok ukur untuk memberikan beberapa gagasan tentang kinerja, yaitu waktu untuk memulai tugas, pengalihan konteks, dll.

Terakhir, ada beberapa kelas pendukung untuk sinkronisasi dan komunikasi tingkat tugas; Antrian dan Semaphore .

Mikael Patel
sumber
3

Dari mantra sebelumnya dari forum ini, pertanyaan / jawaban berikut dipindahkan ke Teknik Elektro. Ini memiliki kode arduino sampel untuk berkedip LED menggunakan penghenti waktu saat menggunakan loop utama untuk melakukan serial IO.

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

Repost:

Interupsi adalah cara umum untuk menyelesaikan sesuatu saat sesuatu yang lain sedang terjadi. Pada contoh di bawah ini, LED berkedip tanpa menggunakan delay(). Setiap kali Timer1kebakaran, interrupt service routine (ISR) isrBlinker()dipanggil. Mengaktifkan / menonaktifkan LED.

Untuk menunjukkan bahwa hal-hal lain dapat terjadi secara bersamaan, loop()berulang kali tulis foo / bar ke port serial yang tidak tergantung pada LED yang berkedip.

#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);
}

Ini adalah demo yang sangat sederhana. ISR dapat menjadi jauh lebih kompleks dan dapat dipicu oleh timer dan peristiwa eksternal (pin). Banyak perpustakaan umum diimplementasikan menggunakan ISR.

walrii
sumber
2

Anda juga bisa mencoba pustaka ThreadHandler saya

https://bitbucket.org/adamb3_14/threadhandler/src/master/

Ini menggunakan penjadwal mengganggu untuk memungkinkan pengalihan konteks tanpa menyampaikan hasil () atau menunda ().

Saya membuat perpustakaan karena saya membutuhkan tiga utas dan saya membutuhkan dua utas untuk berjalan pada waktu yang tepat tidak peduli apa yang sedang dilakukan. Utas pertama menangani komunikasi serial. Yang kedua adalah menjalankan filter Kalman menggunakan perkalian matriks float dengan perpustakaan Eigen. Dan yang ketiga adalah thread loop kontrol arus cepat yang harus dapat mengganggu perhitungan matriks.

Bagaimana itu bekerja

Setiap utas siklik memiliki prioritas dan titik. Jika utas, dengan prioritas lebih tinggi dari utas yang saat ini dijalankan, mencapai waktu eksekusi berikutnya, penjadwal akan menjeda utas saat ini dan beralih ke utas yang lebih tinggi. Setelah utas prioritas tinggi menyelesaikan eksekusi, penjadwal beralih kembali ke utas sebelumnya.

Aturan penjadwalan

Skema penjadwalan perpustakaan ThreadHandler adalah sebagai berikut:

  1. Prioritas tertinggi terlebih dahulu.
  2. Jika prioritasnya sama maka utas dengan tenggat waktu paling awal dijalankan terlebih dahulu.
  3. Jika dua utas memiliki tenggat waktu yang sama, maka utas yang pertama akan dijalankan terlebih dahulu.
  4. Utas hanya dapat diganggu oleh utas dengan prioritas lebih tinggi.
  5. Setelah sebuah thread dieksekusi, thread tersebut akan memblokir eksekusi untuk semua utas dengan prioritas yang lebih rendah hingga fungsi run kembali.
  6. Fungsi lingkaran memiliki prioritas -128 dibandingkan dengan utas ThreadHandler.

Cara Penggunaan

Utas dapat dibuat melalui c ++ inheritance

class MyThread : public Thread
{
public:
    MyThread() : Thread(priority, period, offset){}

    virtual ~MyThread(){}

    virtual void run()
    {
        //code to run
    }
};

MyThread* threadObj = new MyThread();

Atau via createThread dan fungsi lambda

Thread* myThread = createThread(priority, period, offset,
    []()
    {
        //code to run
    });

Objek utas secara otomatis terhubung ke ThreadHandler saat dibuat.

Untuk memulai eksekusi dari objek objek panggilan:

ThreadHandler::getInstance()->enableThreadExecution();
Adam Bäckström
sumber
1

Dan inilah perpustakaan multitasking koperasi mikroprosesor lain - PQRST: Antrian Prioritas untuk Menjalankan Tugas Sederhana.

Dalam model ini, utas diimplementasikan sebagai subkelas dari a Task, yang dijadwalkan untuk beberapa waktu mendatang (dan mungkin dijadwal ulang secara berkala, jika, seperti biasa, ia mensubklasifikasikan LoopTasksebagai gantinya). The run()metode objek disebut ketika tugas menjadi jatuh tempo. The run()Metode melakukan beberapa pekerjaan karena, dan kemudian kembali (ini adalah sedikit koperasi); itu biasanya akan memelihara semacam mesin negara untuk mengelola aksinya pada pemanggilan yang berurutan (contoh yang sepele adalah light_on_p_variabel dalam contoh di bawah). Ini membutuhkan sedikit pemikiran ulang tentang bagaimana Anda mengatur kode Anda, tetapi telah terbukti sangat fleksibel dan kuat dalam penggunaan yang cukup intensif.

Agnostik tentang unit waktu, jadi sama senangnya berjalan di unit millis()seperti micros(), atau centang lainnya yang nyaman.

Berikut adalah program 'blink' yang diimplementasikan menggunakan perpustakaan ini. Ini hanya menampilkan satu tugas berjalan: tugas lain biasanya akan dibuat, dan mulai di dalam 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());
}
Norman Gray
sumber
Ini adalah tugas "run-to-completion", kan?
Edgar Bonet
@ EdgarBonet Saya tidak yakin apa yang Anda maksud. Setelah run()metode dipanggil, tidak terputus, sehingga memiliki tanggung jawab untuk menyelesaikannya dengan cepat. Namun, biasanya, ia akan melakukan tugasnya kemudian menjadwal ulang sendiri (mungkin secara otomatis, dalam kasus subkelas LoopTask) untuk beberapa waktu mendatang. Pola umum adalah untuk tugas untuk mempertahankan beberapa mesin keadaan internal (contoh sepele adalah light_on_p_keadaan di atas) sehingga berperilaku yang sesuai ketika jatuh tempo berikutnya.
Norman Grey
Jadi ya, itu adalah tugas run-to-completion (RtC): tidak ada tugas yang dapat dijalankan sebelum yang saat ini menyelesaikan eksekusi dengan kembali dari run(). Ini berbeda dengan utas kooperatif, yang dapat menghasilkan CPU dengan, misalnya, menelepon yield()atau delay(). Atau utas preemptive, yang dapat dijadwalkan keluar kapan saja. Saya merasa perbedaan itu penting, karena saya telah melihat bahwa banyak orang yang datang ke sini mencari utas melakukannya karena mereka lebih suka menulis kode pemblokiran daripada mesin negara. Memblokir utas nyata yang menghasilkan CPU baik-baik saja. Memblokir tugas RtC tidak.
Edgar Bonet
@ EdgarBonet Ini perbedaan yang bermanfaat, ya. Saya akan menganggap kedua gaya ini, dan gaya hasil-thread, sebagai gaya yang berbeda dari thread kooperatif, sebagai kebalikan dari utas preemptive, tetapi memang benar bahwa mereka memerlukan pendekatan yang berbeda untuk mengkodekannya. Akan menarik untuk melihat perbandingan yang mendalam dan mendalam dari berbagai pendekatan yang disebutkan di sini; satu perpustakaan bagus yang tidak disebutkan di atas adalah protothreads . Saya menemukan banyak hal untuk dikritik di keduanya, tetapi juga untuk memuji. Saya (tentu saja) lebih suka pendekatan saya, karena tampaknya paling eksplisit dan tidak memerlukan tumpukan tambahan.
Norman Grey
(koreksi: protothreads itu disebutkan, dalam jawaban @ sachleen ini )
Norman Gray