Por defecto, todos los objetos en el sistema FreeRTOS se distribuyen dinámicamente: colas, semáforos, temporizadores, tareas (hilos) y mutexes. El programador solo ve el "montón": el área donde la memoria se asigna dinámicamente a pedido de un programa o sistema, y lo que sucede dentro no está claro. Cuanto queda Desconocido ¿Algo toma más de lo que necesitas? Quien sabe Personalmente, prefiero resolver los problemas de organización de la memoria incluso en la etapa de escritura del firmware, sin generar errores de tiempo de ejecución cuando la memoria finaliza inesperadamente.
Este artículo es una
continuación lógica
de ayer sobre la distribución estática de objetos en la memoria del microcontrolador, solo que ahora en relación con los objetos FreeRTOS. Hoy aprenderemos cómo colocar objetos FreeRTOS estáticamente, lo que nos permitirá comprender con mayor claridad lo que está sucediendo en la RAM del microcontrolador, cómo se ubican exactamente nuestros objetos y cuánto ocupan.
Pero simplemente tomar y comenzar a colocar objetos FreeRTOS estáticamente no requiere mucha inteligencia: a partir de la versión 9.0, FreeRTOS proporciona funciones para crear objetos colocados estáticamente. Dichas funciones tienen un sufijo estático en el nombre y estas funciones tienen una excelente documentación con ejemplos. Escribiremos envoltorios C ++ convenientes y hermosos sobre las funciones de FreeRTOS que no solo colocarán objetos estáticamente, sino que también ocultarán todos los menudillos y proporcionarán una interfaz más conveniente.
Este artículo está destinado a programadores principiantes, pero que ya están familiarizados con los conceptos básicos de FreeRTOS y las primitivas de la sincronización de programas multiproceso. Vamos
FreeRTOS es un sistema operativo para microcontroladores. Bueno, ok, no es un sistema operativo completo, sino una biblioteca que te permite ejecutar varias tareas en paralelo. FreeRTOS también permite a las tareas intercambiar mensajes a través de colas de mensajes, usar temporizadores y sincronizar tareas utilizando semáforos y mutexes.
En mi opinión, cualquier firmware en el que necesite hacer dos (o más) tareas simultáneamente puede resolverse de manera mucho más fácil y elegante si usa FreeRTOS. Por ejemplo, lea las lecturas de sensores lentos y al mismo tiempo sirva a la pantalla. Solo para que sin frenos, mientras se leen los sensores. En general, debe tener! Recomiendo mucho estudiar.
Como dije y escribí en un artículo anterior, no me gusta mucho el enfoque de crear objetos dinámicamente si conocemos su número y tamaño en la etapa de compilación. Si tales objetos se colocan estáticamente, entonces podemos obtener una imagen más clara y más comprensible de la asignación de memoria en el microcontrolador y, por lo tanto, evitar sorpresas cuando la memoria termina repentinamente.
Consideraremos los problemas de organización de la memoria FreeRTOS usando la placa BluePill en el microcontrolador STM32F103C8T6 como ejemplo. Para no preocuparnos por el compilador y el sistema de compilación, trabajaremos en el entorno ArduinoIDE, instalando soporte para esta placa. Hay varias implementaciones de Arduino para STM32, en principio, cualquiera servirá. He instalado
stm32duino de acuerdo con las instrucciones del proyecto Readme.md, un gestor de arranque como se menciona
en este artículo . FreeRTOS versión 10.0 se instala a través del administrador de la biblioteca ArduinoIDE. Compilador - gcc 8.2
Se nos ocurrirá una pequeña tarea experimental. Puede que no tenga mucho sentido práctico en esta tarea, pero se utilizarán todas las primitivas de sincronización que están en FreeRTOS. Algo como esto:
- 2 tareas (hilos) trabajan en paralelo
- también funciona un temporizador, que de vez en cuando envía una notificación a la primera tarea utilizando un semáforo en modo de espera de señal
- la primera tarea, después de recibir una notificación del temporizador, envía un mensaje (número aleatorio) a la segunda tarea a través de la cola
- el segundo, después de recibir el mensaje, lo imprime en la consola
- deje que la primera tarea también imprima algo en la consola, y para que no peleen la consola estará protegida por el mutex.
- el tamaño de la cola podría limitarse a un elemento, pero para hacerlo más interesante, colocamos 1000
La implementación estándar (de acuerdo con la documentación y los tutoriales) puede verse así.
#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() {}
Veamos qué sucede en la memoria del microcontrolador, si compila dicho código. Por defecto, todos los objetos FreeRTOS se colocan en la memoria dinámica. FreeRTOS proporciona hasta 5 implementaciones de administradores de memoria que son difíciles de implementar, pero en general tienen la misma tarea: cortar fragmentos de memoria para las necesidades de FreeRTOS y del usuario. Las piezas se cortan del montón general del microcontrolador (usando malloc) o usan su propio montón separado. El tipo de almacenamiento dinámico utilizado para nosotros no es importante; de todos modos, no podemos mirar dentro del almacenamiento dinámico.
Por ejemplo, para un montón del nombre FreeRTOS se verá así (salida de la utilidad objdump)
... 200009dc l O .bss 00002000 ucHeap ...
Es decir vemos una gran pieza, dentro de la cual se cortan todos los objetos FreeRTOS: semáforos, mutexes, temporizadores, colas e incluso las tareas mismas. Los últimos 2 puntos son muy importantes. Dependiendo del número de elementos, la cola puede ser bastante grande y se garantiza que las tareas ocuparán mucho espacio debido a la pila, que también se asigna junto con la tarea.
Sí, esta es una desventaja de la multitarea: cada tarea tendrá su propia pila. Además, la pila debe ser lo suficientemente grande como para que contenga no solo las llamadas y las variables locales de la tarea en sí, sino también la pila de interrupción, si esto ocurre. Bueno, dado que una interrupción puede ocurrir en cualquier momento, cada tarea debe tener una reserva en la pila en caso de interrupción. Además, los microcontroladores CortexM pueden tener interrupciones anidadas, por lo que la pila debe ser lo suficientemente grande como para acomodar todas las interrupciones si ocurren simultáneamente.
El tamaño de la pila de tareas se establece cuando la tarea es creada por el parámetro de la función xTaskCreate. El tamaño de la pila no puede ser menor que el parámetro configMINIMAL_STACK_SIZE (especificado en el archivo de configuración FreeRTOSConfig.h): esta es la misma reserva para las interrupciones. El tamaño de almacenamiento dinámico lo establece el parámetro configTOTAL_HEAP_SIZE y, en este caso, es de 8 kb.
¿Ahora intenta adivinar si todos nuestros objetos caben en un montón de 8 kb? ¿Y un par de objetos? ¿Y algunas tareas más?Con ciertas configuraciones de FreeRTOS, todos los objetos no cabían en el montón. Y se ve así: el programa simplemente no funciona. Es decir todo se compila, se vierte, pero luego el microcontrolador simplemente se cuelga y eso es todo. Y adivina que el problema es exactamente el tamaño del montón. Tuve que aumentar un montón a 12kb.
Detener, ¿cuáles son las variables xTimer, xQueue, xSemaphore y xMutex? ¿No describen los objetos que necesitamos? No, estos son solo identificadores: punteros a una determinada estructura (opaca), que describe los propios objetos de sincronización
200009cc g O .bss 00000004 xTimer 200009d0 g O .bss 00000004 xSemaphore 200009cc g O .bss 00000004 xQueue 200009d4 g O .bss 00000004 xMutex
Como ya mencioné, propongo reparar todo este desastre de la misma manera que en el artículo anterior: distribuiremos todos nuestros objetos estáticamente en la etapa de compilación. Las funciones de distribución estática están disponibles si el parámetro configSUPPORT_STATIC_ALLOCATION se establece en 1 en el archivo de configuración de FreeRTOS.
Comencemos con las líneas. Así es como la documentación en FreeRTOS ofrece asignar filas
struct AMessage { char ucMessageID; char ucData[ 20 ]; }; #define QUEUE_LENGTH 10 #define ITEM_SIZE sizeof( uint32_t )
En este ejemplo, la cola se describe mediante tres variables:
- La matriz ucQueueStorage es donde se colocarán los elementos de la cola. El tamaño de la cola lo establece el usuario para cada cola individualmente.
- La estructura xQueueBuffer: aquí vive la descripción y el estado de la cola, el tamaño actual, las listas de tareas pendientes, así como otros atributos y campos que FreeRTOS necesita para trabajar con la cola. El nombre de la variable, en mi opinión, no es del todo exitoso, en FreeRTOS se llama QueueDefinition (descripción de la cola).
- La variable xQueue1 es el identificador de la cola (identificador). Todas las funciones de gestión de colas, así como algunas otras (por ejemplo, funciones internas para trabajar con temporizadores, semáforos y mutexes) aceptan dicho identificador. De hecho, esto es solo un puntero a QueueDefinition, pero no lo sabemos (por así decirlo) y, por lo tanto, el controlador tendrá que extraerse por separado.
Hacer como en el ejemplo, por supuesto, no será un problema. Pero personalmente, no me gusta tener hasta 3 variables por entidad. Una clase que puede encapsularlo ya lo está pidiendo. Solo un problema: el tamaño de cada cola puede variar. En un lugar necesita una cola más grande, en otro, un par de elementos son suficientes. Como queremos hacer cola de forma estática, de alguna manera debemos especificar este tamaño en tiempo de compilación. Puedes usar la plantilla para esto.
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); } };
Al mismo tiempo, las funciones de envío y recepción de mensajes, que fueron inmediatamente convenientes para nosotros, también se establecieron en esta clase.
La cola se declarará como una variable global, algo como esto
Queue<int, 1000> xQueue;
Envío de mensajes
xQueue.send(value);
Recibir mensaje
int value; xQueue.receive(&value);
Ahora tratemos con los semáforos. Y aunque técnicamente (dentro de FreeRTOS) los semáforos y mutexes se implementan a través de colas, semánticamente son 3 primitivas diferentes. Por lo tanto, los implementaremos en clases separadas.
La implementación de la clase de semáforo será bastante trivial: simplemente almacena varias variables y declara varias funciones.
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); } };
Declaración de semáforo
Sema xSema;
Captura de semáforo
xSema.take();
Lanzamiento de semáforo
xSema.give();
Ahora 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); } };
Como puede ver, la clase mutex es casi idéntica a la clase de semáforo. Pero como dije semánticamente, estas son entidades diferentes. Además, las interfaces de estas clases no están completas y se expandirán en direcciones completamente diferentes. Por lo tanto, los métodos giveFromISR () y takeFromISR () se pueden agregar al semáforo para trabajar con el semáforo en la interrupción, mientras que el mutex solo tiene el método tryLock () agregado; no tiene otras operaciones semánticamente.
Espero que sepas la diferencia entre un semáforo binario y un mutex.Siempre hago esta pregunta en las entrevistas y, desafortunadamente, el 90% de los candidatos no entiende esta diferencia. De hecho, un semáforo puede ser capturado y liberado desde diferentes hilos. Anteriormente, mencioné el modo de semáforo de señal de espera cuando un hilo envía una señal (las llamadas give ()), y el otro espera una señal (con la función take ()).
Mutex, por el contrario, solo se puede liberar de la misma secuencia (tarea) que lo capturó. No estoy seguro de que FreeRTOS monitoree esto, pero algunos sistemas operativos (por ejemplo, Linux) lo siguen de manera bastante estricta.
Mutex se puede usar en estilo C, es decir Llamar directamente a lock () / unlock (). Pero como estamos escribiendo en C ++, podemos aprovechar los encantos de RAII y escribir un contenedor más conveniente que capturará y liberará el mutex mismo.
class MutexLocker { Mutex & mtx; public: MutexLocker(Mutex & mutex) : mtx(mutex) { mtx.lock(); } ~MutexLocker() { mtx.unlock(); } };
Al salir del alcance, el mutex se liberará automáticamente.
Esto es especialmente conveniente si hay varias salidas de la función y no necesita recordar constantemente la necesidad de liberar recursos.
MutexLocker lock(xMutex); Serial.println(value); }
Ahora es el turno de los temporizadores.
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); } };
En general, todo aquí es similar a las clases anteriores, no me detendré en detalle. Quizás la API deja mucho que desear, bueno, o al menos requiere expansión. Pero mi objetivo es mostrar el principio y no llevarlo al estado de producción listo.
Y finalmente, las tareas. Cada tarea tiene una pila y debe colocarse en la memoria de antemano. Usaremos la misma técnica que con las colas: escribiremos una clase de plantilla
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); } };
Dado que los objetos de tarea ahora se declaran como variables globales, se inicializarán como variables globales, antes de llamar a main (). Esto significa que los parámetros que se transfieren a las tareas también deben conocerse en esta etapa. Este matiz debe tenerse en cuenta si en su caso se pasa algo que debe calcularse antes de crear la tarea (solo tengo NULL allí). Si esto aún no le conviene, considere la opción con variables locales estáticas
del artículo anterior .
Compila y obtén el error:
tasks.c:(.text.vTaskStartScheduler+0x10): undefined reference to `vApplicationGetIdleTaskMemory' timers.c:(.text.xTimerCreateTimerTask+0x1a): undefined reference to `vApplicationGetTimerTaskMemory'
Aquí está la cosa. Cada sistema operativo tiene una tarea especial: Tarea inactiva (la tarea predeterminada, la tarea de no hacer nada). El sistema operativo realiza esta tarea si no se pueden realizar todas las demás tareas (por ejemplo, dormir o esperar algo). En general, esta es la tarea más común, solo con la prioridad más baja. Pero aquí se está creando dentro del núcleo FreeRTOS y no podemos influir en su creación. Pero desde que comenzamos a colocar tareas estáticamente, necesitamos decirle de alguna manera al sistema operativo dónde desea colocar la unidad de control y la pila de esta tarea. Para eso es FreeRTOS y nos pide que definamos una función especial vApplicationGetIdleTaskMemory ().
Una situación similar es con la tarea de los temporizadores. Los temporizadores en el sistema FreeRTOS no viven solos: una tarea especial está girando en el sistema operativo, que sirve a estos temporizadores. Y esta tarea también requiere un bloque de control y una pila. Y así, el sistema operativo nos pide que indiquemos dónde están utilizando la función vApplicationGetTimerTaskMemory ().
Las funciones en sí son triviales y simplemente devuelven los punteros correspondientes a los objetos asignados estáticamente.
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; }
Veamos que tenemos.
Ocultaré el código de los ayudantes debajo del spoiler, acabas de verlo 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; }
El código para todo el programa.
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() {}
Puede desmontar el binario resultante y ver qué y cómo está ubicado (la salida de objdump está ligeramente teñida para una mejor legibilidad):
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
El objetivo se logra: ahora todo está a la vista. Cada objeto es visible y su tamaño es comprensible (bueno, excepto que los objetos compuestos del tipo Tarea consideran todas sus piezas de repuesto en una sola pieza). Las estadísticas del compilador también son extremadamente precisas y esta vez muy útiles.
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.
Conclusión
Aunque FreeRTOS le permite crear y eliminar tareas, colas, semáforos y mutexes sobre la marcha, en muchos casos esto no es necesario. Como regla, es suficiente crear todos los objetos al inicio una vez y funcionarán hasta el próximo reinicio. Y esta es una buena razón para distribuir tales objetos estáticamente en la etapa de compilación. Como resultado, obtenemos una comprensión clara de la memoria ocupada por nuestros objetos, dónde se encuentra y cuánta memoria libre queda.
Es obvio que el método propuesto es adecuado solo para colocar objetos cuya vida útil es comparable a la vida útil de toda la aplicación. De lo contrario, debe usar memoria dinámica.
Además de la ubicación estática de los objetos FreeRTOS, también escribimos envoltorios convenientes sobre las primitivas FreeRTOS, lo que nos permitió simplificar un poco el código del cliente y también encapsular
La interfaz se puede simplificar si es necesario (por ejemplo, no verificar el código de retorno o no usar tiempos de espera). También vale la pena señalar que la implementación está incompleta: no me molesté con la implementación de todos los métodos posibles de envío y recepción de mensajes a través de la cola (por ejemplo, desde una interrupción, envío al principio o al final de la cola), no trabajé con primitivas de sincronización desde interrupciones, contando semáforos (no binarios), y mucho mas
Era demasiado vago para llevar este código al estado de "tomar y usar", solo quería mostrar la idea. Pero quién necesita una biblioteca preparada, acabo de encontrarme con la
biblioteca frt . Todo en él es prácticamente igual, solo que se me viene a la mente. Bueno, la interfaz es un poco diferente.
Un ejemplo del artículo está
aquí .
Gracias a todos por leer este artículo hasta el final. Estaré contento con la crítica constructiva. También será interesante para mí discutir los matices en los comentarios.