Esp32c3. насыпаем bluetooth. zephyr. esp-idf.

Тема в разделе "ESP8266, ESP32", создана пользователем parovoZZ, 16 июн 2023.

  1. parovoZZ

    parovoZZ Гуру

    Приехала ко мне такая платка
    upload_2023-6-16_18-6-46.png

    От http://www.vcc-gnd.com/
    Что на ней есть? Это SoC ESP32C2 на базе одноядерного МК на ядре с открытой архитектурой RISC-V. Также есть адресный светодиод WS2812, две кнопки - BOOT и RESET. Кнопка RESET жёстко привязана к ресету МК, а кнопку BOOT можно задействовать в кастомном ПО. Также есть 3 светодиода: красный светодиод индицирует подачу питания на плату, синий и зелёный подключены к линии UART, что позволяет визуально контролировать процесс обмена с МК. Также видим да USB порта системы Type-C. Один из них подключен непосредственно к МК и может выполнять функции как JTAG отладчика, так и UART. Прошить МК через этот порт нельзя, как нельзя и всё остальное. Другой USB порт - это уже преобразователь CH340. Причём на плате уже собрана схема, которая позволяет дёргать пины boot и reset через данный преобразователь.
    Прежде всего, меня интересует bluetooth. Но что выбрать, на чём писать прошивки? Давно присматривался к такому фреймворку, как Zephyr. Он поддерживает какое-то безумное количество плат, имеет собственную экосистему и пропагандирует подход, очень схожий с ардуино: написанный вами код можно скомпилировать для любой поддерживаемой платы без корректировки собственно самого кода.
    Ну что же. Давайте пробовать. Для начала необходимо установить Zephyr и весь необходимый тулчейн. Для этого можно воспользоваться инструкцией с официального сайта Zephyr (или гитхаба), но в таком случае будет выкачано буквально всё. И это всё будет занимать почти 20ГБ дискового пространства. Меня такой подход не устраивает, поэтому буду качать только то, что нужно. Вместо пересказа дам ссылку на инструкцию
    https://danielmangum.com/posts/risc-v-bytes-zephyr-on-esp32/
    Примечание! Для работы bluetooth необходима библиотека tinycrypt, поэтому файл манифеста должен выглядеть так:
    Код (Text):
    manifest:
      projects:
        - name: zephyr
          revision: main
          url: https://github.com/zephyrproject-rtos/zephyr
          west-commands: scripts/west-commands.yml
          import:
            name-allowlist:
              - hal_espressif
              - tinycrypt
     
     
    8bitai, ИгорьК и DetSimen нравится это.
  2. parovoZZ

    parovoZZ Гуру

    Что я буду делать? На плате есть светодиод и давайте им поуправляем через bluetooth со смартфона.
    У стека bluetooth есть две разновидности: classic и BLE. Меня, прежде всего, интересует BLE из-за его низкого токопотребления.
    Разумеется, что изучать bluetooth надо не с примеров, а с изучения основ. На хабре есть очень хороший перевод книги Мохаммада Афане Intro to Bluetooth Low Energy. Перевод выложил пользователь под ником aectaan.
    Чтобы подключиться к BLE устройству, необходимо этому устройству как-то о себе заявить. Осуществляется это с помощью рассылки широковещательных пакетов, а сам процесс назван Advertise (реклама).За процедуру объявления отвечает профиль GAP. Давайте настроим этот профиль. В Zephyr это можно сделать с помощью макроса:
    Код (C++):
    #define ADV_PARAM BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | \
                                          BT_LE_ADV_OPT_USE_NAME, \
                                      BT_GAP_ADV_SLOW_INT_MIN,    \
                                      BT_GAP_ADV_SLOW_INT_MAX, NULL)
    Здесь мы говорим: мы поддерживаем подключения с любого устройства (установлен флаг BT_LE_ADV_OPT_CONNECTABLE, и в качестве адреса узла стоит NULL), мы хотим передавать имя нашего устройства в пакете рекламы, а также устанавливаем минимальный и максимальный периоды, через которые будут посылаться пакеты. Очевидно, что чем реже рассылаются широковещательные пакеты рекламы, тем меньше энергопотребление, но и процесс установления соединения будет не столь оперативным. Также нам необходимо в пакет рекламы включить uuid первичного сервиса и какую версию bluetooth мы поддерживаем. В Zephyr определена специальная структура bt_data:
    Код (C++):
    #define LED_SERVICE_UUID_VAL BT_UUID_128_ENCODE(0xf7547938, 0x68ba, 0x11ec, 0x90d6, 0x0242ac120003)
    static const struct bt_data ad[] = {
        BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
        BT_DATA_BYTES(BT_DATA_UUID128_ALL, LED_SERVICE_UUID_VAL),
    };
    Первые два флага сообщают, что наше устройство поддерживает BLE и не поlдерживает classic. Во второе поле пишем uuid первичного сервиса. Мы полностью кастомное устройство, поэтому нам необходимо придумать и записать uuid длиной 128 бит. Для облегчения формирования uuid в Zephyr создан макрос
    Код (C++):
    BT_UUID_128_ENCODE
    Теперь настало время подумать о характеристиках GATT. У меня три светодиода: красный, зелёный и синий и управлять ими я хочу независимо. Поэтому логично создать три характеристики, каждая из которых будет привязана к своему цвету. В Zephyr это можно сделать на стадии компиляции с помощью макроса:
    Код (C++):
    static struct bt_uuid_128 led_R_char_uuid = BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x9c85a726, 0xb7f1, 0x11ec, 0xb909, 0x0242ac120002));
    static struct bt_uuid_128 led_G_char_uuid = BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x9c85a726, 0xb7f1, 0x11ec, 0xb909, 0x0242ac120003));
    static struct bt_uuid_128 led_B_char_uuid = BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x9c85a726, 0xb7f1, 0x11ec, 0xb909, 0x0242ac120004));

    static struct bt_uuid_128 led_svc_uuid = BT_UUID_INIT_128(LED_SERVICE_UUID_VAL);

    BT_GATT_SERVICE_DEFINE(
        led_svc, BT_GATT_PRIMARY_SERVICE(&led_svc_uuid),
        BT_GATT_CHARACTERISTIC(&led_R_char_uuid.uuid,
                               BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                               BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                               read_R_state, write_R_state, &led_R),
        BT_GATT_CHARACTERISTIC(&led_G_char_uuid.uuid,
                               BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                               BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                               read_G_state, write_G_state, &led_G),
        BT_GATT_CHARACTERISTIC(&led_B_char_uuid.uuid,
                               BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
                               BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                               read_B_state, write_B_state, &led_B));
    Как и для первичного сервиса, нам необходимо придумать и записать uuid для каждой характеристики. Далее мы определяем режим работы наших характеристик: все значения мы передаём в ответе на запрос. Т.е. по сути наше устройство является периферийным сервером с форматом общения запрос-ответ. Также мы определяем уровни доступа - мы имеем право как читать, так и писать. Запросы на чтение и запись мы будем обрабатывать с помощью функций обратного вызова. И последним параметром указываем ссылку напеременную, значение которой мы будем читать и писать в рамках работы характеристики. Таких переменных у нас три: по одной на каждую характеристику:
    Код (C++):
    uint8_t led_R = 0;
    uint8_t led_G = 0;
    uint8_t led_B = 0;
    Теперь опишем главную функцию:
    Код (C++):
    void main(void)
    {
      int err;
      err = bt_enable(NULL);
      if (err)
      {
        printk("Bluetooth init failed (err %d)\n", err);
        return;
      }

      printk("Bluetooth initialized\n");

      err = bt_le_adv_start(ADV_PARAM, ad, ARRAY_SIZE(ad), NULL, 0);
      if (err)
      {
        printk("Advertising failed to start (err %d)\n", err);
        return;
      }
      printk("Advertising successfully started. Interval = %d\n", ADV_PARAM->interval_min);
    }
    В ней разрешаем работу bluetooth. Далее вызываем функцию, которая переводит режим работы устройства в режим рассылки широковещательных пакетов (рекламы). В функцию необходимо передать объявленные ранее параметры GAP, а также данные, которые будут включены в содержимое пакета рекламы. Также неявным образом в пакет будет включено имя устройство. Вывод сообщений в консоль определяется функцией printk. Функция поддерживает форматирование также, как и стандартная printf.
     
    8bitai, ИгорьК и DetSimen нравится это.
  3. parovoZZ

    parovoZZ Гуру

    В характеритсиках GATT мы объявили обратный вызов функций, но не написали их тело. Но прежде надо решить, каким образом мы будем "поджигать" светодиод ws2812. Есть несколько путей решения: тупой bit bang (т.е. непосредственное управление ножкой GPIO с выдержкой необходимых интервалов), с помощью последовательных интерфейсов. У нас их есть несколько: SPI, I2S, I2C, CAN, UART. Из-за аппаратных ограничений нам лучше всего подойдёт SPI. Ну просто потому, что создаваемый нами пакет мы можем наполнять именно так, как нам нужно. Нам не надо подстраиваться ни под стандартные частоты, стартовые биты и биты подтверждения. Итак, два кандидата: GPIO или SPI? Я выбрал SPI, т.к. в таком случае нам достаточно "закинуть" пакет в буфер SPI и его передача во внешний мир будет осуществлена полностью аппаратно.
    Как известно, WS2812 управляется с помощью интервалов определённой длительности, которые указаны в даташите. Но есть энтузиасты, которые выявили предельные значения этих длительностей, что даёт нам некоторую свободу при программировании. Ниже дам ссылку на одного из них:
    https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
    Теперь надо решить, каким образом один информационный бит для WS2812 превратить в пакет SPI. Я решил пойти по самому простому пути, благо и ОЗУ и частота тактирования SPI позволяет это сделать. Один бит будем замещать одним байтом SPI. Тогда получится такая маска:
    Код (C++):
    #define T0 0b11000000
    #define T1 0b11110000
    Теперь напишем функцию, которая будет преобразовывать входной бит в выходной байт. Я не стал изобретать велосипед, а подглядел его здесь:
    https://www.newinnovations.nl/post/controlling-ws2812-and-ws2812b-using-only-stm32-spi/
    Взял не всё, а только колёса:
    Код (C++):
    #define WS2812_FILL_BUFFER(COLOR)             \
      for (uint8_t mask = 0x80; mask; mask >>= 1) \
      {                                           \
        if (COLOR & mask)                         \
        {                                         \
          *ptr++ = T1;                            \
        }                                         \
        else                                      \
        {                                         \
          *ptr++ = T0;                            \
        }                                         \
      }
    И вот мы уже подошли к тому, как в Zephyr конфигурируется периферия. Как я говорил выше, в Zephyr исходный код можно скомпилировать под абсолютно любую из поддерживаемых. Но как быть с периферией, ведь у разных микросхем она абсолютно разная. Для этого в Zephyr используется механизм, который называется devicetree. Его главное отличие от всех остальных в том, что периферия конфигурируется не на стадии выполнения, а на стадии компиляции с помощью препроцессора. Из-за этого весь его синтаксис полностью подчинен его стандарту. К сожалению, на этом его положительная сторона и заканчивается. Дело в том, что поддержку плат в Zephyr осуществляет непосредственно сам производитель. Поэтому синтаксис описания devicetree у каждого производителя будет немного отличаться.
    Итак, что такое devicetree? Это файл с расширением dts, в котором описывается периферия: адресация, её состояния, какие-то ещё ресурсы. Но что делать, если мы хотим описать собственное состояние периферии? Для этого предусмотрен файл с расширением overlay. Файл необходимо назвать по имени платы и поместить его в папку boards. Тогда система сборки автоматически найдёт этот файл и применит все необходимые изменения. Не буду здесь глубоко погружаться в тему devicetree, а приведу целиком файл esp32c3_devkitm.overlay:
    Код (C++):
     
    #include <dt-bindings/spi/spi.h>
    &spi2 {
        compatible = "espressif,esp32-spi";
        status = "okay";
        pinctrl-0 = <&spim2_default>;
        pinctrl-1 = <&spim2_sleep>;
        pinctrl-names = "default", "sleep";
      };

      &pinctrl {
        spim2_default: spim2_default {
          group1 {
            pinmux =  < SPIM2_MOSI_GPIO8 >,
                      <SPIM2_SCLK_GPIO9>;
          };
        };

        spim2_sleep: spim2_sleep {
          group1 {
            pinmux = < SPIM2_MOSI_GPIO8 >;
          };
        };
      };
    Что мы здесь видим? Первое - это название модуля SPI. Оно может быть абсолютно произвольным. В нашем случае это spi2. Далее мы указываем ссылку на стандартный драйвер нашего модуля:
    Код (C++):
    compatible = "espressif,esp32-spi";
    Затем включаем этот драйвер в сборку. Ещё ниже мы определяем конфигурацию стандартного Zephyr коммутатора pinctrl. По факту же это мультиплексор в составе ESP32, который позволяет создать матрицу из сигналов внутренних модулей и внешних выводов. Т.е. любой сигнал с любого модуля мы можем скоммутировать на любую доступную ножку GPIO. Здесь мы видим два состояния pinctrl: pinctrl-0 и pinctrl-1. Но на самом деле таких состояний в Zephyr зарезервировано 4. Но что это? Из названий становится понятно, что речь про режим работы МК: рабочий, легкий сон, глубокий сон и прочее. Нам нужен только рабочий режим. Режим сна здесь приведён в качестве примера и использован не будет (почему - будет понятно ниже). С описанием SPI закончили. Теперь перейдём к описанию матриц мультиплексора. Для SPI в нашем случае необходимо определить только один сигнал: master output slave input. Если обратиться к схеме моей платы, то из неё мы узнаем, что вход in светодиода WS2812 подключен к ножке GPIO8. Но как это указать? Для этого необходимо подключить файл spi.h, в котором описаны всевозможные состояния мультиплексора для сигналов от SPI. Здесь также включен сигнал SCLK - он включен исключительно для контроля и отладки. Для работы светодиода он не нужен. spi2 и pinctrl - это ноды дерева устройств. Они нам понадобятся позже.
    Можно заметить, что везде упоминается SPI2. Почему? Если открыть схему из даташита:
    upload_2023-6-18_18-3-18.png
    то увидим, что в нашем распоряжении имеется аж три блока SPI. Но! SPI0 отведён под работу с внешним flash накопителем (даже если ESP32C3 с внутренней flash памятью, как в моём случае, общение с ней всё равно ведётся через SPI0). SPI1 делит сигналы с SPI0 с помощью специального блока Arbiter и не является полнофункциональным. И в сухом остатке остаётся лишь только SPI2.
    Хорошо. Периферию задали. Возвращаемся к нашему коду. Для возможности работы с SPI, нам необходимо создать специальные структуры и сослаться в них на имена из нашего файла overlay:
    Код (C++):
    #define SPI2_NODE DT_NODELABEL(spi2)
    static const struct device *spi2_dev = DEVICE_DT_GET(SPI2_NODE);

    static struct spi_config spi_cfg = {
        .operation = SPI_WORD_SET(40),
        .frequency = 6600000UL,
        .slave = 0};
    С помощью макроса DT_NODELABEL мы ищем ноду с указанным названием и возвращаем её идентификатор. Далее определяем структуру устройства Zephyr и заполняем поля с помощью макроса DEVICE_DT_GET. Также создадим структуру с дополнительными опциями SPI, в которой укажем количество байт, которые необходимо выдать наружу в рамках одной транзакции, частоту тактирования, режим работы SPI.
    И вот теперь мы можем написать функцию, которая будет формировать пакет и отправлять его в буфер SPI. Выглядит она так:
    Код (C++):

    uint8_tws2812_buffer[40];
    void ws2812_pixel(uint8_t r, uint8_t g, uint8_t b)
    {
      uint8_t *ptr = &ws2812_buffer[8];
      WS2812_FILL_BUFFER(g);
      WS2812_FILL_BUFFER(r);
      WS2812_FILL_BUFFER(b);

      struct spi_buf spi_buffer[1];

      spi_buffer[0].buf = ws2812_buffer;
      spi_buffer[0].len = 40;

      struct spi_buf_set tx_buff_set = {
          .buffers = spi_buffer,
          .count = 1};

      spi_write(spi2_dev, &spi_cfg, &tx_buff_set);
    }
    Сперва создадим буфер, куда будем записывать итоговый пакет. Почему 40 ячеек в массиве? Из ссылки, приведённой выше, становится понятно, что для фиксации значений в WS2812 достаточно выдержать низкий уровень на линии в течение 6 мкс. В этот диапазон при выбранной частоте тактирования SPI укладывается 8 байт с нулевым значением. Итого мы получаем: 8 байт пауза вначале, 24 информационных байта и 8 байт в конце. Итого 40 байт.
     
    8bitai, DetSimen и ИгорьК нравится это.
  4. parovoZZ

    parovoZZ Гуру

    Буфер объявлеям глобально, поэтому его инициализацию (заполнение нулями) берёт на себя компилятор Си. Далее нам надо заполнить этот буфер байтами, преобразованными из входных бит. Для этого мы пропускаем первые 8 байт, о которых говорили выше, берём указатель на адрес 9-ой ячейки и с помощью уже написанного макроса заполняем наш буфер.
    Для отправки пакета воспользуемся функцией spi_write(). Первым аргументом необходимо передать заполненную структуру устройства, вторым аргументом даём ссылку на структуру с опциями SPI и, наконец, передаём ссылку на структуру с данными. Из кода понятно, что к чему.
    Теперь, когда у нас всё готово для передачи по интерфейсу SPI, можем оформить тела функций обратного вызова, которые будут вызываться при общении смартфона с нашим устройством. Для красного светодиода они будут выглядеть так:
    Код (C++):
    static ssize_t read_R_state(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset)
    {
      const uint8_t *val = attr->user_data;
      printk("Value 0x%x read.\n", *val);
      return bt_gatt_attr_read(conn, attr, buf, len, offset, val, sizeof(*val));
    }

    static ssize_t write_R_state(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
    {
      uint8_t *val = attr->user_data;
      *val = *((uint8_t *)buf);

      printk("Value 0x%x written.\n", *val);
      printk("LED R: %d\n", led_R);

      ws2812_pixel(led_R, led_G, led_B);

      return len;
    }
    Здесь всё просто: в функции чтения мы извлекаем переданное значение в ответе на запрос со смартфона и отображаем его в консоле (терминале). В функции записи делаем всё тоже самое, но самое главное - мы по событию записи вызываем функцию управления светодиодом WS2812, описанную ранее. Результатом работы функций должно быть количество прочитанных или записанных байт в случае успеха, а в случае неуспеха необходимо вернуть код ошибки. Мы предполагаем, что ошибок у нас нет, поэтому возвращаем длины.
    Аналогично для двух остальных цветов.
    В самом начале я писал про название нашей платы, которое будет передаваться в составе пакета рекламы, но нигде в коде этого названия нет. В Zephyr это имя задаётся через специальный конфигуратор, очень похожий на тот, что используется в GNU Linux при сборке ядра. Вызывается он с помощью инструмента west:
    Код (Bash):
     west build -t menuconfig
     
    Но если мы попытаемся выполнить эту команду прямо сейчас, то получим ошибку. Почему? А всё просто: система сборки не знает, под какое железо нам необходимо собрать проект. Поэтому первым делом необходимо указать ту плату (из списка поддерживаемых), с которой будем работать. Для этого необходимо выбрать максимально подходящую под наш проект плату из этого списка:
    https://docs.zephyrproject.org/latest/boards/index.html
    Для ESP32C3 в этом списке можем найти две платы: xiao_esp32c3 и esp32c3_devkitm. Моя плата очень похожа на последнюю, поэтому переходим в корневую папку с проектом и выполняем команду:
    Код (Bash):
    west build -b esp32c3_devkitm
    Скорее всего, мы получим кучу ошибок. Это потому, что не указаны некоторые опции. Задаются они также с помощью вызова:
    Код (Bash):
    west build -t menuconfig
    И теперь мы увидим наш конфигуратор:
    upload_2023-6-18_19-32-4.png
    Для задания имени нашей платы необходимо пройти по пути (путь указан в самом верху):
    upload_2023-6-18_19-33-9.png
    Также нам необходимо указать, что наше устройство является периферийным:
    upload_2023-6-18_19-35-17.png
    Также не забываем, что мы используем SPI. Включаем и его:
    upload_2023-6-18_19-39-59.png
    И теперь можем собрать наш проект:
    Код (Bash):
    west build
    При повторной сборке уже нет необходимости указывать, для какой платы мы собираем.
    Но как быть, если мы вдруг захотим собрать наш проект для другой платы? Для этого надо вызвать сборку с функцией очистки:
    Код (Bash):
    west build -p -b esp32c3_devkitm
    или полностью удалить папку build в корне нашего проекта. И тут мы с грустью обнаруживаем, что все наши опции конфигуратора....исчезли. Как быть? Заново вызывать конфигуратор и снова отмечать опции? А если этих опций не 5-6, как у нас, а гораздо больше?
     
    8bitai, DetSimen и ИгорьК нравится это.
  5. parovoZZ

    parovoZZ Гуру

    Для таких случаев в Zephyr предусмотрен специальный файл, который размещается в корне проекта. Файлу необходимо дать имя prj.conf и описать в нём все необходимые опции:
    Код (Text):
    CONFIG_SPI=y
    CONFIG_BT=y
    CONFIG_BT_PERIPHERAL=y
    CONFIG_BT_DEVICE_NAME="Smart LED"
    Zephyr использует систему сборки CMake, поэтому не забываем про файл CMakeLists.txt :
    Код (Text):
    cmake_minimum_required(VERSION 3.22)
    find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
    project(bluetooth)

    target_sources(app PRIVATE src/main.c)
    В итоге структура нашего проекта выглядит так:
    Код (Bash):
    andrey@debian:~/myZephyr/bluetooth$ tree
    .
    ├── boards
    │   └── esp32c3_devkitm.overlay
    ├── CMakeLists.txt
    ├── prj.conf
    └── src
        └── main.c

    2 directories, 4 files
    Собираем
    Код (Bash):
    west build -b esp32c3_devkitm
    Подключаем плату в USB порт и прошиваем:
    Код (Bash):
    west flash
    Zephyr за кадром самостоятельно вызовет необходимый прошивальщик из состава тулчейна (в нашем случае это ESP). Я использую GNU Linux и у меня плата является единственным устройством USB, поэтому она подключается к порту USB0. Этот же порт является дефолтным для прошивальщика.
    Для подключения со смартфона к устройству BLE я использую программу nRF connect. Так же есть BLE Scanner, но по функционалу она несколько беднее. После запуска программа отобразит все BLE устройства, которые вещают в округе. Среди них находим наш Smart LED, подключаемся и в списке характеристик мы видим три наши характеристики. Нажимаем символ со стрелкой вверх, вводим значение в HEX формате (возможно потребуется выбрать тип значения - BYTE), жмём SEND и радостно наблюдаем:

    blueesp.jpg
     
    8bitai, ИгорьК и DetSimen нравится это.
  6. parovoZZ

    parovoZZ Гуру

    Но, постойте! Запахло утюгом! Хотя утюг я не включал! Откуда исходит тепло??? Аааа, это наша плата... Хм. Вроде Low Energy...почему так много тепла? Давайте измерим температуру:
    tempesp.jpg
    У меня сейчас в комнате 27 градусов, а это китайское чудо поддаёт ещё десятку. Давайте погасим светодиод и измерим ток:
    currentesp.jpg
    ЖРЁТ КАК НЕ В СЕБЯ
     
    Последнее редактирование: 19 июн 2023
    8bitai и ИгорьК нравится это.
  7. parovoZZ

    parovoZZ Гуру

    Итак, давайте разбираться - из-за чего такой аппетит? Откроем даташит и посмотрим, какая часть тока это проц, а какая радио.
    upload_2023-6-21_23-1-31.png
    Т.е. 30 мА - это проц, а всё остальное - это радио.
    Какие есть пути снижения тока? Радио - отключать, когда не требуется, проц - снижать частоту, а когда он не требуется и вовсе останавливать тактирование. И всё это ESP32 умеет, но умеет ли Zephyr? На одной из конференций Zephyr было заявлено, что масштабирование частоты Zephyr не поддерживает. Ну хорошо. А что со сном? Даташит утверждает, что в режиме Light sleep радио хоть и не работает, но bluetooth соединение сохраняется.
    Zephyr поддерживает как deep, так и light sleep, правда через вызов API ESP-IDF, что уже противоречит идеологии Zephyr об о переносе кода на другую платформу.
    В списке источников, которые могут пробудить проц после Light sleep, bluetooth присутствует.
    upload_2023-6-21_23-12-29.png
    Но Zephyr поддерживает любое пробуждение, кроме bluetooth. Мало того, любые мои попытки перевести проц в light sleep приводили к разрыву соединения.
    Решение было найдено, но, увы, не на фреймворке Zephyr, а на родном ESP-IDF от Espressif.

    Итак. Что имеем в сухом остатке? Zephyr имеет очень приятную в работе экосистему, очень лёгкий вход в bluetooth (что не отменяет штудирование книжек). Но минусы всё перевешивают: на нём также, как и на ардуино, невозможно осуществить тонкую настройку процессора. Необходимо изучить синтаксис devicetree для применяемой платформы. Поэтому Zephyr пока отложим, а будем ставить ESP-IDF и двигаться дальше в изучении bluetooth. Там много ещё чего интересного!
     
    8bitai, ИгорьК и DetSimen нравится это.