Multitasking penting hari ini. Saya bertanya-tanya bagaimana kita bisa mencapainya dalam mikrokontroler dan pemrograman tertanam. Saya merancang sistem yang didasarkan pada mikrokontroler PIC. Saya telah merancang firmware-nya dalam MplabX IDE menggunakan C dan kemudian merancang aplikasi untuk itu di Visual Studio menggunakan C #.
Karena saya sudah terbiasa menggunakan utas dalam pemrograman C # di desktop untuk mengimplementasikan tugas paralel, apakah ada cara untuk melakukan hal yang sama dalam kode mikrokontroler saya? MplabX IDE menyediakan pthreads.h
tetapi itu hanya sebuah rintisan tanpa implementasi. Saya tahu ada dukungan FreeRTOS tetapi menggunakannya yang membuat kode Anda lebih kompleks. Beberapa forum mengatakan bahwa interupsi juga dapat digunakan sebagai multi tasking tapi saya tidak berpikir interupsi setara dengan utas.
Saya merancang sistem yang mengirimkan beberapa data ke UART dan pada saat yang sama ia perlu mengirim data ke situs web melalui ethernet (kabel). Seorang pengguna dapat mengontrol output melalui situs web tetapi outputnya HIDUP / MATI dengan penundaan 2-3 detik. Jadi itulah masalah yang saya hadapi. Apakah ada solusi untuk multi tasking di mikrokontroler?
sumber
Jawaban:
Ada dua jenis utama sistem operasi multitasking, preemptive dan kooperatif. Keduanya memungkinkan beberapa tugas untuk didefinisikan dalam sistem, perbedaannya adalah bagaimana pengalihan tugas bekerja. Tentu saja dengan prosesor inti tunggal, hanya satu tugas yang benar-benar berjalan pada suatu waktu.
Kedua jenis OS multitasking ini membutuhkan tumpukan terpisah untuk setiap tugas. Jadi ini menyiratkan dua hal: pertama, bahwa prosesor memungkinkan tumpukan untuk ditempatkan di mana saja di RAM dan karena itu memiliki instruksi untuk memindahkan penunjuk tumpukan (SP) di sekitar - yaitu tidak ada tumpukan perangkat keras tujuan khusus seperti yang ada pada low-end PIC. Ini membuat PIC10, 12 dan 16 seri.
Anda dapat menulis OS hampir seluruhnya dalam bahasa C, tetapi pengalih tugas, tempat SP bergerak harus berada dalam perakitan. Di berbagai waktu saya telah menulis pengalih tugas untuk PIC24, PIC32, 8051, dan 80x86. Nyali semuanya sangat berbeda tergantung pada arsitektur prosesor.
Persyaratan kedua adalah bahwa ada cukup RAM untuk menyediakan banyak tumpukan. Biasanya satu ingin setidaknya beberapa ratus byte untuk tumpukan; tetapi bahkan hanya dengan 128 byte per tugas, delapan tumpukan akan membutuhkan 1K byte RAM - Anda tidak harus mengalokasikan tumpukan ukuran yang sama untuk setiap tugas. Ingat Anda membutuhkan tumpukan yang cukup untuk menangani tugas saat ini, dan semua panggilan ke subrutin bersarangnya, tetapi juga tumpukan ruang untuk panggilan interupsi karena Anda tidak pernah tahu kapan akan terjadi.
Ada metode yang cukup sederhana untuk menentukan berapa banyak tumpukan yang Anda gunakan untuk setiap tugas; misalnya Anda dapat menginisialisasi semua tumpukan ke nilai tertentu, katakan 0x55, dan jalankan sistem untuk sementara waktu lalu berhenti dan periksa memori.
Anda tidak mengatakan PIC seperti apa yang ingin Anda gunakan. Kebanyakan PIC24 dan PIC32 akan memiliki banyak ruang untuk menjalankan OS multitasking; PIC18 (satu-satunya PIC 8-bit yang memiliki tumpukan dalam RAM) memiliki ukuran RAM maksimum 4K. Jadi itu sangat rapuh.
Dengan multitasking kooperatif (yang lebih sederhana dari keduanya), pengalihan tugas hanya dilakukan ketika tugas "menyerahkan" kontrolnya kembali ke OS. Ini terjadi setiap kali tugas perlu memanggil rutin OS untuk melakukan beberapa fungsi yang akan ditunggu, seperti permintaan I / O atau panggilan waktu. Ini membuatnya lebih mudah bagi OS untuk berganti tumpukan, karena tidak perlu untuk menyimpan semua register dan informasi negara, SP hanya dapat dialihkan ke tugas lain (jika tidak ada tugas lain yang siap dijalankan, tumpukan menganggur adalah diberikan kontrol). Jika tugas saat ini tidak perlu membuat panggilan OS tetapi telah berjalan untuk sementara waktu, itu perlu menyerahkan kontrol secara sukarela untuk menjaga sistem responsif.
Masalah dengan multitasking kooperatif adalah jika tugas tersebut tidak pernah menyerah kontrol, itu dapat merusak sistem. Hanya itu dan rutinitas interupsi apa pun yang kebetulan diberikan kontrol yang dapat berjalan, sehingga OS tampaknya akan terkunci. Ini adalah aspek "kooperatif" dari sistem ini. Jika pengawas waktu diimplementasikan yang hanya mengatur ulang ketika switch tugas dilakukan, maka dimungkinkan untuk menangkap tugas-tugas yang salah ini.
Windows 3.1 dan sebelumnya adalah sistem operasi kooperatif, yang sebagian mengapa kinerja mereka tidak begitu bagus.
Preemptive multitasking lebih sulit untuk diterapkan. Di sini, tugas tidak diharuskan untuk menyerahkan kontrol secara manual, tetapi sebaliknya setiap tugas dapat diberikan jumlah maksimum waktu untuk menjalankan (katakanlah 10 ms), dan kemudian sakelar tugas dilakukan ke tugas yang dapat dijalankan berikutnya jika ada. Ini mengharuskan penghentian tugas secara sewenang-wenang, menyimpan semua informasi status, dan kemudian mengalihkan SP ke tugas lain dan memulainya. Ini membuat pengalih tugas lebih rumit, membutuhkan lebih banyak tumpukan, dan memperlambat sistem sedikit.
Untuk multitasking kooperatif dan preemptive, interupsi dapat terjadi kapan saja yang sementara akan mencegah tugas berjalan.
Seperti yang ditunjukkan supercat dalam komentar, satu kelebihan yang dimiliki multitasking koperasi adalah lebih mudah untuk berbagi sumber daya (mis. Perangkat keras seperti ADC multi-saluran atau perangkat lunak seperti memodifikasi daftar yang ditautkan). Terkadang dua tugas menginginkan akses ke sumber daya yang sama pada saat yang sama. Dengan penjadwalan preemptive, OS dimungkinkan untuk berpindah tugas di tengah satu tugas menggunakan sumber daya. Jadi kunci diperlukan untuk mencegah tugas lain masuk dan mengakses sumber daya yang sama. Dengan multitasking kooperatif, ini tidak perlu karena tugas mengontrol kapan akan melepaskannya kembali ke OS.
sumber
void foo(void* context)
- fungsi logika pengontrol (kernel) menarik satu penunjuk dan sepasang penunjuk fungsi dari antrian dan menyebutnya satu per satu. Fungsi itu menggunakan konteks untuk menyimpan variabel dan semacamnya dan kemudian dapat menambahkan mengirimkan kelanjutan ke antrian. Fungsi-fungsi itu harus kembali dengan cepat agar tugas-tugas lain ada di CPU. Ini adalah metode berbasis peristiwa yang hanya membutuhkan satu tumpukan.Threading disediakan oleh sistem operasi. Di dunia tertanam kami biasanya tidak memiliki OS ("bare metal"). Jadi ini meninggalkan opsi berikut:
Saya akan menyarankan Anda menggunakan skema paling sederhana di atas yang akan berfungsi untuk aplikasi Anda. Dari apa yang Anda jelaskan, saya akan memiliki paket penghasil loop utama dan menempatkannya dalam buffer bundar. Kemudian minta driver berbasis UART ISR yang menyala setiap kali byte sebelumnya selesai mengirim hingga buffer dikirim, kemudian menunggu konten buffer yang lebih banyak. Pendekatan serupa untuk ethernet.
sumber
Seperti pada prosesor single-core yang melakukan multitasking perangkat lunak nyata tidak mungkin. Jadi, Anda harus berhati-hati untuk beralih di antara beberapa tugas satu arah. RTOS yang berbeda menangani itu. Mereka memiliki penjadwal dan berdasarkan pada centang sistem mereka akan beralih di antara tugas yang berbeda untuk memberi Anda kemampuan multitasking.
Konsep-konsep yang terlibat dalam melakukannya (menyimpan dan memulihkan konteks) cukup rumit, jadi melakukan ini secara manual mungkin akan sulit dan membuat kode Anda lebih kompleks dan karena Anda belum pernah melakukannya sebelumnya, akan ada kesalahan di dalamnya. Saran saya di sini adalah menggunakan RTOS yang diuji seperti FreeRTOS.
Anda menyebutkan bahwa interupsi memberikan tingkat multitasking. Ini agak benar. Interrupt akan mengganggu program Anda saat ini di titik mana pun dan mengeksekusi kode di sana, itu sebanding dengan dua sistem tugas di mana Anda memiliki 1 tugas dengan prioritas rendah dan lainnya dengan prioritas tinggi yang selesai dalam satu irisan waktu penjadwal.
Jadi Anda bisa menulis interrupt handler untuk timer berulang yang akan mengirim beberapa paket melalui UART, lalu biarkan sisa program Anda dieksekusi selama beberapa milidetik dan kirim beberapa byte berikutnya. Dengan begitu Anda semacam mendapatkan kemampuan multitasking terbatas. Tetapi Anda juga akan memiliki interupsi yang agak lama yang mungkin merupakan hal yang buruk.
Satu-satunya cara nyata untuk melakukan banyak tugas sekaligus pada MCU single-core adalah dengan menggunakan DMA dan peripheral karena mereka bekerja secara independen dari core (DMA dan MCU berbagi bus yang sama, sehingga mereka bekerja sedikit lebih lambat ketika keduanya aktif). Jadi, sementara DMA mengocok byte ke UART, inti Anda bebas mengirim barang ke ethernet.
sumber
Jawaban lain sudah menggambarkan opsi yang paling sering digunakan (loop utama, ISR, RTOS). Berikut opsi lain sebagai kompromi: Protothreads . Ini pada dasarnya lib sangat ringan untuk utas, yang menggunakan loop utama dan beberapa makro C, untuk "mengemulasi" RTOS. Tentu saja ini bukan OS lengkap, tetapi untuk utas "sederhana" itu bisa bermanfaat.
sumber
Desain dasar saya untuk RTOS irisan waktu minimal tidak banyak berubah pada beberapa keluarga mikro. Ini pada dasarnya penghenti waktu mengendarai mesin negara. Rutin layanan interupsi adalah kernel OS sedangkan pernyataan switch di loop utama adalah tugas pengguna. Driver perangkat adalah interupsi rutinitas layanan untuk interupsi I / O.
Struktur dasar adalah sebagai berikut:
Ini pada dasarnya adalah sistem multitasking yang kooperatif. Tugas ditulis untuk tidak pernah memasukkan loop infinite tetapi kami tidak peduli karena tugas dijalankan dalam loop event sehingga infinite loop implisit. Ini adalah gaya pemrograman yang mirip dengan bahasa yang berorientasi acara / nonblocking seperti javascript atau go.
Anda dapat melihat contoh gaya arsitektur ini dalam perangkat lunak pemancar RC saya (ya, saya benar-benar menggunakannya untuk menerbangkan pesawat RC sehingga agak aman untuk mencegah saya menabrak pesawat saya dan berpotensi membunuh orang): https://github.com / slebetman / pic-txmod . Ini pada dasarnya memiliki 3 tugas - 2 tugas real-time diimplementasikan sebagai driver perangkat stateful (lihat hal-hal ppmio) dan 1 tugas latar belakang menerapkan logika pencampuran. Jadi pada dasarnya ini mirip dengan server web Anda karena memiliki 2 utas I / O.
sumber
Sementara saya menghargai bahwa pertanyaan yang secara khusus ditanyakan tentang penggunaan RTOS yang tertanam, terpikir oleh saya bahwa pertanyaan yang lebih luas yang ditanyakan adalah "bagaimana mencapai multitasking pada platform tertanam".
Saya sangat menyarankan Anda untuk melupakan menggunakan RTOS tertanam setidaknya untuk saat ini. Saya menyarankan ini karena saya pikir sangat penting untuk terlebih dahulu belajar tentang bagaimana mencapai tugas 'concurrency' dengan menggunakan teknik pemrograman yang sangat sederhana yang terdiri dari penjadwal tugas sederhana dan mesin negara.
Untuk menjelaskan konsepnya dengan sangat singkat, setiap modul pekerjaan yang perlu dilakukan (yaitu masing-masing 'tugas') memiliki fungsi tertentu yang harus disebut ('dicentang') secara berkala agar modul tersebut dapat melakukan beberapa hal. Modul mempertahankan statusnya sendiri saat ini. Anda kemudian memiliki loop infinite utama (penjadwal) yang memanggil fungsi-fungsi modul.
Ilustrasi kasar:
Struktur pemrograman single-threaded seperti ini di mana Anda secara berkala memanggil fungsi-fungsi mesin negara utama dari loop scheduler utama ada di mana-mana dalam pemrograman tertanam, dan inilah mengapa saya akan sangat mendorong OP untuk terbiasa dan nyaman dengan itu terlebih dahulu, sebelum menyelam langsung menggunakan Tugas / utas RTOS.
Saya bekerja pada jenis perangkat tertanam yang memiliki antarmuka LCD perangkat keras, server web internal, klien email, klien DDNS, VOIP, dan banyak fitur lainnya. Meskipun kami menggunakan RTOS (Keil RTX), jumlah utas individu (tugas) yang digunakan sangat kecil dan sebagian besar 'multitasking' dicapai seperti dijelaskan di atas.
Untuk memberikan beberapa contoh perpustakaan yang menunjukkan konsep ini:
Perpustakaan jaringan Keil. Seluruh tumpukan TCP / IP dapat dijalankan single-threaded; Anda secara berkala memanggil main_TcpNet (), yang mengulangi tumpukan TCP / IP dan opsi jaringan lain yang telah Anda kompilasi dari perpustakaan (mis. server web). Lihat http://www.keil.com/support/man/docs/rlarm/rlarm_main_tcpnet.htm . Memang, dalam beberapa situasi (mungkin di luar lingkup jawaban ini) Anda mencapai titik di mana ia mulai bermanfaat atau perlu menggunakan utas (terutama jika menggunakan pemblokiran soket BSD). (Catatan lebih lanjut: V5 MDK-ARM baru sebenarnya memunculkan thread Ethernet khusus - tapi saya hanya mencoba memberikan ilustrasi.)
Pustaka VOIP Linphone. Pustaka linphone itu sendiri berulir tunggal. Anda memanggil
iterate()
fungsi pada interval yang cukup. Lihat http://www.linphone.org/docs/liblinphone-javadoc/org/linphone/core/LinphoneCore.html#iterate () . (Sedikit contoh buruk karena saya menggunakan ini pada platform Linux yang tertanam dan perpustakaan dependensi linphone tidak diragukan lagi menelurkan thread, tapi sekali lagi ini untuk menggambarkan suatu hal.)Kembali ke masalah khusus yang digariskan oleh OP, masalahnya tampaknya menjadi fakta bahwa komunikasi UART harus terjadi pada saat yang sama dengan beberapa jaringan (mengirimkan paket melalui TCP / IP). Saya tidak tahu perpustakaan jaringan apa yang sebenarnya Anda gunakan, tetapi saya menganggap itu memiliki fungsi utama yang perlu sering dipanggil. Anda perlu menulis kode Anda yang berhubungan dengan pengiriman / penerimaan data UART agar disusun dengan cara yang sama, sebagai mesin negara yang dapat diulang dengan panggilan berkala ke fungsi utama.
sumber