Программирование ARM ESP-IDF: обработка ошибок Fri, June 21 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

ESP-IDF: обработка ошибок Печать
Добавил(а) microsin   

Идентификация и обработка ошибок времени выполнения кода (run-time errors) важна для разработки надежных приложений. Существует несколько видов подобных ошибок.

Восстановимые (recoverable errors):

- Ошибки, о которых было сообщено кодом возврата из функции (error codes).
- Исключения C++ (exceptions), выбрасываемые оператором throw.

Не восстановимые (fatal errors):

- Ошибки assert (при использовании макроса assert и эквивалентных ему методов, см. раздел "Assertions" документации [3]) и вызовов abort().
- Исключения процессора (CPU exceptions): доступ к защищенным регионам памяти, недопустимая инструкция, и т. п.
- Проверки системного уровня: watchdog timeout, cache access error, stack overflow, stack smashing, heap corruption, и т. д.

В этом руководстве (перевод [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.
2. ESP-IDF: диагностика не восстановимых ошибок.
3. Espressif IoT Development Framework Style Guide site:docs.espressif.com.
4. ESP-IDF Error Codes Reference site:docs.espressif.com.
5. ESP-IDF Monitor site:docs.espressif.com.
6. ESP-IDF C++ Support site:docs.espressif.com.

 

Добавить комментарий


Защитный код
Обновить

Top of Page