Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кармин Новиелло - Освоение STM32.pdf
Скачиваний:
2740
Добавлен:
23.09.2021
Размер:
47.68 Mб
Скачать

Запуск FreeRTOS

661

23.9. Возможности отладки

Отладка микропрограммы, построенной с использованием ОСРВ, не может быть тривиальной. Переключение контекста может усложнить выполнение пошаговой отладки. FreeRTOS предлагает некоторые возможности отладки, часть из которых полезны, особенно когда ваш проект использует много потоков, порождаемых динамически.

23.9.1. Макрос configASSERT()

Исходный код FreeRTOS полон обращений к макросу configASSERT(). Это пустой макрос, который разработчики могут определить внутри FreeRTOSConfig.h, и он играет ту же роль, что и функция Си assert(). CubeMX автоматически определяет его для нас следующим образом:

#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}

Макрос работает так, что если assert-условие является ложным, то все прерывания запрещаются (путем установки регистра PRIMASK на ядрах Cortex-M0/0+ и увеличения значения BASEPRI в других микроконтроллерах STM32) и выполняется бесконечный цикл. В то время как данное поведение нормально во время сеанса отладки, оно может стать источником уймы головных болей, если наше устройство не работает под отладчиком, поскольку трудно сказать, почему перестала работать микропрограмма. Поэтому автор книги предпочитает определять макрос другими способами:

void __configASSERT(uint8_t x) { if ((x) == 0) {

taskDISABLE_INTERRUPTS();

if((CoreDebug->DHCSR & 0x1) == 0x1) { /* Если под отладкой */ HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);

HAL_Delay(1000); asm("BKTP #0");

}else {

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); HAL_Delay(100);

}

}

}

#define configASSERT( x ) __configASSERT(x)

Функция __configASSERT() использует интерфейс CoreDebug ядра Cortex-M для проверки того, находится ли микроконтроллер в состоянии отладки: отладчики устанавливают первый бит регистра управления и состояния приостановки ядра отладкой (Debug Halting Control and Status Register, DHCSR), когда микроконтроллер находится в состоянии отладки. Если это так, то устанавливается программная точка останова, когда assertусловие ложно. Однако данная функция имеет два значительных ограничения:

она работает только в микроконтроллерах на базе Cortex-M3/4/7;

регистр DHCSR не сбрасывается в ноль, когда происходит системный сброс, также невозможно сбросить первый бит из микропрограммы; это означает, что нам нужно полностью выключить устройство, иначе микропрограмма зависнет, если assert-условие ложно.

Запуск FreeRTOS

662

23.9.2.Статистика среды выполнения и информация о состоянии потоков

Когда потоки создаются динамически, трудно отслеживать их жизненный цикл. FreeRTOS предоставляет способы получения как полного списка активных потоков, так и некоторой соответствующей информации об их состоянии.

Функция uxTaskGetNumberOfTasks() возвращает количество существующих потоков. Под термином существующие потоки (live threads) мы подразумеваем все потоки, эффективно выделенные ядром, даже те, которые помечены как удаленные (deleted)43. Функция

UBaseType_t uxTaskGetSystemState(TaskStatus_t * const pxTaskStatusArray,

const UBaseType_t uxArraySize, unsigned long * const pulTotalRunTime );

возвращает информацию о состоянии каждого потока в системе, заполняя экземпляр структуры TaskStatus_t для каждого потока. Структура TaskStatus_t определена следующим образом:

typedef struct xTASK_STATUS {

 

 

TaskHandle_t xHandle;

/* Дескриптор потока, к которому относится остальная

 

 

информация в структуре

*/

const char *pcTaskName;

/* Указатель на имя потока

*/

UBaseType_t xTaskNumber;

/* Соответствует идентификатору потока (Thread ID)

*/

eTaskState eCurrentState;

/* Состояние, в котором существовал поток при

 

 

заполнении структуры

*/

UBaseType_t uxCurrentPriority; /* Приоритет, с которым выполнялся поток

*/

UBaseType_t uxBasePriority;

/* Приоритет, к которому вернется поток, если текущий

 

 

приоритет потока был унаследован, чтобы избежать

 

 

неограниченной инверсии приоритета при получении

 

 

мьютекса. Действителен, только если configUSE_MUTEXES

 

определен как 1 в FreeRTOSConfig.h.

*/

uint32_t ulRunTimeCounter;

/* Общее время выполнения, выделенное потоку до текущего

 

времени, как определено тактовым сигналом статистики

 

времени выполнения (run time stats clock).

*/

uint16_t usStackHighWaterMark; /* Минимальный объем стекового пространства,

 

 

оставшегося для потока с момента его создания.

*/

} TaskStatus_t;

 

 

uxTaskGetSystemState() принимает предварительно выделенный массив, содержащий экземпляры структур TaskHandle_t для каждого потока, максимальное количество элементов, которое может содержать массив (uxArraySize), и указатель на переменную (pulTotalRunTime), которая будет содержать общее время выполнения с момента запуска ядра. Фактически FreeRTOS может дополнительно собирать информацию о количестве времени обработки, которое использовалось каждым потоком. Статистика среды выполнения должна быть явно включена путем определения макроса configGENERATE_RUN_TIME_STATS в FreeRTOSConfig.h. Более того, эта функция требует, чтобы мы использовали другой таймер, отличный от того, который использовался для реализации

43 Удаленные потоки обычно удерживаются в памяти в течение очень короткого времени. Когда поток помечается для удаления, он фактически удаляется из системы холостым потоком idle.

Запуск FreeRTOS

663

счетчика тиков. Это связано с тем, что временной отсчет статистики среды выполнения должен иметь более высокое разрешение, чем прерывание от тика, иначе статистика может быть слишком неточной, чтобы быть действительно полезной.

Если функции потока хорошо спроектированы и не используют циклы активного ожидания, обычно поток длится меньше времени тика, равного 1 мс и представляющего собой максимальный временной интервал, выделенный для потока. Однако статистика среды выполнения работает так, что перед тем, как поток будет переведен в состояние «выполняется», текущее значение таймера, используемого для статистики, сохраняется. Когда поток выходит из состояния «выполняется» (либо из-за того, что он уступает управление, либо из-за того, что истек его квант времени), выполняется сравнение между предыдущим сохраненным временем и текущим. Если для этой операции используется таймер тиков, то эта разница всегда равна нулю. По этой причине рекомендуется сконфигурировать генератор временного отсчета для статистики в 10-100 раз быстрее, чем прерывание от тика. Чем быстрее временной отсчет, тем более точной будет статистика, но при этом быстрее будет переполнено значение таймера.

Когда макрос configGENERATE_RUN_TIME_STATS установлен в 1, мы должны предоставить

два дополнительных макроса. Первый, portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(), ис-

пользуется для конфигурации таймера, необходимого для статистики среды выполнения. Второй, portGET_RUN_TIME_COUNTER_VALUE(), используется FreeRTOS для получения совокупного значения счетчика таймера. Поскольку этот таймер должен работать очень быстро, не рекомендуется настраивать его ISR и увеличивать глобальную переменную по его истечении: это повлияет на общую производительность системы. В микроконтроллерах STM32, предоставляющих 32-разрядные таймеры, достаточно использовать один из них, установив значение Period максимальным (0xFFFFFFFF). Другой альтернативой в Cortex-M3/4/7 является использование счетчика тактовых циклов DWT, как было показано в Главе 11. В следующем коде показана возможная реализация для двух макросов:

#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()

\

do {

\

DWT->CTRL |= 1 ; /* разрешение счетчика */

\

DWT->CYCCNT = 0;

\

}while(0)

 

#define portGET_RUN_TIME_COUNTER_VALUE() DWT->CYCCNT

А теперь мы собираемся проанализировать полную реализацию трассировки, заключающуюся в наличии специального потока, который выводит через интерфейс UART2 информацию статистики при нажатии пользовательской кнопки USER платы Nucleo.

Имя файла: src/main-ex7.c

32void threadsDumpThread(void const *argument) {

33TaskStatus_t *pxTaskStatusArray = NULL;

34char *pcBuf = NULL;

35char *pcStatus;

36uint32_t ulTotalRuntime;

37

38while(1) {

39if(HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == GPIO_PIN_RESET) {

Запуск FreeRTOS

664

40/* Выделение буфера сообщений. */

41pcBuf = pvPortMalloc(100 * sizeof(char));

43/* Выделение индекса массива для каждой задачи. */

44pxTaskStatusArray = pvPortMalloc(xTaskGetNumberOfTasks() * sizeof(TaskStatus_t));

46if(pcBuf != NULL && pxTaskStatusArray != NULL) {

47/* Генерирование (бинарных) данных. */

48uxTaskGetSystemState(pxTaskStatusArray, uxTaskGetNumberOfTasks(), &ulTotalRuntime)

50

sprintf(pcBuf, "

LIST OF RUNNING THREADS

\r\n

51

-----------------------------------------

 

\r\n");

52 HAL_UART_Transmit(&huart2, (uint8_t*)pcBuf, strlen(pcBuf), HAL_MAX_DELAY); 53

54for(uint16_t i = 0; i < uxTaskGetNumberOfTasks(); i++ ) {

55sprintf(pcBuf, "Thread: %s\r\n", pxTaskStatusArray[i].pcTaskName);

56HAL_UART_Transmit(&huart2, (uint8_t*)pcBuf, strlen(pcBuf), HAL_MAX_DELAY);

58sprintf(pcBuf, "Thread ID: %lu\r\n", pxTaskStatusArray[i].xTaskNumber);

59HAL_UART_Transmit(&huart2, (uint8_t*)pcBuf, strlen(pcBuf), HAL_MAX_DELAY);

61

sprintf(pcBuf, "\tStatus: %s\r\n",

62

pcConvertThreadState(pxTaskStatusArray[i].eCurrentState));

63

HAL_UART_Transmit(&huart2, (uint8_t*)pcBuf, strlen(pcBuf), HAL_MAX_DELAY);

64

 

65

sprintf(pcBuf, "\tStack watermark number: %d\r\n",

66

pxTaskStatusArray[i].usStackHighWaterMark);

67

HAL_UART_Transmit(&huart2, (uint8_t*)pcBuf, strlen(pcBuf), HAL_MAX_DELAY);

68

 

69sprintf(pcBuf, "\tPriority: %lu\r\n", pxTaskStatusArray[i].uxCurrentPriority);

70HAL_UART_Transmit(&huart2, (uint8_t*)pcBuf, strlen(pcBuf), HAL_MAX_DELAY);

71

 

72

sprintf(pcBuf, "\tRun-time time in percentage: %f\r\n",

73

((float)pxTaskStatusArray[i].ulRunTimeCounter/ulTotalRuntime)*100);

74HAL_UART_Transmit(&huart2, (uint8_t*)pcBuf, strlen(pcBuf), HAL_MAX_DELAY);

75}

76vPortFree(pcBuf);

77vPortFree(pxTaskStatusArray);

78}

79}

80osDelay(100);

Код должен быть довольно простым для понимания. Когда нажата пользовательская кнопка USER, этот поток выделяет буфер (pxTaskStatusArray), который будет содержать структуры TaskStatus_t для каждого потока в системе. uxTaskGetSystemState() в строке 48 заполняет данный массив, и для каждого потока, содержащегося в нем, через VCP платы Nucleo в терминал выводится некоторая статистика.

В то время как uxTaskGetSystemState() заполняет структуру TaskStatus_t для каждого потока в системе, vTaskGetInfo() заполняет структуры TaskStatus_t только для одной