FreeRTOS: базовые техники отладки и поиска ошибок (FAQ)
Добавил(а) microsin
Предварительные замечания. Большинство запросов в техподдержку для микроконтроллеров ARM Cortex-M (например STM32) связано с некорректным назначением приоритетов прерываний (см. [2]). FreeRTOS V7.5.0 и более свежие релизы включают в код вызовы configASSERT() для перехвата этой общей для многих пользователей ошибки. Убедитесь, что при разработке определен макрос configASSERT(). когда приложение полностью отлажено, configASSERT и другие подобные проверки можно отключить.
Внимательно изучите принципы назначения приоритетов и вложенности прерываний в статье [2], и соответствующие настройки в файле FreeRTOSConfig.h. Также очень полезно руководство, помогающие найти причину ошибки ARM Cortex-M hard fault exception [3]. Если после изучения методов в этой статье (перевод [1]) Вы все еще сталкиваетесь с проблемами, то изучите ресурс [4] (там есть полезные ссылки на страницы официального сайта FreeRTOS.org), и также обратитесь к архиву официальной техподдержки [5]. Полный список ответов на часто задаваемые вопросы см. по ссылке [6], здесь рассмотрены только базовые техники поиска и отладки ошибок.
Приложение на основе FreeRTOS не работает. Как найти причину, что сделано не так? Ниже перечислены основные ситуации возникновения проблем, с которыми сталкиваются пользователи FreeRTOS.
1. Созданное приложение компилируется, но не работает. Каждый официальный порт FreeRTOS поставляется с официальным demo (по крайней мере до настоящего момента это было так), которое компилируется и запускается на аппаратной платформе, для которой разработано, без необходимости внесения какой-либо модификации. Demo-проекты предоставлены для того, чтобы новые пользователи могли максимально быстро и без лишней суеты начать работать с FreeRTOS (написание ПО для многозадачных систем и так сама по себе достаточно сложная задача). Всегда рекомендуется начинать разработку нового проекта FreeRTOS с готового простого и отлаженного примера, и потом доработать его до нужной кондиции, добавив необходимый функционал приложения. Все поставляемые примеры уже корректно сконфигурированы, в них содержатся все необходимые исходные файлы и файлы заголовок, в коде инсталлированы необходимые обработчики прерываний (ISR). Использование готовых примеров избавит начинающих пользователей FreeRTOS от лишних ошибок.
Если проект, который Вы создали, компилируется, и как минимум доходит до места запуска планировщика (вызов osKernelInitialize();), но после вызова планировщика (vTaskStartScheduler) выполняется только одна задача, или ни одна задача не выполняется, то вероятно, что некорректна таблица векторов прерываний. Все порты FreeRTOS используют прерывание таймера, и некоторые порты FreeRTOS используют несколько прерываний. В качестве примера используйте предоставляемые demo-проекты.
Специальное замечание для пользователей ARM Cortex-M: порты ARM Cortex-M3, ARM Cortex-M4 и ARM Cortex-M4F требуют установки обработчиков FreeRTOS на векторы прерываний SysTick, PendSV и SVCCall. Таблицу векторов можно непосредственно заполнить определенными в коде FreeRTOS функциями xPortSysTickHandler(), xPortPendSVHandler() и vPortSVCHandler() соответственно. Или если таблица векторов прерываний совместима с CMSIS, можно добавить три строки в FreeRTOSConfig.h, чтобы отобразить имена функций FreeRTOS в их эквиваленты CMSIS:
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
Использование определений #define в этом методе будет работать только если Вашими средствами разработки предоставлены обработчики по умолчанию (default handlers), определенные как weak-символы (что такое weak-символы, см. в статье [3]). Если обработчики по умолчанию не определены как weak-символы, то они должны быть закомментированы в коде, или удалены.
2. Стеки. Переполнение стека - наиболее часто встречающаяся причина обращения пользователей в службу поддержки. Размер доступного для задачи стека устанавливается в параметре usStackDepth API-функции xTaskCreate(), xTaskCreateStatic() или в поле stack_size структуры конфигурации задачи, передаваемой как параметр в osThreadNew().
Попробуйте увеличить размер стека, выделенного для задачи, в которой возникает проблема, или уменьшить использование стека в этой задаче. Не пишите такие обработчики прерываний, которые требуют большого пространства в стеке.
Задачи могут вызывать любые форматирующие строки функции, которые могут требовать большого места в стеке – в частности, когда используется компилятор GCC. Такие задачи особенно склонны к переполнению стека.
Каждый байт стека задачи заполняется байтами 0xA5 в момент создания задачи, поэтому относительно просто увидеть переполнение стека задачи. Дополнительно функция usTaskCheckFreeStackSpace() в модуле tasks.c демонстрирует, как можно проверить использование стека во время выполнения программы, runtime (хотя это несколько не эффективная функция, поэтому её следует использовать только для отладки).
Также см. API-функцию uxTaskGetStackHighWaterMark() [7] и опции для определения переполнения стека [8].
Я как раз столкнулся с падением системы FreeRTOS, когда происходило зависание в обработчике исключения void HardFault_Handler(void), потому что сконфигурировал задачу со слишком маленьким размером стека 128 байт (это значение взял из примера проекта, который сгенерировала утилита STM32CubeMX для микроконтроллера STM32F407). Определение и запуск задачи обработки принимаемых символов USART:
// Вернет ошибку, если формат является указателем NULL:if (!fmt) { return-1; }
// Вернет ошибку, если строка превышает размер буфера, с учетом// необходимых дополнительных 2 символов: CR и нулевой терминатор ASCIIZ:if (UPRINTF_BUF_SIZE-2<strlen(fmt)) { return-1; }
После увеличения .stack_size до 4096 байт проблема была решена.
3. Не выполняются прерывания. Сначала абсолютно точно убедитесь, что проблема связана с FreeRTOS - путем создания простого приложения с прерыванием, которое не использует FreeRTOS.
Если функция FreeRTOS API вызывается перед запуском планировщика, то прерывания намеренно останутся запрещенными, и не разрешаются снова до момента запуска (не создания!) первой задачи. Это сделано для защиты системы от сбоев, вызванных прерываниями, которые используют API-функции FreeRTOS при инициализации системы, перед запуском планировщика, когда планировщик может быть в противоречивом состоянии.
Не изменяйте биты разрешения прерываний микроконтроллера или флаги приоритетов, используя другие методы, кроме вызовов taskENTER_CRITICAL() и taskEXIT_CRITICAL(). Эти макросы сохраняют счетчик глубины их вложенных вызовов, чтобы гарантировать, что прерывания будут снова разрешены только когда все вложенные вызовы будут полностью развернуты (счетчик вложений обнулится). Имейте в виду, что некоторые библиотечные функции (сторонний код) могут разрешать или запрещать прерывания.
4. После добавления к demo простейшей задачи система начала падать. Создание задачи требует резервирования памяти, выделенной из кучи ядра (kernel heap). Многие demo-проекты приложений задают размер кучи по минимуму - только для запуска демонстрационных задач, но этого может быть недостаточно, чтобы добавить в проект другие задачи. когда Вы запускаете планировщик (RTOS scheduler), автоматически создается задача ожидания (idle task). Если недостаточен размер кучи для создания idle task, то из vTaskStartScheduler() произойдет выход – в результате приложение даже не запустится.
Чтобы устранить эту проблему, увеличьте объем памяти кучи или удалите некоторые задачи демонстрационного приложения.
5. Использование API-функций FreeRTOS в обработчиках прерываний. Не используйте API-функции в теле обработчиков прерываний (ISR), если имя этой функции не заканчивается на FromISR().
{spoiler title=Особое замечание для ARM Cortex-M3, ARM Cortex-M4 и ARM Cortex-M7 opened=0
Именно эта причина 95% обращений в службу поддержки пользователей микроконтроллеров ARM Cortex-M. Внимательно изучите страничку [2], где объясняются приоритеты прерываний ARM Cortex-M и их встраивание в модель вложенности прерываний FreeRTOS.
API-функции не должны вызываться из прерывания, если это прерывание имеет приоритет выше, чем приоритет, установленный #define-определением configMAX_SYSCALL_INTERRUPT_PRIORITY. Тщательно проверьте следующие моменты, когда устанавливаете приоритеты прерываний:
• configMAX_SYSCALL_INTERRUPT_PRIORITY определено в FreeRTOSConfig.h, и используемые прерывания имеют меньший приоритет, чем это значение. На микроконтроллерах ARM Cortex-M3, низкое числовое значение приоритета соответствует более высокому логическому приоритету прерывания. Не оставляйте не назначенными приоритеты прерываний, потому что по умолчанию приоритеты установлены 0. Приоритет 0 это самый высокий приоритет прерывания, и он будет заведомо более приоритетный, чем configMAX_SYSCALL_INTERRUPT_PRIORITY.
• При назначении приоритетов имейте в виду, что разные реализации ARM Cortex-M3 (разные модели микроконтроллеров) используют разное количество бит приоритетов.
• ARM Cortex-M3 внутри себя использует n самых старших бит байта для представления приоритета прерывания, где n зависит от реализации как было упомянуто выше. ARM и различные лицензиаты ARM Cortex-M3 предоставляют библиотечные функции, чтобы назначать приоритеты прерываний, однако некоторые из них ожидают, чтобы значение приоритета было сдвинуто влево в самые старшие биты байта перед вызовом функции, в то время как другие делают этот сдвиг внутри себя.
• Биты, которые определяют приоритет прерывания, разделены на те, которые представляют приоритет вытеснения (preemption priority) и те, которые представляют подчиненный приоритет (subpriority). Для упрощения и наилучшей совместимости убедитесь, что все биты приоритета назначены как биты preemption priority.
Все эти моменты подробно обсуждаются в документации [2]. Там же есть пример настройки приоритета для обработки приема порта USART6 микроконтроллера STM32F407 в среде FreeRTOS.
{/spoiler}
6. Планировщик падает, когда пытается запустить первую задачу. Если Вы используете ARM7, то процессор должен быть в режиме Supervisor, когда запускается планировщик RTOS.
7. Неправильно устанавливается флаг разрешения прерывания. Не используйте никакие методы для запрета и разрешения прерываний, кроме как через вызовы portENTER_CRITICAL() и portEXIT_CRITICAL(). Эти макросы задают границы критической секции приложения, и сохраняют счетчик глубины их вложенных вызовов, чтобы гарантировать, что прерывания будут снова разрешены только когда все вложенные вызовы будут полностью развернуты (счетчик вложений обнулится).
Если перед стартом планировщика вызывается API-функция FreeRTOS API, то большинство портов FreeRTOS сознательно запрещают прерывания и разрешают их снова, когда начинается выполняться первая задача. Это сделано для защиты системы от сбоев, вызванных прерываниями, пытающимися использовать функции FreeRTOS API во время инициализации системы до того, как был запущен планировщик.
8. Приложение падает перед запуском планировщика. Переключение контекста не может произойти, пока не будет запущен планировщик (RTOS scheduler). Любой ISR, который потенциально может привести к переключению контекста, не должен вызваться до запуска планировщика. То же самое верно для любого ISR, который пытается отправить что-то в очередь или взять из неё, или задействовать семафор.
Многие API-функции нельзя вызывать до запуска планировщика RTOS. Лучше всего запретить вероятность такого использования путем предварительного создания задач, очередей и семафоров, которые будут использоваться после начала работы планировщика.
9. Приостановка планировщика (вызовом vTaskSuspendAll()) создает проблемы. Не вызывайте API-функции FreeRTOS, когда планировщик приостановлен. Некоторые функции использовать можно, но не все. Это не предназначено для механизма приостановки.
10. Новое приложение создано, но оно не компилируется. Создавайте свои новые приложения на основе предоставленных примеров (demo-проекты) для Вашего порта FreeRTOS. Это будет гарантией того, что в проекте будут добавлены корректные файлы и компилятор правильно сконфигурирован.
• Переполнение стека, см. [8]. • Неправильное назначение прерываний, особенно для микроконтроллеров Cortex-M3, где применяется обратная нумерация приоритетов, что может выглядеть не очевидным (см. [2]). См. configMAX_SYSCALL_INTERRUPT_PRIORITY в статье [9]. Также см. описание следующей проблемы 12. • Вызов API-функции из критической секции, или когда приостановлен планировщик.
В общем случае см. список известных проблем на страничке [10] и архив форума техподдержки [5].
12. Зависание в функции vPortValidateInterruptPriority. В функции vPortValidateInterruptPriority (модуль port.c) есть макрос configASSERT, проверяющий уровень приоритета прерывания в текущем контексте. Если приоритет текущего прерывания выше прерывания тика FreeRTOS, то произойдет искусственное зависание.
когда использование нулевого значения в вызове NVIC_SetPriorityGrouping может
привести к непредсказуемому поведению. */
configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue );
}
Устранить проблему можно редактированием макроса TICK_INT_PRIORITY так, чтобы его значение было меньше приритета любого прерывания в системе. Пример настройки уровней приоритета для STM32 (значение макроса configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY определяется в файле конфигурации FreeRTOSConfig.h):