Distribusi statis objek FreeRTOS

Secara default, semua objek dalam sistem FreeRTOS didistribusikan secara dinamis - antrian, semaphore, timer, tugas (utas), dan mutex. Programmer hanya melihat "heap" - area di mana memori dialokasikan secara dinamis atas permintaan suatu program atau sistem, dan apa yang terjadi di dalamnya tidak jelas. Berapa yang tersisa? Tidak dikenal Apakah ada yang lebih dari yang Anda butuhkan? Siapa tahu Secara pribadi, saya lebih suka menyelesaikan masalah mengatur memori bahkan pada tahap penulisan firmware, tanpa membawa kesalahan runtime ketika memori tiba-tiba berakhir.

Artikel ini adalah kelanjutan logis kemarin tentang distribusi statis objek dalam memori mikrokontroler, hanya sekarang dalam kaitannya dengan objek FreeRTOS. Hari ini kita akan belajar bagaimana menempatkan objek FreeRTOS secara statis, yang akan memungkinkan kita untuk lebih jelas memahami apa yang terjadi dalam RAM mikrokontroler, bagaimana tepatnya objek kita berada dan seberapa banyak mereka menempati.

Tetapi hanya dengan mengambil dan mulai menempatkan objek FreeRTOS secara statis tidak memerlukan banyak kecerdasan - dimulai dengan versi 9.0, FreeRTOS menyediakan fungsi untuk membuat objek yang ditempatkan secara statis. Fungsi-fungsi tersebut memiliki akhiran Statis dalam nama dan fungsi-fungsi ini memiliki dokumentasi yang sangat baik dengan contoh. Kami akan menulis pembungkus C ++ yang nyaman dan indah di atas fungsi FreeRTOS yang tidak hanya akan menempatkan objek secara statis, tetapi juga menyembunyikan semua jeroan ayam itik, serta menyediakan antarmuka yang lebih nyaman.

Artikel ini ditujukan untuk programmer pemula, tetapi yang sudah terbiasa dengan dasar-dasar FreeRTOS dan primitif sinkronisasi program multithreaded. Ayo pergi.

FreeRTOS adalah sistem operasi untuk mikrokontroler. Baiklah, ok, bukan OS lengkap, tapi perpustakaan yang memungkinkan Anda menjalankan beberapa tugas secara paralel. FreeRTOS juga memungkinkan tugas untuk bertukar pesan melalui antrian pesan, menggunakan penghitung waktu, dan menyinkronkan tugas menggunakan semaphore dan mutex.

Menurut pendapat saya, firmware apa pun di mana Anda perlu secara bersamaan melakukan dua (atau lebih) tugas dapat diselesaikan dengan lebih mudah dan lebih elegan jika Anda menggunakan FreeRTOS. Misalnya, baca bacaan dari sensor lambat dan pada saat yang sama melayani tampilan. Hanya agar tanpa rem, sedangkan sensornya terbaca. Secara umum, harus punya! Saya sangat merekomendasikan untuk belajar.

Seperti yang saya katakan dan tulis dalam artikel sebelumnya, saya tidak terlalu suka pendekatan membuat objek secara dinamis jika kita tahu jumlah dan ukurannya pada tahap kompilasi. Jika objek seperti itu ditempatkan secara statis, maka kita bisa mendapatkan gambaran alokasi memori yang lebih jelas dan lebih mudah dipahami dalam mikrokontroler, dan karenanya menghindari kejutan ketika memori tiba-tiba berakhir.

Kami akan mempertimbangkan masalah organisasi memori FreeRTOS menggunakan papan BluePill pada mikrokontroler STM32F103C8T6 sebagai contoh. Agar tidak khawatir tentang kompiler dan sistem build, kami akan bekerja di lingkungan ArduinoIDE, memasang dukungan untuk board ini. Ada beberapa implementasi Arduino untuk STM32 - pada prinsipnya, semua akan dilakukan. Saya telah menginstal stm32duino sesuai dengan instruksi dari proyek Readme.md, sebuah bootloader sebagaimana disebutkan dalam artikel ini . FreeRTOS versi 10.0 diinstal melalui manajer perpustakaan ArduinoIDE. Kompiler - gcc 8.2

Kami akan datang dengan tugas eksperimental kecil. Mungkin tidak ada banyak arti praktis dalam tugas ini, tetapi semua primitif sinkronisasi yang ada di FreeRTOS akan digunakan. Sesuatu seperti ini:

  • 2 tugas (utas) bekerja secara paralel
  • timer juga berfungsi, yang dari waktu ke waktu mengirim pemberitahuan ke tugas pertama menggunakan semafor dalam mode menunggu sinyal
  • tugas pertama, setelah menerima pemberitahuan dari timer, mengirim pesan (nomor acak) ke tugas kedua melalui antrian
  • yang kedua, setelah menerima pesan, mencetaknya ke konsol
  • biarkan tugas pertama juga mencetak sesuatu ke konsol, dan agar mereka tidak melawan konsol akan dilindungi oleh mutex.
  • ukuran antrian bisa terbatas pada satu elemen, tetapi untuk membuatnya lebih menarik, kami menempatkan 1000

Implementasi standar (sesuai dengan dokumentasi dan tutorial) mungkin terlihat seperti ini.

#include <STM32FreeRTOS.h> TimerHandle_t xTimer; xSemaphoreHandle xSemaphore; xSemaphoreHandle xMutex; xQueueHandle xQueue; void vTimerCallback(TimerHandle_t pxTimer) { xSemaphoreGive(xSemaphore); } void vTask1(void *) { while(1) { xSemaphoreTake(xSemaphore, portMAX_DELAY); int value = random(1000); xQueueSend(xQueue, &value, portMAX_DELAY); xSemaphoreTake(xMutex, portMAX_DELAY); Serial.println("Test"); xSemaphoreGive(xMutex); } } void vTask2(void *) { while(1) { int value; xQueueReceive(xQueue, &value, portMAX_DELAY); xSemaphoreTake(xMutex, portMAX_DELAY); Serial.println(value); xSemaphoreGive(xMutex); } } void setup() { Serial.begin(9600); vSemaphoreCreateBinary(xSemaphore); xQueue = xQueueCreate(1000, sizeof(int)); xMutex = xSemaphoreCreateMutex(); xTimer = xTimerCreate("Timer", 1000, pdTRUE, NULL, vTimerCallback); xTimerStart(xTimer, 0); xTaskCreate(vTask1, "Task 1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL); xTaskCreate(vTask2, "Task 2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL); vTaskStartScheduler(); } void loop() {} 

Mari kita lihat apa yang terjadi dalam memori mikrokontroler, jika Anda mengkompilasi kode tersebut. Secara default, semua objek FreeRTOS ditempatkan dalam memori dinamis. FreeRTOS menyediakan sebanyak 5 implementasi manajer memori yang sulit untuk diimplementasikan, tetapi secara umum mereka memiliki tugas yang sama - untuk memotong potongan memori untuk kebutuhan FreeRTOS dan pengguna. Potongan dipotong baik dari tumpukan umum mikrokontroler (menggunakan malloc) atau menggunakan tumpukan terpisah mereka sendiri. Jenis tumpukan apa yang digunakan untuk kita tidak penting - lagi pula, kita tidak bisa melihat ke dalam tumpukan itu.

Sebagai contoh, untuk tumpukan nama FreeRTOS akan terlihat seperti ini (keluaran dari utilitas objdump)

 ... 200009dc l O .bss 00002000 ucHeap ... 

Yaitu kita melihat satu bagian besar, di mana semua objek FreeRTOS dipotong - semafor, mutex, timer, antrian, dan bahkan tugas itu sendiri. 2 poin terakhir sangat penting. Bergantung pada jumlah elemen, antrian bisa sangat besar, dan tugas dijamin akan memakan banyak ruang karena tumpukan, yang juga dialokasikan bersama dengan tugas.

Ya, ini adalah minus dari multitasking - setiap tugas akan memiliki tumpukan sendiri. Selain itu, tumpukan harus cukup besar sehingga tidak hanya berisi panggilan dan variabel lokal dari tugas itu sendiri, tetapi juga tumpukan interupsi, jika ini terjadi. Nah, karena gangguan dapat terjadi kapan saja, maka setiap tugas harus memiliki cadangan di tumpukan jika terjadi gangguan. Selain itu, mikrokontroler CortexM dapat memiliki interupsi bersarang, sehingga tumpukan harus cukup besar untuk mengakomodasi semua interupsi jika terjadi secara bersamaan.

Ukuran tumpukan tugas diatur saat membuat tugas dengan parameter fungsi xTaskCreate. Ukuran stack tidak boleh kurang dari parameter configMINIMAL_STACK_SIZE (ditentukan dalam file konfigurasi FreeRTOSConfig.h) - ini adalah cadangan yang sama untuk interupsi. Ukuran heap diatur oleh parameter configTOTAL_HEAP_SIZE dan dalam hal ini adalah 8kb.

Sekarang coba tebak apakah semua benda kita akan muat di tumpukan 8kb? Dan beberapa benda? Dan beberapa tugas lagi?
Dengan pengaturan FreeRTOS tertentu, semua objek tidak muat di heap. Dan kelihatannya seperti ini: programnya tidak berfungsi. Yaitu semuanya dikompilasi, dituangkan, tetapi kemudian mikrokontroler hanya hang dan hanya itu. Dan coba tebak bahwa masalahnya persis ukuran tumpukan. Saya harus menambah banyak menjadi 12kb.

Berhenti, apa saja variabel xTimer, xQueue, xSemaphore, dan xMutex? Bukankah mereka menggambarkan benda yang kita butuhkan? Tidak, ini hanya handle - pointer ke struktur (buram) tertentu, yang menggambarkan objek sinkronisasi itu sendiri

 200009cc g O .bss 00000004 xTimer 200009d0 g O .bss 00000004 xSemaphore 200009cc g O .bss 00000004 xQueue 200009d4 g O .bss 00000004 xMutex 

Seperti yang telah saya sebutkan, saya mengusulkan untuk memperbaiki semua kekacauan ini dengan cara yang sama seperti pada artikel sebelumnya - kami akan mendistribusikan semua objek kami secara statis pada tahap kompilasi. Fungsi distribusi statis menjadi tersedia jika parameter configSUPPORT_STATIC_ALLOCATION diatur ke 1 dalam file konfigurasi FreeRTOS.

Mari kita mulai dengan garis. Berikut adalah cara dokumentasi tentang FreeRTOS menawarkan untuk mengalokasikan antrian

 struct AMessage { char ucMessageID; char ucData[ 20 ]; }; #define QUEUE_LENGTH 10 #define ITEM_SIZE sizeof( uint32_t ) // xQueueBuffer will hold the queue structure. StaticQueue_t xQueueBuffer; // ucQueueStorage will hold the items posted to the queue. Must be at least // [(queue length) * ( queue item size)] bytes long. uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ]; void vATask( void *pvParameters ) { QueueHandle_t xQueue1; // Create a queue capable of containing 10 uint32_t values. xQueue1 = xQueueCreate( QUEUE_LENGTH, // The number of items the queue can hold. ITEM_SIZE // The size of each item in the queue &( ucQueueStorage[ 0 ] ), // The buffer that will hold the items in the queue. &xQueueBuffer ); // The buffer that will hold the queue structure. // The queue is guaranteed to be created successfully as no dynamic memory // allocation is used. Therefore xQueue1 is now a handle to a valid queue. // ... Rest of task code. } 

Dalam contoh ini, antrian dijelaskan oleh tiga variabel:

  • Array ucQueueStorage adalah tempat elemen antrian akan ditempatkan. Ukuran antrian ditetapkan oleh pengguna untuk setiap antrian secara terpisah.
  • Struktur xQueueBuffer - di sini menampilkan deskripsi dan status antrian, ukuran saat ini, daftar tugas yang tertunda, serta atribut dan bidang lain yang diperlukan oleh FreeRTOS untuk bekerja dengan antrian. Nama untuk variabel, menurut pendapat saya, tidak sepenuhnya berhasil, di FreeRTOS sendiri hal ini disebut QueueDefinition (deskripsi antrian).
  • Variabel xQueue1 adalah pengidentifikasi antrian (pegangan). Semua fungsi manajemen antrian, serta beberapa yang lain (misalnya, fungsi internal untuk bekerja dengan pengatur waktu, semaphore, dan mutex) menerima pegangan seperti itu. Sebenarnya, ini hanya sebuah penunjuk ke QueueDefinition, tetapi kami tidak tahu ini (seolah-olah), dan oleh karena itu pegangan harus ditarik secara terpisah.

Untuk melakukan seperti pada contoh, tentu saja, tidak akan menjadi masalah. Namun secara pribadi, saya tidak ingin memiliki sebanyak 3 variabel per entitas. Kelas yang dapat merangkumnya sudah memintanya. Hanya satu masalah - ukuran setiap antrian dapat bervariasi. Di satu tempat Anda membutuhkan antrian yang lebih besar, di tempat lain beberapa elemen sudah cukup. Karena kami ingin mengantri secara statis, kami harus menentukan ukuran ini pada waktu kompilasi. Anda dapat menggunakan templat untuk ini.

 template<class T, size_t size> class Queue { QueueHandle_t xHandle; StaticQueue_t x QueueDefinition; T xStorage[size]; public: Queue() { xHandle = xQueueCreateStatic(size, sizeof(T), reinterpret_cast<uint8_t*>(xStorage), &xQueueDefinition); } bool receive(T * val, TickType_t xTicksToWait = portMAX_DELAY) { return xQueueReceive(xHandle, val, xTicksToWait); } bool send(const T & val, TickType_t xTicksToWait = portMAX_DELAY) { return xQueueSend(xHandle, &val, xTicksToWait); } }; 

Pada saat yang sama, fungsi mengirim dan menerima pesan, yang langsung nyaman bagi kami, juga menetap di kelas ini.

Antrian akan dinyatakan sebagai variabel global, sesuatu seperti ini

 Queue<int, 1000> xQueue; 

Pengiriman pesan

  xQueue.send(value); 

Terima pesan

  int value; xQueue.receive(&value); 

Sekarang mari kita berurusan dengan semaphores. Dan meskipun secara teknis (di dalam FreeRTOS) semaphore dan mutex diimplementasikan melalui antrian, secara semantik ini adalah 3 primitif berbeda. Oleh karena itu, kami akan mengimplementasikannya dalam kelas yang terpisah.

Implementasi kelas semaphore akan sangat sepele - hanya menyimpan beberapa variabel dan mendeklarasikan beberapa fungsi.

 class Sema { SemaphoreHandle_t xSema; StaticSemaphore_t xSemaControlBlock; public: Sema() { xSema = xSemaphoreCreateBinaryStatic(&xSemaControlBlock); } BaseType_t give() { return xSemaphoreGive(xSema); } BaseType_t take(TickType_t xTicksToWait = portMAX_DELAY) { return xSemaphoreTake(xSema, xTicksToWait); } }; 

Deklarasi semafor

 Sema xSema; 

Menangkap semaphore

  xSema.take(); 

Rilis Semaphore

  xSema.give(); 

Sekarang mutex

 class Mutex { SemaphoreHandle_t xMutex; StaticSemaphore_t xMutexControlBlock; public: Mutex() { xMutex = xSemaphoreCreateMutexStatic(&xSemaControlBlock); } BaseType_t lock(TickType_t xTicksToWait = portMAX_DELAY) { return xSemaphoreTake(xMutex, xTicksToWait); } BaseType_t unlock() { return xSemaphoreGive(xMutex); } }; 

Seperti yang Anda lihat, kelas mutex hampir identik dengan kelas semaphore. Tetapi seperti yang saya katakan secara semantik, ini adalah entitas yang berbeda. Selain itu, antarmuka dari kelas-kelas ini tidak lengkap, dan mereka akan berkembang ke arah yang sama sekali berbeda. Jadi, metode giveFromISR () dan takeFromISR () dapat ditambahkan ke semaphore untuk bekerja dengan semaphore dalam interrupt, sementara mutex hanya memiliki metode tryLock () yang ditambahkan - ia tidak memiliki operasi lain secara semantik.

Saya harap Anda tahu perbedaan antara semaphore biner dan mutex.
Saya selalu menanyakan pertanyaan ini saat wawancara dan, sayangnya, 90% kandidat tidak memahami perbedaan ini. Bahkan, semafor dapat ditangkap dan dilepaskan dari utas yang berbeda. Di atas, saya menyebutkan mode semafor sinyal-tunggu ketika satu utas mengirim sinyal (panggilan memberi ()), dan yang lainnya menunggu sinyal (dengan fungsi take ()).

Mutex, sebaliknya, hanya bisa dilepaskan dari aliran yang sama (tugas) yang menangkapnya. Saya tidak yakin FreeRTOS memonitor ini, tetapi beberapa sistem operasi (misalnya, Linux) mengikuti ini dengan sangat ketat.

Mutex dapat digunakan dalam gaya C, mis. langsung memanggil kunci () / membuka kunci (). Tetapi karena kita menulis dalam C ++, kita dapat mengambil keuntungan dari pesona RAII dan menulis pembungkus yang lebih nyaman yang akan menangkap dan melepaskan mutex itu sendiri.

 class MutexLocker { Mutex & mtx; public: MutexLocker(Mutex & mutex) : mtx(mutex) { mtx.lock(); } ~MutexLocker() { mtx.unlock(); } }; 

Saat meninggalkan ruang lingkup, mutex akan dibebaskan secara otomatis.

Ini sangat nyaman jika ada beberapa pintu keluar dari fungsi dan Anda tidak perlu terus mengingat kebutuhan akan sumber daya gratis.

  MutexLocker lock(xMutex); Serial.println(value); } // mutex will be unlocked here 

Sekarang giliran timer.

 class Timer { TimerHandle_t xTimer; StaticTimer_t xTimerControlBlock; public: Timer(const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction) { xTimer = xTimerCreateStatic(pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, &xTimerControlBlock); } void start(TickType_t xTicksToWait = 0) { xTimerStart(xTimer, xTicksToWait); } }; 

Secara umum, semua yang ada di sini mirip dengan kelas-kelas sebelumnya, saya tidak akan membahas secara rinci. Mungkin API menyisakan banyak yang diinginkan, baik, atau setidaknya membutuhkan ekspansi. Tetapi tujuan saya adalah untuk menunjukkan prinsip, dan tidak membawanya ke keadaan produksi siap.

Dan akhirnya, tugasnya. Setiap tugas memiliki tumpukan dan harus ditempatkan di memori terlebih dahulu. Kami akan menggunakan teknik yang sama seperti dengan antrian - kami akan menulis kelas templat

 template<const uint32_t ulStackDepth> class Task { protected: StaticTask_t xTaskControlBlock; StackType_t xStack[ ulStackDepth ]; TaskHandle_t xTask; public: Task(TaskFunction_t pxTaskCode, const char * const pcName, void * const pvParameters, UBaseType_t uxPriority) { xTask = xTaskCreateStatic(pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, xStack, &xTaskControlBlock); } }; 

Karena objek tugas sekarang dinyatakan sebagai variabel global, mereka akan diinisialisasi sebagai variabel global - sebelum memanggil main (). Ini berarti bahwa parameter yang ditransfer ke tugas juga harus diketahui pada tahap ini. Nuansa ini harus diperhitungkan jika dalam kasus Anda sesuatu dilewatkan yang perlu dihitung sebelum membuat tugas (saya hanya punya NULL di sana). Jika ini masih tidak sesuai dengan Anda, pertimbangkan opsi dengan variabel statis lokal dari artikel sebelumnya .

Kompilasi dan dapatkan kesalahan:

 tasks.c:(.text.vTaskStartScheduler+0x10): undefined reference to `vApplicationGetIdleTaskMemory' timers.c:(.text.xTimerCreateTimerTask+0x1a): undefined reference to `vApplicationGetTimerTaskMemory' 

Ini masalahnya. Setiap OS memiliki tugas khusus - Idle Task (tugas default, tugas tidak melakukan apa-apa). Sistem operasi melakukan tugas ini jika semua tugas lain tidak dapat dilakukan (misalnya, tidur, atau menunggu sesuatu). Secara umum, ini adalah tugas yang paling umum, hanya dengan prioritas terendah. Tetapi di sini ia dibuat di dalam kernel FreeRTOS dan kami tidak dapat memengaruhi pembuatannya. Tetapi karena kami mulai menempatkan tugas secara statis, kami perlu memberi tahu OS di mana Anda ingin menempatkan unit kontrol dan tumpukan tugas ini. Itulah tujuan FreeRTOS dan meminta kami untuk mendefinisikan fungsi khusus vApplicationGetIdleTaskMemory ().

Situasi serupa terjadi pada tugas pengatur waktu. Pengatur waktu dalam sistem FreeRTOS tidak hidup sendiri - tugas khusus berputar di OS, yang melayani pengatur waktu ini. Dan tugas ini juga membutuhkan blok kontrol dan tumpukan. Dan seperti itu, OS meminta kami untuk menunjukkan di mana mereka menggunakan fungsi vApplicationGetTimerTaskMemory ().

Fungsi-fungsi itu sendiri sepele dan hanya mengembalikan pointer yang sesuai ke objek yang dialokasikan secara statis.

 extern "C" void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) { static StaticTask_t Idle_TCB; static StackType_t Idle_Stack[configMINIMAL_STACK_SIZE]; *ppxIdleTaskTCBBuffer = &Idle_TCB; *ppxIdleTaskStackBuffer = Idle_Stack; *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; } extern "C" void vApplicationGetTimerTaskMemory (StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize) { static StaticTask_t Timer_TCB; static StackType_t Timer_Stack[configTIMER_TASK_STACK_DEPTH]; *ppxTimerTaskTCBBuffer = &Timer_TCB; *ppxTimerTaskStackBuffer = Timer_Stack; *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; } 

Mari kita lihat apa yang kita dapat.

Saya akan menyembunyikan kode pembantu di bawah spoiler, Anda baru saja melihatnya
 template<class T, size_t size> class Queue { QueueHandle_t xHandle; StaticQueue_t xQueueDefinition; T xStorage[size]; public: Queue() { xHandle = xQueueCreateStatic(size, sizeof(T), reinterpret_cast<uint8_t*>(xStorage), &xQueueDefinition); } bool receive(T * val, TickType_t xTicksToWait = portMAX_DELAY) { return xQueueReceive(xHandle, val, xTicksToWait); } bool send(const T & val, TickType_t xTicksToWait = portMAX_DELAY) { return xQueueSend(xHandle, &val, xTicksToWait); } }; class Sema { SemaphoreHandle_t xSema; StaticSemaphore_t xSemaControlBlock; public: Sema() { xSema = xSemaphoreCreateBinaryStatic(&xSemaControlBlock); } BaseType_t give() { return xSemaphoreGive(xSema); } BaseType_t take(TickType_t xTicksToWait = portMAX_DELAY) { return xSemaphoreTake(xSema, xTicksToWait); } }; class Mutex { SemaphoreHandle_t xMutex; StaticSemaphore_t xMutexControlBlock; public: Mutex() { xMutex = xSemaphoreCreateMutexStatic(&xMutexControlBlock); } BaseType_t lock(TickType_t xTicksToWait = portMAX_DELAY) { return xSemaphoreTake(xMutex, xTicksToWait); } BaseType_t unlock() { return xSemaphoreGive(xMutex); } }; class MutexLocker { Mutex & mtx; public: MutexLocker(Mutex & mutex) : mtx(mutex) { mtx.lock(); } ~MutexLocker() { mtx.unlock(); } }; class Timer { TimerHandle_t xTimer; StaticTimer_t xTimerControlBlock; public: Timer(const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction) { xTimer = xTimerCreateStatic(pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, &xTimerControlBlock); } void start(TickType_t xTicksToWait = 0) { xTimerStart(xTimer, xTicksToWait); } }; template<const uint32_t ulStackDepth> class Task { protected: StaticTask_t xTaskControlBlock; StackType_t xStack[ ulStackDepth ]; TaskHandle_t xTask; public: Task(TaskFunction_t pxTaskCode, const char * const pcName, void * const pvParameters, UBaseType_t uxPriority) { xTask = xTaskCreateStatic(pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, xStack, &xTaskControlBlock); } }; extern "C" void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) { static StaticTask_t Idle_TCB; static StackType_t Idle_Stack[configMINIMAL_STACK_SIZE]; *ppxIdleTaskTCBBuffer = &Idle_TCB; *ppxIdleTaskStackBuffer = Idle_Stack; *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; } extern "C" void vApplicationGetTimerTaskMemory (StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize) { static StaticTask_t Timer_TCB; static StackType_t Timer_Stack[configTIMER_TASK_STACK_DEPTH]; *ppxTimerTaskTCBBuffer = &Timer_TCB; *ppxTimerTaskStackBuffer = Timer_Stack; *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; } 


Kode untuk seluruh program.

 Timer xTimer("Timer", 1000, pdTRUE, NULL, vTimerCallback); Sema xSema; Mutex xMutex; Queue<int, 1000> xQueue; Task<configMINIMAL_STACK_SIZE> task1(vTask1, "Task 1", NULL, tskIDLE_PRIORITY); Task<configMINIMAL_STACK_SIZE> task2(vTask2, "Task 2", NULL, tskIDLE_PRIORITY); void vTimerCallback(TimerHandle_t pxTimer) { xSema.give(); MutexLocker lock(xMutex); Serial.println("Test"); } void vTask1(void *) { while(1) { xSema.take(); int value = random(1000); xQueue.send(value); } } void vTask2(void *) { while(1) { int value; xQueue.receive(&value); MutexLocker lock(xMutex); Serial.println(value); } } void setup() { Serial.begin(9600); xTimer.start(); vTaskStartScheduler(); } void loop() {} 

Anda dapat membongkar biner yang dihasilkan dan melihat apa dan bagaimana lokasinya (output dari objdump sedikit diwarnai untuk keterbacaan yang lebih baik):

 0x200000b0 .bss 512 vApplicationGetIdleTaskMemory::Idle_Stack 0x200002b0 .bss 92 vApplicationGetIdleTaskMemory::Idle_TCB 0x2000030c .bss 1024 vApplicationGetTimerTaskMemory::Timer_Stack 0x2000070c .bss 92 vApplicationGetTimerTaskMemory::Timer_TCB 0x200009c8 .bss 608 task1 0x20000c28 .bss 608 task2 0x20000e88 .bss 84 xMutex 0x20000edc .bss 4084 xQueue 0x20001ed0 .bss 84 xSema 0x20001f24 .bss 48 xTimer 

Tujuan tercapai - sekarang semuanya dalam tampilan penuh. Setiap objek terlihat dan ukurannya dapat dipahami (well, kecuali bahwa objek majemuk dari jenis Tugas mempertimbangkan semua suku cadangnya dalam satu bagian). Statistik kompiler juga sangat akurat dan kali ini sangat berguna.

 Sketch uses 20,800 bytes (15%) of program storage space. Maximum is 131,072 bytes. Global variables use 9,332 bytes (45%) of dynamic memory, leaving 11,148 bytes for local variables. Maximum is 20,480 bytes. 

Kesimpulan


Meskipun FreeRTOS memungkinkan Anda untuk membuat dan menghapus tugas, antrian, semafor, dan mutex dengan cepat, dalam banyak kasus ini tidak perlu. Sebagai aturan, itu sudah cukup untuk membuat semua objek di mulai sekali dan mereka akan bekerja sampai reboot berikutnya. Dan ini adalah alasan yang baik untuk mendistribusikan benda-benda seperti itu secara statis pada tahap kompilasi. Sebagai hasilnya, kita mendapatkan pemahaman yang jelas tentang memori yang ditempati oleh objek kita, di mana yang terletak dan berapa banyak memori yang tersisa.

Jelas bahwa metode yang diusulkan hanya cocok untuk menempatkan objek yang masa pakainya sebanding dengan masa pakai seluruh aplikasi. Jika tidak, Anda harus menggunakan memori dinamis.

Selain penempatan statis objek FreeRTOS, kami juga menulis pembungkus yang nyaman di atas primitif FreeRTOS, yang memungkinkan kami untuk menyederhanakan kode klien dan juga merangkum

Antarmuka dapat disederhanakan jika perlu (misalnya, tidak memeriksa kode kembali, atau tidak menggunakan batas waktu). Perlu juga dicatat bahwa implementasinya tidak lengkap - saya tidak repot dengan implementasi semua metode yang mungkin untuk mengirim dan menerima pesan melalui antrian (misalnya, dari interupsi, mengirim ke awal atau akhir antrian), saya tidak mengimplementasikan primitif sinkronisasi dari interupsi, menghitung (non-biner) semaphore, dan masih banyak lagi.

Saya terlalu malas untuk membawa kode ini ke "take and use" state, saya hanya ingin menunjukkan idenya. Tapi siapa yang butuh perpustakaan siap pakai, saya baru saja menemukan perpustakaan frt . Semua yang ada di dalamnya praktis sama, hanya diingatkan. Ya, antarmuka sedikit berbeda.

Contoh dari artikel ada di sini .

Terima kasih semua telah membaca artikel ini sampai akhir. Saya akan senang menerima kritik yang membangun. Ini juga akan menarik bagi saya untuk membahas nuansa dalam komentar.

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


All Articles