FreeRTOS - диагностика ошибок и хуки

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 можно (даже нужно) просто закомментировать.

MSP430.js | исходники

screenshot

Вывод текста на экран осуществляется функциями с префиксом 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 );
}

И как вы думаете что произойдёт при запуске ? А получим мы вот такую красоту !

screenshot

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

screenshot

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

screenshot

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

Далее о проблемах организации совместного доступа нескольких задач/прерываний к одному ресурсу.

links

social