| ESP-IDF: зависание, если нет соединения через USB |
|
| Добавил(а) microsin |
|
Проблема с проектом ESP-IDF: если не подключен виртуальный последовательный порт к хосту USB (т. е. к хосту отладки), то программа перестает работать. Возможные причины такой ситуации описаны ниже. [Перегруженный вывод] Программа перестает работать, так как функции вывода (например, printf или ESP_LOGI) в ESP-IDF по умолчанию используют виртуальный USB-порт. Если порт не подключен, буфер передачи заполняется, и выполнение блокируется в ожидании хоста (USB). Эту проблему можно решить программно или изменив настройки конфигурации. 1. Отключение блокирующего вывода В коде программы (например, в задаче FreeRTOS) используйте функции, которые не будут бесконечно ждать отправки, если USB-хост отключен. Если вы используете компонент esp_console или usb_serial_jtag, убедитесь, что операции вывода не блокируют планировщик наглухо. 2. Изменение настроек в menuconfig Вы можете перенаправить вывод логов с виртуального USB на обычный физический UART или задать таймауты в ESP-IDF menuconfig: ● Перенаправление вывода на UART: перейдите в Component config -> ESP System Settings и в параметре Channel for console output (или UART for console output) выберите физический UART вместо USB CDC / USB Serial/JTAG Controller. [Блокирующий режим драйвера] Для микроконтроллера ESP32-S3 в ESP-IDF зависание при отключенном USB CDC может происходить из-за того, что драйвер usb_serial_jtag по умолчанию работает в блокирующем режиме. Когда буфер передачи заполняется, функции логирования (ESP_LOGx, printf) бесконечно ждут подтверждения от USB-хоста. Вот три способа решить эту проблему. 1. Изменение настроек в menuconfig (рекомендуемый способ) Вы можете отключить блокировку при заполнении буфера прямо в конфигурации проекта. В этом случае, если USB не подключен, логи будут просто отбрасываться, а программа продолжит работать. - Откройте терминал и запустите: idf.py menuconfig. 2. Изменение таймаута блокировки в коде Если вы хотите управлять поведением USB-порта прямо из кода (например, динамически менять таймаут), вы можете установить нулевое время ожидания для драйвера usb_serial_jtag: #include "driver/usb_serial_jtag.h" 3. Проверка подключения перед отправкой (для Native USB CDC) Если вы используете стек TinyUSB (tinyusb компонент), а не встроенный usb_serial_jtag, то перед отправкой данных всегда проверяйте, готов ли хост принимать данные: #include "tusb.h" [Проблема с питанием] 1. Иногда проблема связана с тем, что плата перезагружается при подключении к USB или испытывает скачок напряжения. Убедитесь, что вы используете качественный кабель, передающий данные (а не только питание), и при необходимости подайте внешнее питание 5 В на ножки платы. 2. Возможно медленное нарастание напряжения питания микроконтроллера, и по этой причине сброс программы происходит позже, чем начинает работу внешнее оборудование. Т. е. при подключенном кабеле USB, когда микроконтроллер постоянно получает питание, момент сброса программы после прошивки (idf.py flash) или при подключении монитора (idf.py monitor) происходит в других интервалах времени по отношению к включению питания других схем, и программа ведет себя по-другому. Именно с таким случаем я как раз и столкнулся. Выглядело это как непредсказуемое поведение программы в разных вариантах компиляции. Заработает программа нормально или нет при включении питания зависело от разных факторов, которые никак не должны влиять на работоспособность. Т. е. если к примеру скомпилировать поддержку Ethernet, то программа запускается нормально, даже если кабель USB не подключен, а если код Ethernet выкинуть, то программа запускается только если подключен кабель USB. Если нажать кнопку сброса, то программа запускается всегда нормально. Что можно сделать в этом случае: 1. Проверить скорость нарастания питания микроконтроллера, формирование сигнала сброса. Как вариант применить внешний аппаратный супервизор питания, генерирующий сброс микроконтроллера. 2. Опытным путем добавить и подобрать в программе задержки для запуска отдельных потоков в коде. Для моего случая помогло простое добавление vTaskDelay(pdMS_TO_TICKS(1000)) в самом начале тела функции app_main. [Решение проблемы] Ообычно основная причина зависания — бесконечное ожидание очистки TX-буфера. Активация опции CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_BUFF_FAIL_DROP в menuconfig или принудительная установка таймаута usb_serial_jtag_ll_set_tx_timeout(0) полностью устраняют блокировку программы при отключенном USB-кабеле. Но могут быть и другие причины, вызванные взамодействием потоков в программе и внешним оборудованием. [Ссылки] 1. ESP-IDF FAQ. |