Программирование ARM ESP-IDF FAQ Thu, November 21 2024  

Поделиться

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

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


ESP-IDF FAQ Печать
Добавил(а) microsin   

Здесь описаны решения проблем, которые связаны с программированием ESP32 в среде разработки ESP-IDF.

[Общие замечания]

Хорошая новость - среда разработки ESP-IDF [4] бесплатная, и к тому же кроссплатформенная, т. е. будет работать одинаково (почти) и на Windows, и на Linux, и на MacOS. Плохая новость - к сожалению, среда основана на Eclipse, и процесс компиляции плотно завязан на скрипты компиляции Python, поэтому приготовьтесь к тому, что будете много времени тратить на компиляцию проекта, особенно после изменения его опций компиляции. На Linux компиляция работает быстрее, чем на Windows. Поэтому компьютер обязательно должен быть современный, памяти не меньше 16 гигабайт, и обязательно рабочий диск должен быть SSD.

Известная проблема, связанная с созданием Launch Target, когда при создании нового проекта или его импорте не была подключена к COM-порту отладочная плата. В этом случае после процесса компиляции проекта в окне сообщений сборки (CDT Build Console) может появиться сообщение:

No esp launch target found. Please create/select the correct 'Launch Target'

Проблема была в том, процесс сборки требует указания наличия цели запуска (Launch Target). Цель запуска это обычно отладочная плата, подключенная через USB, и она видна на компьютере как виртуальный COM-порт.

Если Launch Target для проекта не создана или не выбрана, то сборка не завершится. Launch Target создается в среде ESP-IDF по стандартной процедуре, описанной в [1]:

1. Подключите плату ESP32 к порту USB. На компьютере должен появиться последовательный порт (COM-порт на Windows).
2. В среде ESP-IDF разверните третий выпадающий список, выберите New Launch Target.
3. В поле Name укажите любое имя. В поле IDF Target выберите esp32. В поле Serial Port выберите порт подключения платы. Кликните на кнопку Finish для сохранения конфигурации.

Перед запуском сборки выберите эту конфигурацию.

[Глюк ESP-IDF, связанный с созданием Launch Target]

Если проект был создан без подключенной платы, то будет наблюдаться глюк, когда нельзя создать цель запуска. Т. е. если пройти все описанные выше шаги New Launch Target, то цель запуска не будет создана, и компиляция будет неизменно заканчиваться ошибкой "No esp launch target found. Please create/select the correct 'Launch Target'".

Решить эту проблему можно следующим образом: создайте/импортируйте проект заново. После этого будет нормально работать New Launch Target. Мало того, в выпадающем списке магически появятся все цели, которые были до этого созданы неудачными попытками.

Даже на современном компьютере (64-разрядная операционная система Windows 10, процессор x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz, диск SSD, память 16 гигабайт) компиляция самого простого проекта blink может занимать 5 минут и больше. Что можно сделать с этим?

1. Отключите на время компиляции антивирус и Windows Defender.

2. Убедитесь, что среда ESP-IDF и компилируемый проект находится на диске SSD.

3. Посмотрите, чем загружена система в момент компиляции (с помощью Диспетчера задач) и закройте все ненужные сервисы и программы, которые больше всего отнимают процессорное время.

4. Настройте утилиту make на использование нескольких ядер процессора (Make -jN [2]).

5. Попробуйте перенесите мало изменяемый код в двоичные библиотеки.

6. Если совсем ничего не помогает, то перейдите с Windows на Linux.

1. Обязательно (!) подключить плату ESP32-C3-DevKitC-02v1.1 через USB к компьютеру. На компьютере должен появиться COM-порт.

2. Сконфигурировать New Launch Target (третий выпадающий список на панели инструментов) для появившегося COM-порта и используемого процессора ESP32-C3.

3. File -> New -> Espressif IDF Project -> blink.

4. idf.py set-target ESP32-C3, идем пить кофе.

5. F5, Двойной клик на sdkconfig, Example Configuration -> RMT - Addressable LED, RMT Channel 0, Blink GPIO number 8.

6. Serial flasher config -> Flash size 4 MB.

7. Component config -> FreeRTOS -> Run FreeRTOS only on first core.

8. Ctrl+S, меню Project -> Build Project, снова идем пить кофе.

9. idf.py flash

После этого на платке должен начать мигать RGB-светодиод WS2812.

Модули исходного кода добавляются в проект путем редактирования файлов CMakeLists.txt. Обычно такой файл есть в корневом каталоге проекта, а также в подкаталогах main и components проекта.

Как добавить файл с исходным кодом в проект, процесс по шагам:

1. Скопируйте *.c и его заголовочный файл *.h в подкаталог main. Предположим, это был модуль OneButton.c и заголовок OneButton.h.

2. Откройте в подкаталоге main файл CMakeLists.txt, добавьте модуль OneButton.c в проект следующим образом:

idf_component_register(SRCS "ledc_basic_example_main.c" "OneButton.c"
                    INCLUDE_DIRS ".")

Список добавляемых исходных файлов нужно вставлять через пробел, каждое имя файла нужно заключать в двойные кавычки. Можно также добавлять каждый файл на отдельной строке:

idf_component_register(SRCS "ledc_basic_example_main.c"
                            "OneButton.c"
                    INCLUDE_DIRS ".")

3. Перекомпилируйте проект командой idf.py build. Система сборки обнаружит изменение в CMakeLists.txt, и в процесс компиляции будет добавлен новый модуль.

Подробнее про систему сборки и файлы CMakeLists см. [6].

Предположим, у нас есть плата ESP32-С3-WROOM-02, у которой трехцветный RGB-светодиод WS2812 подключен к ножке порта GPIO18. Как добавить поддержку драйвера RGB и управлять этим светодиодом, процесс по шагам:

1. Создайте в подкаталоге main проекта файл Kconfig.projbuild со следующим содержимым (в этом файле текст должен быть только английским):

menu "Example control RGB-LED WS2812"
 
    choice BLINK_LED
        prompt "Blink LED type"
        default BLINK_LED_GPIO if IDF_TARGET_ESP32
        default BLINK_LED_RMT
        help
            Defines the default peripheral for blink example
 
        config BLINK_LED_GPIO
            bool "GPIO"
        config BLINK_LED_RMT
            bool "RMT - Addressable LED"
    endchoice
 
    config BLINK_LED_RMT_CHANNEL
        depends on BLINK_LED_RMT
        int "RMT Channel"
        range 0 7
        default 0
        help
            Set the RMT peripheral channel.
            ESP32 RMT channel from 0 to 7
            ESP32-S2 RMT channel from 0 to 3
            ESP32-S3 RMT channel from 0 to 3
            ESP32-C3 RMT channel from 0 to 1
 
    config BLINK_GPIO
        int "Blink GPIO number"
        range 0 48
        default 8 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32H2
        default 18 if IDF_TARGET_ESP32S2
        default 48 if IDF_TARGET_ESP32S3
        default 5
        help
            GPIO number (IOxx) to blink on and off or the RMT signal for the addressable LED.
            Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink.
 
    config BLINK_PERIOD
        int "Blink period in ms"
        range 10 3600000
        default 1000
        help
            Define the blinking period in milliseconds.
endmenu

2. Запустите команду idf.py menuconfig, и в разделе настроек "Example control RGB-LED WS2812" поменяйте следующие опции:

Blink LED type -> RMT - Addressable LED
Blink GPIO number 18

Сохраните настройки (S) и выйдите из menuconfig (Esc).

3. Откройте файл CMakeLists.txt, который находится в корневом каталоге проекта. Перед директивой include добавьте директиву set, указывающую на дополнительный компонент led_strip:

cmake_minimum_required(VERSION 3.5)
 
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/led_strip)
 
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
get_filename_component(ProjectId ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" ProjectId ${ProjectId})
project(${ProjectId})

4. Добавьте в основной модуль проекта (где находится функция app_main) следующий код:

#include "led_strip.h"
 
static uint8_t s_led_state = 0;
static led_strip_t *pStrip_a;
 
static void configure_led(void)
{
    ESP_LOGI("configure_led", "Example configured to blink addressable LED!");
    /* LED strip initialization with the GPIO and pixels number*/
    pStrip_a = led_strip_init(CONFIG_BLINK_LED_RMT_CHANNEL, CONFIG_BLINK_GPIO, 1);
    /* Set all LED off to clear all pixels */
    pStrip_a->clear(pStrip_a, 50);
}
 
static void blink_led(void)
{
    /* If the addressable LED is enabled */
    if (s_led_state) {
        /* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */
        pStrip_a->set_pixel(pStrip_a, 0, 16, 16, 16);
        /* Refresh the strip to send data */
        pStrip_a->refresh(pStrip_a, 100);
    } else {
        /* Set all LED off to clear all pixels */
        pStrip_a->clear(pStrip_a, 50);
    }
}
 
void app_main(void)
{
    configure_led();
    while(1)
    {
        /* Toggle the LED state */
        blink_led();
        s_led_state = !s_led_state;
        vTaskDelay(CONFIG_BLINK_PERIOD / portTICK_PERIOD_MS);
    }
}

5. Скомпилируйте проект командой idf.py build.

Наиболее часто встречающиеся причины:

• Проблемы с питанием - уровень меньше 3.3V, просадки.
• Проблемы с сигналами подключения SPI flash.
• Проблемы с сигналами приема и передачи UART, через который происходит взаимодействие с загрузчиком.

Подробно решение таких проблем описано в статье [7].

После компиляции проектов примеров, поставляемых вместе со средой программирования ESP-IDF, в корневой папке проекта создается каталог esp_idf_components с кучей подкаталогов, причем они все пустые.

ESP IDF components empty folder

Структура каталогов этой папки esp_idf_components полностью совпадает со структурой каталогов папки c:\Espressif\frameworks\esp-idf-v4.4\components\, но эти каталоги уже не пустые, там находится исходный код компонентов, которые могут использоваться в проекте.

Возникает законный вопрос - зачем нужен каталог esp_idf_components с пустыми папками?

Оказывается, эти пустые папки каталоги нужны для того, чтобы можно было по желанию менять поведение некоторых компонентов, путем правки его исходного кода, и не затрагивая при этом исходный код оригинала. Для этого модуль исходного кода, который нужно исправить, копируется в соответствующий пустой подкаталог папки esp_idf_components, и в файл CMakeLists.txt проекта добавляется ссылка на копируемый файл. После этого система сборки будет брать для компиляции этот модуль вместо исходного, который находился в папке c:\Espressif\frameworks\esp-idf-v4.4\components\.

Покажу на примере проекта Console Example. Предположим, что нужно поменять интерфейс команд консоли. Процесс по шагам:

1. Скопируйте модуль commands.c из папки c:\Espressif\frameworks\esp-idf-v4.4\components\console\ в папку c:\Espressif\frameworks\esp-idf-v4.4\workspace\console_basic\esp_idf_components\console\.

2. Откройте на редактирование файл main\CMakeLists.txt проекта, добавьте в него ссылку на скопированный модуль:

idf_component_register(SRCS "cmd_wifi.c"
                            "console_example_main.c"
                            "../esp_idf_components/console/commands.c"
                    INCLUDE_DIRS ".")

3. Внесите изменения в скопированный модуль commands.c и перекомпилируйте проект.

Windows -> Preferences -> General -> Workspace -> Text file encoding -> в выпадающем списке выберите кодировку.

ESP IDF text editor UTF8

Для вывода русского текста в консоли монитора (idf.py monitor) выберите кодировку UTF-8.

1. Утилита командной строки bin2c [8] (входит в пакет hxtools).

2. Функция линковщика ld, превращающая любой файл в объект [9]:

ld -r -b binary -o binary.o foo.bar  # затем линковка binary.o

3. На некоторых SDK для этого есть специальные функции, например макрос EMBED_FILES системы сборки ESP-IDF (см. врезку "Пример CMakeLists компонента" [6]).

~/esp/esp-idf$ git status
Отсоединённый указатель HEAD указывает на v4.4.1

idf.py menuconfig → Component config → Log output → Default log verbosity

( ) No output
( ) Error
( ) Warning
(X) Info
( ) Debug
( ) Verbose

Для этого можно использовать команду idf.py size, например:

каталогпроекта$ idf.py size
...
Total sizes:
Used stat D/IRAM:  155732 bytes ( 171948 remain, 47.5% used)
      .data size:   23580 bytes
      .bss  size:   49864 bytes
      .text size:   82288 bytes
Used Flash size : 1458700 bytes
      .text     : 1034218 bytes
      .rodata   :  424226 bytes
Total image size: 1564568 bytes (.bin may be padded larger)

В устройстве понадобилось использовать вывод GPIO2 и как вход для ADC1, и как вход для вывода устройства из режима низкого энергопотребления deep sleep. Конечно, не одновременно, а в разные моменты времени жизни приложения.

Однако при этом столкнулся с проблемой: если при пробуждении настроить GPIO2 как вход АЦП, а потом при уходе в сон попытаться перенастроить этот вывод на пробуждение из deep sleep, то функция пробуждения перестает работать. Если точнее, то устройство пробуждается сразу после ухода в сон, как будто на входе появился пробуждающий уровень лог. 0, хотя на самом деле ничего такого нет.

Код, который настраивает вход GPIO2 как вход АЦП:

ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12));
// ADC1_CHANNEL_2 соответствует выводу GPIO_NUM_2, т. е. GPIO2:
ESP_ERROR_CHECK(adc1_config_channel_atten(ADC1_CHANNEL_2, ADC_ATTEN_DB_11));

Код, который делает попытку настроить этот же вывод на пробуждение:

uint64_t wakeup_mask = BIT(GPIO_NUM_2);
ESP_ERROR_CHECK(esp_deep_sleep_enable_gpio_wakeup(wakeup_mask, ESP_GPIO_WAKEUP_GPIO_LOW));
ESP_LOGI(__FUNCTION__, "Entering deep sleep...");
esp_sleep_pd_config(ESP_PD_DOMAIN_VDDSDIO, ESP_PD_OPTION_OFF);
esp_deep_sleep_start();

Решение проблемы - перед настройкой глубокого пробуждения снова переключить этот вывод на работу как GPIO с помощью функции gpio_config:

gpio_config_t pincfg =
{  .pin_bit_mask = BIT(GPIO_NUM_2),
   .mode         = GPIO_MODE_INPUT,
   .pull_up_en   = GPIO_PULLUP_ENABLE };
ESP_ERROR_CHECK(gpio_config(&pincfg));
// Далее настройка пробуждения вызовами esp_deep_sleep_enable_gpio_wakeup
// и esp_sleep_pd_config, и затем уход в сон через esp_deep_sleep_start.

Интересно, что вызов gpio_reset_pin не восстанавливает функционал вывода из deep sleep, хотя по описанию вызов gpio_reset_pin должен возвратить вывод GPIO в состояние по умолчанию (выбор функции порта ввод/вывода общего назначения, разрешение pullup и запрет работы на вход и выход).

Неожиданно столкнулся в VS Code с ошибкой:

Squiggles are disabled for this translation unit (/home/username/myproject/src/main.c).

Решение проблемы:

1. Добавьте явное определение "compilerPath" в конфигурацию VS Code. Для этого в файле c_cpp_properties.json, который находится в папке .vscode корневого каталога проекта, путь до используемого компилятора в свойстве compilerPath, например "compilerPath": "/usr/bin/gcc":

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${default}",
                "./include"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

2. В файле settings.json (который находится в той же папке .vscode) поменяйте настройку C_Cpp.errorSquiggles на "Enabled", например:

{
    "idf.adapterTargetName": "esp32c3",
    "C_Cpp.errorSquiggles": "enabled"
}

В некоторых случаях бывает нужно подавить предупреждение компилятора, что переменная была определена и ей присвоено значение, но она после этого не используется. Например, это может случиться при использовании макроса ESP_GOTO_ON_ERROR, когда переменная ret, куда макрос записывает возвращаемое значение, не используется. Пример:

TEvent evt;
for(uint8_t i=0; i < 3; i++)
{
   esp_err_t ret = ESP_OK;  // компилятор выдаст предупреждение на
                            // не используемую переменную ret...
   ESP_GOTO_ON_ERROR(pdTRUE == xQueueReceive(queue, &evt, 100) ? ESP_OK : ESP_FAIL,
                     err_measure, __FUNCTION__, "Failed to read voltage");
   ESP_LOGI(__FUNCTION__, "%.3fV", voltage);err_measure:
   vTaskDelay(100/portTICK_PERIOD_MS);
}

Устранить выдачу предупреждения можно следующим способом:

   esp_err_t ret = ESP_OK;
   (void)ret;  // подавляет warning на не используемую переменную ret

#include "esp_idf_version.h"
 
#if (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(4, 4, 1))
# include "driver/gpio.h"
# include "driver/uart.h"
#elif (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 1))
# include "rom/gpio.h"
# include "rom/uart.h"
#endif

# register ESP IDF component
if("v4.4.1-dirty" STREQUAL ${IDF_VER})
idf_component_register( REQUIRES        "driver"
                                        "esp_adc_cal")
elseif("v5.0.1" STREQUAL ${IDF_VER})
idf_component_register( REQUIRES        "driver"
                                        "esp_adc")
endif()

error: format '%u' expects argument of type 'unsigned int', but argument 6
 has type 'uint32_t' {aka 'long unsigned int'} [-Werror=format=]

Эту ошибку выдает код:

uint32_t phaseincrement = (uint32_t)((uint64_t)4294967296*freq/samplerate);
ESP_LOGI("", "phaseincrement %u", phaseincrement);

Как исправить:

ESP_LOGI("", "phaseincrement %" PRIu32, phaseincrement);

Ошибка:

error: expected ')' before 'PRIu32'
   ESP_LOGI("", "phaseincrement %" PRIu32, phaseincrement);
                                   ^~~~~~

Проблема в том, что не подключен заголовок inttypes.h [10].

error: missing binary operator before token "(" #if (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(4, 4, 1))

Проблема в том, что не подключен заголовочный файл esp_idf_version.h.

Возможно дважды применен атрибут IRAM_ATTR к одной и той же функции - один раз в объявлении заголовка (или в static-объявлении функции), и второй раз в реализации. Например:

// Здесь не надо указывать атрибут IRAM_ATTR:
void IRAM_ATTR ledc_timer_overflow_isr(void *arg);
 
// А вот здесь как раз IRAM_ATTR нужен:
void IRAM_ATTR ledc_timer_overflow_isr(void *arg)
{
  // Очистка бит прерываний:
  uint32_t intstatus = READ_PERI_REG(LEDC_INT_ST_REG);
  WRITE_PERI_REG(LEDC_INT_CLR_REG, intstatus);
 
  // Здесь код обработчика прерывания:
  ...
}

__FILE__: макрос, который компилятор переводит в текстовое имя модуля исходного кода, где встретился этот макрос.

__FUNCTION__: макрос, который компилятор переводит в текстовое имя функции, где встретился этот макрос.

__LINE__: макрос, который компилятор переводит в текстовое имя функции, где встретился этот макрос.

Благодаря этим макросам появляется удобная возможность в логе отследить ход выполнения программы:

ESP_LOGW(__FUNCTION__, "%d", __LINE__);

В результате в логе будут появляться вот такие строки:

I (662) phy_init: phy_version 909,156dee4,Apr  7 2022,20:27:09
I (842) wifi:mode : sta (a0:76:4e:14:5a:3c)
I (842) wifi:enable tsf
W (842) off_task: 475
W (842) off_task: 483
 
assert failed: 0x4038aaea
Core  0 register dump:
MEPC    : 0x4038085e  RA      : 0x4038a710  SP      : 0x3fcae140  GP      : 0x3fc94a00
TP      : 0x3fc4563c  T0      : 0x37363534  T1      : 0x7271706f  T2      : 0x33323130
S0/FP   : 0x00000000  S1      : 0x00000000  A0      : 0x3fcae154  A1      : 0x00000061
A2      : 0x00000003  A3      : 0x3fcae168  A4      : 0x00000001  A5      : 0x3fca3000
A6      : 0x7a797877  A7      : 0x76757473  S2      : 0x00000000  S3      : 0x00000000
S4      : 0x00000000  S5      : 0x00000000  S6      : 0x00000000  S7      : 0x00000000
S8      : 0x00000000  S9      : 0x00000000  S10     : 0x00000000  S11     : 0x00000000
T3      : 0x6e6d6c6b  T4      : 0x6a696867  T5      : 0x66656463  T6      : 0x62613938
MSTATUS : 0x00001801  MTVEC   : 0x40380001  MCAUSE  : 0x00000007  MTVAL   : 0x00000000
MHARTID : 0x00000000
..

В результате становится намного проще разобраться в реальной последовательности выполнения кода и найти место ошибки. 

Макрос __FILE__ может пригодиться в больших проектах, где содержится множество отдельных файлов исходного кода. 

Вызов vTaskDelete(NULL) используется, чтобы задача могла удалить саму себя. Особенность такого вызова в том, что он должен быть обязательно размещен в конце функции задачи. Только при выполнении этого условия состояние задачи перейдет в eDeleted.

Если же разместить vTaskDelete(NULL) в любом другом месте тела функции задачи, то её состояние навсегда останется eRunning.

[Ссылки]

1. Configuring Launch target.
2. [Advice] Boost ESP-IDF Compile time on Windows 10 site:reddit.com.
3. ESP32 compiling time is way too long site:esp32.com.
4. Установка среды разработки ESP-IDF для ESP32.
5. Конфигурация проекта ESP-IDF.
6. ESP-IDF Build System.
7. ESP32: проблемы с прошивкой SPI Flash и загрузкой.
8. hxtools bin2c site:manpages.debian.org.
9. Embedding binary blobs using gcc mingw site:stackoverflow.com.
10. inttypes.h I/O format of integer types site:ibm.com.

 

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


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

Top of Page