ESP-IDF: обработка ошибок |
![]() |
Добавил(а) microsin |
Идентификация и обработка ошибок времени выполнения кода (run-time errors) важна для разработки надежных приложений. Существует несколько видов подобных ошибок. Восстановимые (recoverable errors): - Ошибки, о которых было сообщено кодом возврата из функции (error codes). Не восстановимые (fatal errors): - Ошибки assert (при использовании макроса assert и эквивалентных ему методов, см. раздел "Assertions" документации [3]) и вызовов abort(). В этом руководстве (перевод [1]) объясняется использование механизмов обработки ошибок ESP-IDF (error handling), относящихся к восстанавливаемым ошибкам (recoverable errors), и предоставляются некоторые общие шаблоны обработки ошибок. Для руководства по диагностике не восстановимых ошибок (unrecoverable errors или fatal errors) см. [2]. [Коды ошибок] Основная масса функций ESP-IDF возвращают тип esp_err_t, который представляет коды ошибок (error codes). Тип esp_err_t это целое число со знаком. Успешный возврат (отсутствие ошибки) обозначается кодом ESP_OK, который определен как 0. Различные заголовочные файлы ESP-IDF определяют возможные коды ошибок, используя директивы препроцессора #define. Обычно эти определения дают имена кодом ошибок, начинающиеся с префикса ESP_ERR_. Общие коды ошибок для традиционных отказов (out of memory, timeout, invalid argument, и т. п.) определены в файле esp_err.h file. Различные компоненты в ESP-IDF могут определять дополнительные коды ошибок для отдельных ситуаций. Полный список ошибок см. в справочнике Error Code Reference [4]. [Преобразование кода ошибки в сообщение ошибки] Для каждого кода ошибки, определенного в компонентах ESP-IDF, значение esp_err_t может быть преобразовано в имя кода ошибки с помощью функций esp_err_to_name() или esp_err_to_name_r(). Например, если передать 0x101 в esp_err_to_name(), то она возвратит строку "ESP_ERR_NO_MEM". Такие строки можно использовать в выводе лога, чтобы упростить понимание, какая произошла ошибка. Дополнительно функция esp_err_to_name_r() будет пытаться интерпретировать код ошибки как стандартный код ошибки POSIX, если не было найдено подходящего значения ESP_ERR_. Это делается с помощью функции strerror_r. Коды ошибок POSIX (такие как ENOENT, ENOMEM) определены в errno.h, и они обычно извлекаются из переменной errno. В ESP-IDF эта переменная является локальной для каждого потока (задачи): в приложении работает несколько задач FreeRTOS, и у каждой из них есть своя собственная копия errno. Функции, которые устанавливают переменную errno, модифицируют её только у той задачи, в которой функция была запущена. Эта фича по умолчанию разрешена, однако её можно запретить для уменьшения размера бинарника приложения, см. опцию CONFIG_ESP_ERR_TO_NAME_LOOKUP. Когда эта фича запрещена, esp_err_to_name() и esp_err_to_name_r() все еще определены, и могут быть вызваны. В этом случае esp_err_to_name() вернет UNKNOWN ERROR, а esp_err_to_name_r() вернет Unknown error 0xXXXX(YYYYY), где 0xXXXX и YYYYY соответственно шестнадцатеричное и десятичное представление кода ошибки. [Макросы для обработки ошибок] ESP_ERROR_CHECK. Макрос ESP_ERROR_CHECK используется для тех же целей, что и assert, за исключением того, что ESP_ERROR_CHECK проверяет свое значение как esp_err_t, а не bool. Если аргумент ESP_ERROR_CHECK не равен ESP_OK, то в консоль печатается сообщение об ошибке, и вызывается abort(). Выводимое сообщение об ошибке будет выглядеть примерно так: ESP_ERROR_CHECK failed: esp_err_t 0x107 (ESP_ERR_TIMEOUT) at 0x400d1fdf file: "/Users/user/esp/example/main/main.c" line 20 func: app_main expression: sdmmc_card_init(host, &card) Backtrace: 0x40086e7c:0x3ffb4ff0 0x40087328:0x3ffb5010 0x400d1fdf:0x3ffb5030 0x400d0816:0x3ffb5050 Замечание: если используется IDF monitor (idf.py monitor [5]), то адреса в backtrace будут преобразованы в имена файлов и номера строк исходного кода. Первая строка сообщения об ошибке выводит код ошибки в шестнадцатеричном формате, и в круглых скобках идентификатор для этого кода ошибки, который используется в исходном коде. Последнее зависит от установленной опции CONFIG_ESP_ERR_TO_NAME_LOOKUP. Также в конце строки печатается адрес в программе, где произошла ошибка. Последующие строки показывают место в программе, где был вызван макрос ESP_ERROR_CHECK, и выражение, которое было передано в макрос как аргумент. В завершение печатается backtrace. Это часть вывода panic handler, общий для всех фатальных ошибок. Более подробно про backtrace см. в документации [2]. ESP_ERROR_CHECK_WITHOUT_ABORT. Макрос ESP_ERROR_CHECK_WITHOUT_ABORT работает так же, как и ESP_ERROR_CHECK, за исключением того, что не вызывает abort(). ESP_RETURN_ON_ERROR. Макрос ESP_RETURN_ON_ERROR проверяет код ошибки, и если он не равен ESP_OK, то печатает сообщение об ошибке и выполняет возврат из вызвавшей макрос функции. ESP_GOTO_ON_ERROR. Макрос ESP_GOTO_ON_ERROR проверяет код ошибки, и если он не равен ESP_OK, то печатает сообщение, установит локальную переменную ret в err_code, и затем выполнит переход по метки goto_tag. ESP_RETURN_ON_FALSE. Макрос ESP_RETURN_ON_FALSE проверяет условие, и если оно не равно true, то печатает сообщение и делает возврат с предоставленным err_code. ESP_GOTO_ON_FALSE. Макрос ESP_GOTO_ON_FALSE проверяет условие, и если оно не равно true, то печатает сообщение, установит локальную переменную ret в предоставленный err_code, и затем выполнит переход по метке goto_tag. Несколько примеров использования этих макросов: static const char* TAG = "Test"; esp_err_t test_func(void) { esp_err_t ret = ESP_OK; // Если x не равен ESP_OK, то печатается сообщение об ошибке и затем вызывается abort(): ESP_ERROR_CHECK(x); // Если x не равен ESP_OK, то печатается сообщение об ошибке без вызова abort(): ESP_ERROR_CHECK_WITHOUT_ABORT(x); // Если x не равен ESP_OK, то печатается сообщение, и функция выполнит возврат с кодом x: ESP_RETURN_ON_ERROR(x, TAG, "fail reason 1"); // Если x не равен ESP_OK, то печатается сообщение об ошибке, ret устанавливается в x, // и затем делается переход на метку err: ESP_GOTO_ON_ERROR(x, err, TAG, "fail reason 2"); // Если a не равно true, то печатается сообщение об ошибке, и функция выполнит возврат // с кодом err_code: ESP_RETURN_ON_FALSE(a, err_code, TAG, "fail reason 3"); // Если a не равно true, то печатается сообщение об ошибке, ret устанавливается в err_code, // и затем делается переход на метку err: ESP_GOTO_ON_FALSE(a, err_code, err, TAG, "fail reason 4"); err: // Очистка ... return ret; } Замечание: если разрешена опция CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT в Kconfig, то сообщение об ошибке отбрасывается, но остальные действия работают как и прежде. Макросы ESP_RETURN_XX и ESP_GOTO_xx не могут вызываться из ISR. Однако существуют их версии xx_ISR, например ESP_RETURN_ON_ERROR_ISR, которые можно использовать в ISR. [Шаблоны обработки ошибок] Здесь приведены типовые методы реализации обработки ошибок. 1. Попытка восстановления из состояния ошибки. В зависимости от ситуации мы можем попробовать следующие методы: - Сделать повторную попытку через некоторое время. Пример: esp_err_t err;
do { err = sdio_slave_send_queue(addr, len, arg, timeout); // Повторные попытки, пока очередь не переполнится. }while (err == ESP_ERR_TIMEOUT); if (err != ESP_OK) { // Обработка других ошибок. } 2. Передать ошибку "наверх", в вызывающий код. В некоторых промежуточных компонентах это означает, что функция должна выполнить выход с тем же самым кодом ошибки, и должна гарантировать, что любые выделенные ресурсы возвращены в исходное состояние. Пример: sdmmc_card_t* card = calloc(1, sizeof(sdmmc_card_t));if (card == NULL) { return ESP_ERR_NO_MEM; } esp_err_t err = sdmmc_card_init(host, &card); if (err != ESP_OK) { // Очистка: free(card); // Передача ошибки на верхний уровень (например для оповещения пользователя). // Альтернативно приложение может определить и возвратить пользовательский код ошибки. return err; } 3. Преобразовать ситуацию в невосстановимую ошибку, например с использованием макроса ESP_ERROR_CHECK. Прерывание работы приложения в случае обнаружения нежелательного поведения обычно нежелательно для промежуточных компонентов, однако это иногда допустимо на уровне приложения. Многие их примеров ESP-IDF используют ESP_ERROR_CHECK для обработки ошибок от различных API-функций. Это не лучшая практика для приложений, и делается для того, чтобы сделать код примера более лаконичным. Пример: ESP_ERROR_CHECK(spi_bus_initialize(host, bus_config, dma_chan)); [C++ Exceptions] Обработку исключений C++ см. в документации [6]. [Ссылки] 1. ESP-IDF Error Handling site:docs.espressif.com. |