Тестирование потребления энергии в lora-at

Как следует разобравшись с тем, как работает Power Profiler Kit 2 (PPK2), я решил протестировать разные режимы работы. На самом деле lora-at - не такое уж и простое приложение. В нём есть работа с bluetooth и чипом sx127x, обработка команд с UART шины и режим глубокого сна. Есть, где развернуться.

Методология измерений

Перед тем как начинать что-то измерять, необходимо определиться с тем, как это будет измеряться.

Во-первых, я собираюсь использовать PPK2 по-максимуму. Некоторые тесты требует десятки измерений и делать их вручную не имеет смысла. Я написал небольшой скрипт, который позволял отправлять разные AT команды на устройство, а потом выбирать данные из .CSV файла и группировать нужным образом. Так вот, чтобы понять когда началось измерение, а когда закончилось, я использовал встроенный логический анализатор.

Во-вторых, некоторые процессы протекают быстро и потребляют много энергии, а некоторые - медленно и потребляют мало энергии. Сравнивать среднее потребление тока в таком случае некорректно. Там, где имеет смысл сравнивать такие процессы, я сравнивал величину заряда. Она измеряется в кулонах. Кулон (Кл) — это величина заряда, прошедшего через сечение проводника при силе тока 1 А за время 1 с. PPK2 интегрирует график по времени и может очень быстро отображать потраченный заряд за выбранный интервал. Прям чувствуется атмосфера лабы по физике.

В-третьих, параметров, которые могут влиять на потребление энергии, очень много. Для этого я фиксировал одни и менял другие. Такой подход позволил хоть как-то сравнивать и анализировать результаты. Например, для всех тестов я использовал Heltec LoRa 32 v2, который работал на частоте 80Мгц.

В-четвёртых, PPK2 сэмплирует со скоростью 100Кгц, что генерирует достаточно большое количество данных. Все эти точки невозможно отобразить на графике, поэтому я брал среднее за некоторый интервал. Это не влияет на результирующее значение потраченного заряда, но немного сглаживает сам график.

В-пятых, измеряется потребление энергии всей платы, а не конкретных компонент.

sx127x

Начать, пожалуй, лучше всего с самого основного - чипа sx127x. Он поддерживает разные типы модуляции, приём и передачу данных, их скорость, усилители мощности и многое другое. Я решил померить лишь те, которые теоретически могут повлиять на потребление энергии. И даже после этого, тестов оказалось приличное количество.

Получение данных

Для тестирования приёмника, я настроил второй модуль, который отправляет короткое сообщение:

AT+LORATX=CAFE,433200012,125000,9,5,18,10,8,4,0,0,1,0

А измеряемый модуль принимает:

AT+LORARX=433200012,125000,9,5,18,10,8,4,0,0,1,0

После чего, то же самое для FSK модуляции:

AT+FSKTX=CAFE,433200012,4800,5000,4,12AD,0,2,1,4,0,0

И приём:

AT+FSKRX=433200012,4800,5000,4,12AD,0,2,1,4,5000,20000

В итоге получился следующий график:

На нём видно, что:

  • LoRa гораздо быстрее обрабатывает сообщение: 13.6мс против 19мс
  • Более энергоэффектиное. За счёт скорости обработки общее потребление 511мкКл против 775мкКл
  • Пиковое потребление чуть ниже

На самом деле такое сравнение некорректно. Потребление энергии напрямую зависит от скорости передачи данных. Чем выше скорость, тем быстрее обрабатывается сообщение и меньше тратится энергии.

CAD режим

В sx127x есть специальный режим, который называется CAD (Channel Activity Detection). Он используется для оптимизации энергопотребления приёмника. Идея заключается в следующем:

  • приёмник включается на полную мощность на определённое время
  • после чего переходит в режим пониженного энергопотребления и пытается проанализировать полученный сигнал
  • если обнаружена преамбула пакета, то чип генерирует прерывание и можно переключаться в обычным режим приёма
  • если преамбула не обнаружена, то чип выжидает некоторое время в режиме низкого энергопотребления и возвращается к шагу 1

Диаграмма выглядит следующим образом:

Consumption
Consumption
CadDone
CadDone
NSS
NSS
CAD duration
CAD duration
Time in RX mode
Time in RX mode
IDDC_L
IDDC_L
IDDR_L
IDDR_L
Text is not SVG - cannot display

При этом, согласно спецификации, потребление может уменьшаться чуть ли не в два раза:

Bandwidth (kHz) Full Rx, IDDR_L (mA) Processing, IDDC_L (mA)
7.8 to 41.7 11 5.2
62.5 11 5.6
125 11.5 6
250 12.4 6.8
500 13.8 8.3

Запустить приём в CAD режиме можно следующей командой:

AT+LORACADRX=433200012,125000,9,5,18,10,8,4,0,0,1,0

В итоге мне удалось получить:

Тут сходу видно несколько интересных вещей:

  • Почему-то потребление тока в режиме поиска преамбулы чуть больше, чем в обычном режиме
  • Разница между активным режимом и пассивным ~6мА, что действительно похоже на правду при 125кГц
  • После выхода из CAD режима и перед переходом обратно проходит примерно 2мс. Это время нужно ESP32, чтобы обработать прерывание и опять перейти в CAD режим. Поскольку CAD режим заканчивается тем, что переходит в STANDBY режим, потребление энергии существенно ниже. 2мс на обработку прерывания - это очень много. Если в это время начнётся передача сообщения, то приёмник может пропустить его начало. Контрольная сумма не совпадёт и всё сообщение не будет принято. С другой стороны, на практике всё работает отлично.

Если сравнивать энергопотребление, то CAD режим всё-таки более эффективный:

  • Потраченый заряд 1.41мКл против 1.52мКл за 40мс
  • Средний ток 35.27мА против 37.85мА

Отправка данных

Потребление тока при отправке данных зависит от:

  • скорости передачи
  • типа модуляции
  • мощности передатчика
  • длины сообщения

Чтобы упростить тестирование, я отправлял сообщение длиной 255 байт мощностью 4dbm. При этом менял скорость передачи и тип модуляции. Вот пример команды для тестирования FSK:

AT+FSKTX=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe,433200012,1200,5000,4,12AD,0,2,1,4,240,0

На графике видна однозначная зависимость. Чем больше скорость передачи, тем меньше тратится энергии. Кстати, график к районе 76800 бод не всегда был такой. Когда я впервые замерил скорость, оказалось, что она в два раза меньше, чем при 38400 бод. И её увеличение вообще никак не влияло на потребление. Немного повозившись, я нашёл багу в коде. При чтении из UART происходило переполнение uint16_t и устанавливалась гораздо меньшая скорость. Упс!

С отправкой данных в режиме LoRa всё гораздо сложнее. На скорость могут влиять: bandwidth, spreading factor и coding rate. Например, последний контролирует сколько бит используется для кодов коррекции ошибок. Чем больше, тем надёжнее передача, но при этом дольше передаются данные.

Я зафиксировал coding rate=4 и менял лишь bandwidth и spreading factor:

AT+LORATX=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe,433200012,500000,6,5,18,8,0,1,0,255,4,240,0

Небольшой анализ результатов:

  • Чем больше spreading factor, тем дольше передаётся сообщение и тем больше тратится энергии
  • Самая быстрая передача данных в LoRa (коэффициент 6 и ширина канала 500Кгц) тратит энергии больше, чем самая быстрая передача FSK (скорость 300Кбод)

В следующем тесте я сделал наоборот: зафиксировал скорость передачи данных и менял выходную мощность от -4 до 20.

AT+LORATX=CA,433200012,125000,9,5,18,8,0,1,1,0,-4,240,0
AT+LORATX=CA,433200012,125000,9,5,18,8,0,1,1,0,-3,240,0
...
AT+LORATX=CA,433200012,125000,9,5,18,8,0,1,1,0,17,240,1
AT+LORATX=CA,433200012,125000,9,5,18,8,0,1,1,0,20,240,1

Интересно, почему при более высоком уровне мощности графики начинают пересекаться? Я передавал абсолютно одинаковое сообщение с одинаковыми параметрами. Время на передачу было одного и то же. Значит потраченный заряд должен линейно зависить от тока. Но вместо этого зависимость нелинейная.

Кстати, при 7dbm потребление тока достаточно ровное. А вот при 10dbm уже нет. Шумит внутренний усилитель мощности?

Помимо разных уровней мощности, в чипе есть три разных физических пина, к которым может подключаться антенна: RFO_LF, RFO_HF и PA_BOOST. +20dbm может передаваться только по PA_BOOST. Но если передавать +7dbm, то есть ли разница какой пин использовать?

AT+LORATX=CA,168000012,125000,9,5,18,8,0,1,1,0,7,240,0
AT+LORATX=CA,433200012,125000,9,5,18,8,0,1,1,0,7,240,0
AT+LORATX=CA,433200012,125000,9,5,18,8,0,1,1,0,7,240,1

Да, разница есть.

  • Если сравнивать RFO_LF и RFO_HF, то видно, что потребление тока на частоте 168Мгц выше. Видимо, на этих частотах антенна не согласована больше.
  • Потребление тока при передаче через отдельный усилитель мощности и PA_BOOST пин больше, чем при передаче через RFO_HF. Наверное, разница уходит на питание того самого усилителя.

Токовая защита усилителя

В чипе есть защита усилителя от превышения выходящего тока. При этом приговариваются слова про батарею, химические компоненты и пиковое потребление тока. Поначалу я ничего не понял. Немного почитав теорию, стало чуть более понятно. Усилитель рассчитан на импеданс антенны 50Ом. Если импеданс не совпадает, либо перестал совпадать (антенна заржавела), то для генерации заданного уровня мощности, будет тратится всё больше и больше тока. Это может привести к тому, что усилитель может перегореть. Я сравнил отправку сообщения: без защиты (240мА), с небольшим ограничением (140мА), с максимальным ограничением (45мА).

AT+LORATX=CA,140200012,125000,9,5,18,8,0,1,1,0,17,240,1
AT+LORATX=CA,140200012,125000,9,5,18,8,0,1,1,0,17,140,1
AT+LORATX=CA,140200012,125000,9,5,18,8,0,1,1,0,17,45,1

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

Сравнение с C++ версией

Как я уже писал, lora-at изначально была реализована на C++ с помощью библиотек разного уровня качества. Моя теория заключалась в том, что переписав проект на С, я бы смог достичь более высокой эффективности и простоты. Одна из целей была достигнута - сборка проекта стала занимать меньше времени и размер прошивки уменьшился в два раза. Но что насчёт энергопотребления?

Работа чипа sx127x не зависит от выбранного языка, но вот всё остальное может теоретически отличаться.

Подключение по bluetooth

Одно из основных отличий первой версии от второй является работа с bluetooth. Если в первой версии использовалась С++ библиотека поверх Bluedroid, то во второй версии я использовал Nimble. В документации сказано, что Nimble - это более легковесная реализация BLE протокола. Но так ли это на самом деле? И есть ли разница в скорости работы? Это очень легко проверить. Для этого можно использовать команду:

AT+BLUETOOTH=B8:27:EB:6C:7C:F8

Эта команда сделает следующее:

  • запустит подключение к bluetooth серверу по адресу B8:27:EB:6C:7C:F8
  • выполнит поиск нужного GATT сервиса и характеристики

Тут стоит отметить, что у меня не всегда получалось подключиться к серверу! В какой-то момент мне пришлось поднести плату поближе к RaspberryPI. Тут-то и обнаружилось, что чем ближе к bluetooth серверу, тем быстрее происходит подключение. Даже если я использовал разные версии приложения, то время на подключение было очень разным. Что давало сильный разброс в результатах. Например, с 3-х метров С-код мог потратить 386.8мКл, а C++ вообще не подключиться. Или с одного метра С++ тратил 308.3мКл, а С-код - 150.2мКл. В общем, так себе получился тест.

Глубокий сон

Глубокий сон - это особый режим работы, при котором отключена вся периферия и ESP32 работает потребляя минимальное количество энергии. В этом режиме невозможно сделать ничего полезного, поэтому глубокий сон обычно чередуется активной фазой. В активной фазе устройство делает полезные операции, после чего опять уходит в сон. Чем меньше активная фаза, тем меньше энергии будет потреблять устройство в конечном итоге. В lora-at можно конфигурировать интервал глубокого сна. Например, команда ниже задаст конфигурацию: после 15 секунд бездействия перейти на 15 секунд в режим глубокого сна.

AT+DSCONFIG=15000,15000

Во время активной фазы lora-at подключается по bluetooth к серверу, получает расписание следующего включения и снова засыпает.

  • Потребление энергии сильно зависит от скорости подключения к bluetooth серверу. Причём иногда это занимает 2 секунды, а иногда 7. Из-за такой нестабильности сложно делать какие-то выводы
  • Потребление энергии достаточно сильно варьируется: от 474.6мКл (С++ версия) до 179.2мКл (С версия)
  • Время старта C++ чуть меньше. ~500мс против ~664мс. Однако, мне ни разу не удалось получить меньше 7 секунд для подключения С++ версии. Возможно, она пропускала какое-то bluetooth событие и вынуждена была ожидать перепосылку от сервера.

Потребление энергии в режиме глубокого сна - 1.7мА. Это примерно в миллион раз больше, чем теоретический минимум ESP32. Я потратил примерно 2 недели, чтобы разобраться в чём проблема. После всех ухищрений мне удалось уменьшить потребление до 10мкА.

Работа на пониженной частоте

ESP32 может работать как на частоте 240Мгц, так и на 80Мгц. Очевидно, это уменьшает потребление энергии, за счёт более медленной работы. Но куда нам спешить, когда SPI шина работает на частоте 3Мгц?

Режим работы Среднее потребление тока (C) Среднее потребление тока (C++)
240Мгц 48.27 73.36
160Мгц 35.81 50.64
80Мгц 26.38 39.44
  • Как и ожидалось, чем меньше частота микроконтроллера, тем меньше потребление энергии
  • Иногда у меня получалось получить ещё более низкие значения, но я не смог понять как их воспроизвести

Кстати, разница между С и С++ версией связана с тем, как обрабатываются события. В С-версии вся обработка происходит в отдельных тасках FreeRTOS. Основной таск вообще перестаёт работать:

I (595) main_task: Returned from app_main()

А вот С++ версия проверяет события в бесконечном цикле. Если добавить похожий цикл в С-версию, то потребление энергии будет одинаковым:

uint64_t counter = 0;
uint64_t active = 1000000;
while (true) {
  counter++;
  if (counter == active) {
    counter = 0;
    vTaskDelay(pdMS_TO_TICKS(5000));
  }
}

Обработка прерываний UART

Мне настолько понравилось разбираться в потреблении энергии, что я решил измерить совершенно странные вещи. Например, мне стало интересно сколько энергии тратится на обработку полученного символа по UART. Сильно увеличенный график выглядит следующим образом:

  • Каждое нажатие вызывает скачок потребления тока примерно на 10мА и длится примерно 1.4мс
  • Не очень понятно почему два скачка
  • Если положить в буфер сразу несколько символов с помощью CMD+V, то это сгенерирует одно прерывание и очень похожий график

Если за 1.4мс потрачено 37.32мкКл, а в режиме простоя за это же время тратится 35.11мкКл, то нажатие на кнопку обошлось в 2.21мкКл.

Выводы

  • С-код в среднем более энергоэффективен за счёт использования задач FreeRTOS, а не бесконечного цикла
  • По графику потребления энергии можно находить баги в коде
  • Bluetooth Low Energy - не самый предсказуемый в плане потребления энергии протокол
  • Даже если код перехода в глубокий сон вызван правильным образом - это не гарантирует того, что устройство действительно потребляет мало энергии. Нужно мерить!