Sedikit tentang multitasking dalam mikrokontroler

Sedikit tentang multitasking


Setiap orang yang hari demi hari, atau dari kasus ke kasus, terlibat dalam pemrograman mikrokontroler, cepat atau lambat akan dihadapkan dengan pertanyaan: apakah saya harus menggunakan sistem operasi multi-tasking? Ada cukup banyak dari mereka di jaringan, dan banyak dari mereka gratis (atau hampir gratis). Pilih saja.


Keraguan serupa muncul ketika Anda menemukan sebuah proyek di mana mikrokontroler harus secara bersamaan melakukan beberapa tindakan berbeda. Beberapa dari mereka tidak terhubung dengan yang lain, sementara sisanya, sebaliknya, tidak bisa tanpa satu sama lain. Selain itu, mungkin terlalu banyak dari keduanya. Apa yang “terlalu banyak” tergantung pada siapa yang akan mengevaluasi atau siapa yang akan melaksanakan pengembangan. Nah, apakah itu orang yang sama.


Sebaliknya, ini bukan masalah kuantitas, tetapi pertanyaan tentang perbedaan kualitatif dalam tugas dalam kaitannya dengan kecepatan eksekusi, atau persyaratan lainnya. Pikiran seperti itu mungkin muncul, misalnya, ketika proyek perlu secara teratur memonitor tegangan suplai (apakah itu hilang?), Cukup sering membaca dan menyimpan nilai-nilai dari jumlah input (mereka tidak memberikan istirahat), sesekali memantau suhu dan mengontrol kipas (tidak ada yang bernapas), periksa menonton dengan seseorang yang Anda percaya (baik bagi Anda untuk memerintahkannya), tetap berhubungan dengan operator (cobalah untuk tidak membuatnya kesal), periksa checksum dari memori permanen program untuk demensia (ketika dihidupkan, atau seminggu sekali, atau di pagi hari).


Tugas heterogen seperti itu dapat diprogram dengan sangat bermakna dan berhasil, bergantung pada tugas latar belakang tunggal dan pengatur waktu. Dalam penangan interupsi ini, setiap kali salah satu dari "potongan" tugas berikutnya dieksekusi. Tergantung pada pentingnya, urgensi, atau pertimbangan serupa, tantangan ini sering diulang untuk beberapa tugas, tetapi jarang untuk yang lain. Namun, kita harus memastikan bahwa setiap tugas melakukan bagian waktu yang singkat dari pekerjaan, kemudian bersiap untuk bagian kecil berikutnya dari pekerjaan, dan seterusnya. Pendekatan ini, jika Anda terbiasa, sepertinya tidak terlalu rumit. Ketidaknyamanan terjadi ketika Anda ingin membangun proyek. Atau, misalnya, tiba-tiba dipindahkan ke yang lain. Perlu dicatat bahwa yang kedua sering lebih sulit dan tanpa banyak tugas semu.


Tetapi bagaimana jika Anda menggunakan sistem operasi yang sudah jadi untuk mikrokontroler? Tentu, banyak yang melakukannya. Ini pilihan yang bagus. Tetapi penulis kalimat-kalimat ini, sampai sekarang, telah dan terus dihentikan oleh gagasan bahwa perlu memahami ini, setelah menghabiskan banyak waktu, memilih dari apa yang berhasil kami peroleh dan hanya menggunakan apa yang benar-benar diperlukan dari ini. Dan lakukan semua ini, ingatlah, menyelidiki kode orang lain! Dan tidak ada kepastian bahwa dalam enam bulan ini tidak perlu diulang, karena itu akan dilupakan.


Dengan kata lain, mengapa Anda perlu garasi penuh peralatan dan perlengkapan jika sepeda disimpan dan digunakan di sana?


Oleh karena itu, ada keinginan untuk membuat tugas "switch" sederhana hanya untuk Cortex-M4 (well, mungkin bahkan untuk M3 dan M7). Tapi yang lama, keinginan yang baik untuk tidak terlalu banyak tidak hilang.


Jadi, kami melakukan yang paling sederhana. Sejumlah kecil tugas berbagi waktu eksekusi secara merata. Seperti pada Gambar 1 di bawah ini, empat tugas melakukan ini. Biarkan yang utama menjadi nol, karena sulit membayangkan yang lain.



Dengan bekerja dengan cara ini, mereka dijamin mendapatkan slot atau rentang waktu mereka (tick) dan tidak perlu tahu tentang keberadaan tugas lain. Setiap tugas tepat setelah 3 kutu lagi akan mendapatkan kesempatan untuk melakukan sesuatu.


Tetapi, di sisi lain, jika ada tugas yang diperlukan untuk menunggu acara eksternal, misalnya, menekan tombol, maka itu akan dengan bodohnya menghabiskan waktu berharga dari mikrokontroler kita. Kami tidak bisa setuju dengan ini. Dan katak kita (hati nurani) - juga. Sesuatu harus dilakukan.


Dan biarkan tugas itu, jika tidak ada hubungannya sejauh ini, berikan waktu yang tersisa dari kutu ke rekan-rekannya, yang, kemungkinan besar, membajak dengan sekuat tenaga.


Dengan kata lain, berbagi itu perlu. Biarkan tugas 2 melakukan hal itu, seperti pada Gambar 2.



Dan mengapa tugas latar belakang utama kita tidak diizinkan untuk memberikan sisa waktu, jika Anda masih harus menunggu? Mari kita ijinkan. Seperti yang ditunjukkan pada Gambar 3.



Dan jika Anda tahu bahwa beberapa tugas tidak akan segera mengharuskan Anda untuk memeriksa sesuatu lagi atau hanya bekerja? Dan dia bisa membiarkan dirinya sedikit tidur, dan sebagai gantinya akan membuang waktu dan mendapatkan di bawah kakinya. Bukan pesanan, itu harus diperbaiki. Biarkan tugas 3 melewatkan satu bagian waktunya (atau seribu). Seperti yang ditunjukkan pada gambar 4.



Seperti yang kita lihat, kita telah menggariskan koeksistensi yang adil dari tugas atau sesuatu seperti itu. Kita sekarang harus membuat tugas individu kita berperilaku seperti yang ditentukan. Dan jika kita mencoba menilai waktu, maka ada baiknya mengingat bahasa tingkat rendah (saya tidak takut pada kata assembler) dan tidak sepenuhnya mempercayai kompiler dari bahasa apa pun, level tinggi atau sangat tinggi. Memang, jauh di lubuk hati kita, kita dengan tegas menentang semua ketergantungan. Selain itu, fakta bahwa kita tidak memerlukan assembler apa pun, tetapi hanya dari Cortex-M4, menyederhanakan hidup kita.


Untuk stack, kami memilih satu area umum RAM yang akan terisi, yaitu, dengan tujuan mengurangi alamat memori. Mengapa Hanya karena itu tidak bekerja secara berbeda. Kami akan secara mental membagi area penting ini menjadi bagian yang sama sesuai dengan jumlah tugas maksimum yang dinyatakan. Gambar 5 menunjukkan ini untuk empat tugas.



Selanjutnya, kami memilih tempat di mana kami akan menyimpan salinan penunjuk tumpukan untuk setiap tugas. Sekarang, dengan menyela dari timer, yang kita ambil sebagai system timer, kita menyimpan semua register tugas saat ini di area stack-nya (register SP sekarang menunjuk ke sana), kemudian kita menyimpan stack pointer-nya di tempat khusus (kita menyimpan nilainya), kita mendapatkan stack pointer dari tugas selanjutnya ( tulis nilai baru ke register SP) dari tempat khusus kami dan pulihkan semua registernya. Salinan mereka sekarang ditunjukkan oleh register SP dari tugas kita selanjutnya. Yah, kita keluar dari gangguan, tentu saja. Selain itu, seluruh konteks tugas selanjutnya dalam daftar muncul di register.


Mungkin, akan berlebihan untuk mengatakan bahwa task3 setelah berikutnya dalam antrian akan menjadi utama. Dan tentu saja, tidak berlebihan untuk mengingat bahwa Cortex-M4 sudah memiliki timer SysTick dan interupsi khusus darinya, dan banyak produsen mikrokontroler mengetahuinya. Kami akan menggunakannya dan gangguan ini sebagaimana dimaksud.


Untuk memulai penghitung waktu sistem ini, serta melakukan semua persiapan dan pemeriksaan yang diperlukan, Anda harus menggunakan prosedur yang dimaksudkan untuk ini.


U8 main_start_task_switcher(void); 

Rutin ini mengembalikan 0 jika semua pemeriksaan telah lulus, atau kode kesalahan jika terjadi kesalahan. Diperiksa, pada dasarnya, apakah tumpukan disejajarkan dengan benar dan apakah ada cukup ruang untuknya, dan juga semua tempat khusus kami diisi dengan nilai awal. Singkatnya, kebosanan.


Jika seseorang ingin melihat teks program, maka pada akhir narasi ia akan dapat dengan mudah melakukan ini, misalnya, melalui surat pribadi.


Ya, saya benar-benar lupa ketika kita mengambil register dari tugas berikutnya dari penyimpanan untuk pertama kali dalam hidupnya, perlu bahwa mereka mendapatkan nilai asli yang bermakna. Dan karena, dia akan mengambilnya dari bagian tumpukannya, Anda harus meletakkannya terlebih dahulu dan menggerakkan penunjuk tumpukannya sehingga nyaman untuk diambil. Untuk ini kita perlu prosedur


  U8 task_run_and_return_task_number(U32 taskAddress); 

Untuk subrutin ini, kami melaporkan alamat 32-bit dari awal tugas kami yang ingin kami jalankan. Dan dia (subrutin) memberi tahu kami jumlah tugas, yang ternyata dalam tabel umum khusus, atau 0 jika tidak ada ruang di dalam tabel. Lalu kita bisa menjalankan tugas lain, lalu yang lain dan seterusnya, meskipun ketiganya merupakan tambahan dari tugas utama kita yang tidak pernah berakhir. Dia tidak akan pernah memberikan nomor nolnya kepada siapa pun.


Beberapa kata tentang prioritas. Prioritas utama adalah dan tetap tidak membebani pembaca dengan detail yang tidak perlu.


Tapi serius, kita harus ingat bahwa ada gangguan dari port serial, dari beberapa koneksi SPI, dari konverter analog-ke-digital, dari timer lain, setelah semua. Dan apa yang akan terjadi jika kita akan beralih ke tugas lain (beralih konteks) ketika kita berada di handler dari beberapa jenis interupsi. Lagi pula, ini bukan tugas yang sah, tetapi pengaburan sementara dari program. Dan kita akan menjaga konteks yang aneh ini sebagai semacam tugas. Akan ada kebingungan: kerah tidak kencang, tutupnya tidak pas. Hentikan, tidak, ini dari cerita yang berbeda.


Dalam kasus kami, ini tidak bisa dibiarkan. Kita tidak boleh mengizinkan kita untuk mengubah konteks selama pemrosesan interupsi yang tidak direncanakan. Inilah prioritas untuk ini. Kita hanya harus menunggu sedikit, dan hanya kemudian, ketika keberanian yang belum pernah terjadi ini berakhir, dengan tenang beralih ke tugas lain. Singkatnya, prioritas interupsi dari pengalihan tugas kami harus lebih lemah daripada prioritas interupsi lainnya yang digunakan. Omong-omong, ini juga dilakukan dalam prosedur start-up kami, dan di sanalah ia diinstal, yang paling tidak prioritas dari semua kemungkinan.


Saya tidak ingin berbicara, tetapi saya harus. Prosesor kami memiliki dua mode operasi: istimewa dan tidak istimewa. Dan juga dua register untuk stack pointer:
proses SP dan SP utama. Jadi, kami tidak akan menukar dengan hal-hal sepele, kami hanya akan menggunakan mode istimewa dan hanya pointer tumpukan utama. Selain itu, semua ini sudah diberikan pada awal controller. Jadi, kita tidak akan mempersulit hidup kita.


Masih perlu diingat bahwa setiap tugas, pasti, ingin dapat membuang segalanya ke neraka dan bagaimana untuk bersantai. Dan ini bisa terjadi kapan saja selama hari kerja, yaitu selama kutu kami. Cortex-M4 menyediakan untuk kasus seperti itu perintah assembler khusus SVC, yang akan kami adaptasi dengan situasi kami. Ini mengarah pada gangguan yang akan membawa kita ke tujuan. Dan kami akan membiarkan tugas tidak hanya meninggalkan tempat kerja setelah makan siang, tetapi tidak akan datang besok. Kenapa, biarkan saja setelah liburan. Dan jika perlu, maka biarkan sampai ketika selesai perbaikan atau tidak datang sama sekali. Untuk melakukan ini, ada prosedur yang bisa disebabkan oleh tugas itu sendiri.


  void release_me_and_set_sleep_period(U32 ticks); 

Rutin ini hanya perlu menunjukkan berapa banyak kutu yang direncanakan untuk beristirahat. Jika 0, maka Anda hanya dapat mengistirahatkan sisa dari centang saat ini. Jika 0xFFFFFFFF, maka tugas itu akan "tidur" sampai seseorang bangun. Semua angka lain menunjukkan jumlah kutu di mana tugas akan berada dalam kondisi tidur.


Agar orang lain dapat bangun dari samping atau membuatnya tidur, saya harus menambahkan prosedur seperti itu.


  void task_wake_up_action(U8 taskNumber); void set_task_sleep_period(U8 taskNumber, U32 ticks); 

Dan, untuk jaga-jaga, bahkan subrutin seperti itu.


  void task_remove_action(U8 taskNumber); 

Secara kasar, dia mencoret tugas dari daftar karyawan. Jujur, saya belum tahu mengapa saya menulisnya. Tiba-tiba berguna?


Sudah waktunya untuk menunjukkan bagaimana tempat di mana satu tugas digantikan oleh yang lain, yaitu, saklar itu sendiri, terlihat seperti.


Untuk jaga-jaga, mari kita ingat bahwa beberapa register, ketika memasuki interupsi, disimpan di stack tanpa partisipasi kita, secara otomatis (seperti kebiasaan di Cortex-M4). Karena itu, kita hanya perlu menyimpan sisanya. Ini bisa dilihat di bawah. Jangan khawatir dengan apa yang Anda lihat, ini adalah instruksi assembler Cortex-M4 (M3, M7), sebagaimana diuraikan oleh IAR Embedded Workbench.


Mereka yang belum menemukan instruksi perakitan, percayalah padaku, mereka benar-benar terlihat seperti itu. Ini adalah molekul yang membentuk program apa pun di bawah ARM Cortex-M4.


 SysTick_Handler STMDB SP!,{R4-R11} ;   LDR R0,=timersTable ;    LDR R1,=stacksTable ;    LDR R2,[R0] ;R2   ()  STR SP,[R1,R2,LSL #2] ;   SP (R2 * 4) __st_next_check ADD R2,R2,#1 ;   CMP R2,#TASKS_LIMIT ;R2-TASKS_LIMIT  BLO __st_no_border_yet ;   MOV R2,#0 ;    (main) LDR R3,[R1] ; main SP MOV SP,R3 B __st_timer_ok __st_no_border_yet ;; LDR SP,[R1,R2,LSL #2] ;    (errata Cortex M4) ;; CMP SP,#0 ; LDR R3,[R1,R2,LSL #2] ;  SP      CMP R3,#0 ; =0     BEQ __st_next_check MOV SP,R3 LDR R3,[R0,R2,LSL #2] ;  suspend timer CBZ R3,__st_timer_ok ; 0    ,   ; CMP R3,#0xFFFFFFFF ; ,   BEQ __st_next_check SUB R3,R3,#1 ;  1 STR R3,[R0,R2,LSL #2] ;  suspend timer B __st_next_check __st_timer_ok STR R2,[R0] ;     LDMIA SP!,{R4-R11} ;  R4-R11 BX LR 

Menangani interupsi yang diperintahkan oleh tugas itu sendiri ketika mengembalikan sisa centang tampak serupa. Satu-satunya perbedaan adalah Anda masih harus repot-repot tidur sebentar (atau tertidur lelap). Ada satu kehalusan. Dua tindakan harus dilakukan, tulis nomor yang diinginkan di sleep timer dan menyebabkan SVC terputus. Fakta bahwa kedua tindakan ini tidak terjadi secara atom (yaitu, tidak keduanya sekaligus) membuat saya sedikit khawatir. Bayangkan selama satu milidetik bahwa kita baru saja menyalakan pengatur waktu dan pada saat itu sudah waktunya untuk mengerjakan tugas lain. Yang lain mulai menghabiskan kutu nya, sementara tugas kita akan tidur kutu berikutnya, seperti yang diharapkan (karena timer-nya tidak nol). Kemudian, ketika saatnya tiba, tugas kita akan menerima centangnya dan segera memberikannya untuk mengganggu SVC, karena dua tindakan ini masih harus dilakukan. Tidak ada yang mengerikan, menurut saya, akan terjadi, tetapi endapan akan tetap ada. Karena itu, kami akan melakukannya. Penghitung waktu tidur yang akan datang diletakkan di tempat pendahuluan. Ini diambil dari sana oleh rutinitas interupsi itu sendiri dari SVC. Atomicity, seolah-olah, tercapai. Ini ditunjukkan di bawah ini.


 SVC_Handler LDR R0,__sysTickAddr ; SysTick  MOV R1,#6 ;   CSR ,   STR R1,[R0] ;Stop SysTimer MOV R1,#7 ; ,   STR R1,[R0] ;Start SysTimer ; STMDB SP!,{R4-R11} ;   LDR R0,=timersTable ;    LDR R1,=stacksTable ;    LDR R2,[R0] ;R2   ()  STR SP,[R1,R2,LSL #2] ;   SP (R2 * 4) LDR R3,=tmpTimersTable ;   tmpTimers LDR R3,[R3,R2,LSL #2] ;tmpTimer    STR R3,[R0,R2,LSL #2] ; timer  __svc_next_check ADD R2,R2,#1 ;   CMP R2,#TASKS_LIMIT ;R2-TASKS_LIMIT  BLO __svc_no_border_yet ;   MOV R2,#0 ;    (main) LDR R3,[R1] ; main SP MOV SP,R3 B __svc_timer_ok __svc_no_border_yet ;; LDR SP,[R1,R2,LSL #2] ;Restore SP does not work (errata Cortex M4) ;; CMP SP,#0 ; LDR R3,[R1,R2,LSL #2] ;  SP      CMP R3,#0 ; =0     BEQ __svc_next_check MOV SP,R3 LDR R3,[R0,R2,LSL #2] ;  suspend timer CBZ R3,__svc_timer_ok ; 0    ,   B __svc_next_check __svc_timer_ok STR R2,[R0] ;     LDMIA SP!,{R4-R11} ; R4-R11 BX LR 

Harus diingat bahwa semua subrutin dan penangan interupsi ini mengacu pada area data tertentu, yang terlihat dilakukan oleh penulis seperti yang ditunjukkan pada Gambar 7.


  DATA SECTION .taskSwitcher:CODE:ROOT(2) __topStack DCD sfe(CSTACK) __botStack DCD sfb(CSTACK) __dimStack DCD sizeof(CSTACK) __sysAIRCRaddr DCD 0xE000ED0C __sysTickAddr DCD 0xE000E010 __sysSHPRaddr DCD 0xE000ED18 __sysTickReload DCD RELOAD ;******************************************************************************* ; Task table for concurrent tasks (main is number 0). ;******************************************************************************* SECTION TABLE:DATA:ROOT(2) DS32 1 ;stack shift due to FPU mainCopyCONTROL DS32 1 ;Needed to determine if FPU is used mainPSRvalue DS32 1 ;Copy from main ;******************************************************************************* 

Untuk memastikan bahwa semua hal di atas masuk akal, penulis harus menulis proyek kecil di bawah IAR Embedded Workbench, di mana ia berhasil memeriksa dan menyentuh semuanya secara detail. Semuanya diuji pada pengontrol STM32F303VCT6 (ARM Cortex-M4). Atau lebih tepatnya, menggunakan papan STM32F3DISCOVERY. Ada cukup LED untuk memberi setiap tugas banyak flashing dengan LED sendiri secara terpisah.


Ada beberapa fitur yang menurut saya berguna. Misalnya, subrutin yang menghitung dalam setiap area tumpukan jumlah kata yang tidak terpengaruh, yaitu, tetap sama dengan nol. Ini dapat berguna saat debugging, ketika Anda perlu memeriksa apakah mengisi tumpukan dengan satu tugas atau yang lain terlalu dekat dengan level batas.


  U32 get_task_stack_empty_space(U8 taskNum); 

Saya ingin menyebutkan satu fungsi lagi. Ini adalah peluang bagi tugas itu sendiri untuk mengetahui nomor Anda dalam daftar. Anda bisa memberi tahu seseorang nanti.


 ;******************************************************************************* ; Example: U8 get_my_number(void); ;     (). ..    . ;******************************************************************************* get_my_number LDR R0,=timersTable ;    (currentTaskNumber) LDR R0,[R0] ;  BX LR ;============================================================== 

Mungkin itu saja untuk saat ini.

Source: https://habr.com/ru/post/id454874/


All Articles