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.
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.
Jawaban:
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.
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.
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, tasklist
bisa 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 function1
melakukan manipulasi LED, dan function2
melakukan perhitungan float. Ini akan menjadi tanggung jawab setiap tugas (fungsi) untuk mematuhi waktu yang dialokasikan untuk itu.
Semoga ini cukup untuk Anda mulai.
Sesuai deskripsi kebutuhan Anda:
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.
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 .
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.
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 Timer1
kebakaran, 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.
Saya juga membahas topik ini sambil menerapkan tampilan LED matriks.
Dalam satu kata, Anda dapat membuat penjadwal pemungutan suara dengan menggunakan fungsi millis () dan penghenti waktu di Arduino.
Saya menyarankan artikel-artikel berikut dari 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
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.
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.
Skema penjadwalan perpustakaan ThreadHandler adalah sebagai berikut:
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();
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 LoopTask
sebagai 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());
}
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.
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.