Programming is like sex. One mistake and you have to support it for the rest of your life. — Michael Sinz
Продолжаем осваивать FreeRTOS.
Рассмотренный ранее пример отлично работает, но присутствуют элементы магии — например какой размер стека нужно выделять под задачу да и вообще как правильно ловить ошибки ядра RTOS, которые нельзя проверить на этапе компиляции приложения ? Во FreeRTOS для этих целей предусмотрены следующие механизмы:
-
макрос
configASSERT()активно используется ядром, а разработчик приложения может (должен) задать свою реализацию в зависимости от доступного устройства вывода в конкретном устройстве -
во FreeRTOS реализован собственный механизм динамического выделения памяти, а для диагностики подобного рода ошибок предусмотрены специальные перехватчики (хуки)
configUSE_MALLOC_FAILED_HOOKиconfigCHECK_FOR_STACK_OVERFLOWи возможно что-то еще — доки в руки
В нашем устройстве присутствует монохромный дисплей и туда же будем кидать все диагностированные ошибки. Будет две задачи — анимация на левых светодиодах и вывод различной системной информации на экран. При обнаружении ошибки ядра или выделения памяти будет «BSoD» — не баг, это фича. Будем надеяться заказчик нашего устройства его не увидит — для этого в релизе, уже после того как всё заработает как надо, диагностику ошибок FreeRTOS можно (даже нужно) просто закомментировать.

Вывод текста на экран осуществляется функциями с префиксом draw, как это работает было рассмотрено в одном из предыдущих материалов. После внимательного изучения официальной документации добавим в где-то в конец файла конфигурации FreeRTOSConfig.h следующее:
FreeRTOSConfig.h
// tutorial: 2
extern void vAssertCalled( unsigned long, const char * const );
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __LINE__, __FILE__ )
// Method 2 is a bit slower than method 1
// but it will most likely catch all stack overflows.
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_MALLOC_FAILED_HOOK 1
hooks.c
#include <FreeRTOS.h>
#include <task.h>
#include "draw.h"
void vApplicationStackOverflowHook( xTaskHandle *pxTsk, signed portCHAR *pcTskNm );
void vApplicationStackOverflowHook( xTaskHandle *pxTsk, signed portCHAR *pcTskNm ) {
taskDISABLE_INTERRUPTS(); // game over
draw_clr();
draw_str(1, 0, "Error: StackOverflow");
draw_str(1, 2, "Task Name:");
draw_str(1 + (6 * sizeof("Task Name:")), 2, (char*)pcTskNm);
for( ;; );
}
void vApplicationMallocFailedHook( void );
void vApplicationMallocFailedHook( void ) {
taskDISABLE_INTERRUPTS(); // game over
draw_clr();
draw_str(1, 0, "Error: MallocFailed");
for( ;; );
}
void vAssertCalled( unsigned long ulLine, const char * const pcFileName ) {
taskDISABLE_INTERRUPTS(); // game over
draw_clr();
draw_str(1, 0, "Error: AssertCalled");
draw_str_hex(2, "File Name:", ulLine);
for( ;; );
}
Задача с анимациями для левых светодиодов почти ничем не отличается от предыдущего материала, только вместо API временной задержки vTaskDelay теперь vTaskDelayUntil, которая точнее первой. Для данной задачи точность не критична, но при передаче в функцию vTaskDelayUntil неправильных аргументов сработает configASSERT, что даст возможность проверить саму проверку:
task_l_leds.c
#include <FreeRTOS.h>
#include <task.h>
#include "task_l_leds.h"
static portTickType xLastWakeTime;
static void rotate_rgb(volatile unsigned char * const port) {
*port = 0b10000;
do {
vTaskDelayUntil( &xLastWakeTime, 5 /* ticks */ );
*port >>= 1;
} while (*port);
}
void vTaskLedsL( void *pvParameters ) {
xLastWakeTime = xTaskGetTickCount();
for( ;; ) {
rotate_rgb(&P4OUT);
rotate_rgb(&P5OUT);
rotate_rgb(&P6OUT);
}
vTaskDelete( NULL );
}
Вывод полезной текущей информации о системе мог бы выглядеть примерно следующим образом — опять-таки если хорошо покопаться в официальной документации к FreeRTOS то наверняка можно найти ещё какие-нибудь интересные штуковины, это только набросок того что попалось на глаза:
task_stat.c
#include <FreeRTOS.h>
#include <task.h>
#include "draw.h"
#include "task_stat.h"
void vTaskStat( void *pvParameters ) {
// once
for (uint8_t i = 0; i < (128 / 6); i++) {
draw_char(1 + (i * 6) , 3, '-');
}
draw_str_hex(4, "portBASE_TYPE", sizeof(portBASE_TYPE));
draw_str_hex(5, "portTickType", sizeof(portTickType));
draw_str_hex(6, "tick rate (Hz)", configTICK_RATE_HZ);
// loop
for( ;; ) {
draw_str_hex(0, "uptime", xTaskGetTickCount());
draw_str_hex(1, "freeHeapSize", xPortGetFreeHeapSize());
draw_str_hex(2, "numberOfTasks", uxTaskGetNumberOfTasks());
}
vTaskDelete( NULL );
}
И как вы думаете что произойдёт при запуске ? А получим мы вот такую красоту !

Как несложно догадаться сработал перехватчик vApplicationStackOverflowHook. Чтобы починить это достаточно увеличить configMINIMAL_STACK_SIZE с 50 до 100. Точно так же в качестве эксперимента можно теперь уменьшить configTOTAL_HEAP_SIZE скажем до 42 для проверки хука vApplicationMallocFailedHook:

Ну и напоследок достаточно при вызове vTaskDelayUntil передать в качестве любого аргумента 0 и сработает configASSERT, который знает имя файла и номер строки где случилась ошибка диагностики ядра:

Ещё у FreeRTOS есть один интересный перехватчик для задачи Бездействия (Idle task) — configUSE_IDLE_HOOK. Задача idle автоматически создаётся при запуске планировщика и она постоянно находится в состоянии готовности к выполнению. Ее приоритет задается макроопределением tskIDLE_PRIORITY как самый низкий в программе (обычно 0). Это гарантирует, что задача Бездействие не будет выполняться, пока в программе есть хотя бы одна задача в состоянии готовности к выполнению и как только появится любая готовая к выполнению задача, задача Бездействие будет ею вытеснена. Ещё эта задача занимается освобождением памяти, выделенной задачам, которые были удалены и поэтому в приложениях, использующих vTaskDelete(), важно обеспечить время для задачи idle. Также часто задачу Бездействие используют для того, чтобы вызывать режим энергосбережения процессора.
Далее о проблемах организации совместного доступа нескольких задач/прерываний к одному ресурсу.