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. Также часто задачу Бездействие используют для того, чтобы вызывать режим энергосбережения процессора.
Далее о проблемах организации совместного доступа нескольких задач/прерываний к одному ресурсу.