Программирование ARM ESP32: программирование сопроцессора ULP Thu, April 18 2024  

Поделиться

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

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

ESP32: программирование сопроцессора ULP Печать
Добавил(а) microsin   

Сопроцессор ULP это простой автомат конечных состояний (FSM), который был разработан для проведения измерений с использованием ADC, датчика температуры и внешних датчиков I2C, когда основные ядра устройства находятся в режиме глубокого сна (deep sleep mode, см. [2]). Сопроцессор ULP может обращаться к области памяти RTC_SLOW_MEM и регистрам периферийных устройств RTC_CNTL, RTC_IO и SARADC. Сопроцессор ULP использует 32-битные инструкции фиксированной длины, 32-битную адресацию памяти, и имеет четыре 16-разрядных регистров общего назначения. Этот сопроцессор в среде разработки ESP-IDF [4] упоминается как ULP FSM.

[Установка тулчейна]

Код для сопроцессора ULP FSM разрабатывается на ассемблере, и компилируется с помощью тулчейна binutils-esp32ulp.

Если Вы установили ESP-IDF с системой сборки в соответствии с руководством быстрого старта (Getting Started Guide), то тулчейн ULP FSM уже установлен.

[Программирование ULP FSM]

ULP FSM может быть запрограммирован с использованием поддерживаемого набора инструкций. Альтернативно его можно программировать макросами языка C на main CPU. Подробнее с методами программирования ULP FSM ознакомьтесь в документации [6, 7].

[Компиляция кода ULP]

Для компиляции кода ULP FSM как части компонента, должны быть выполнены следующие шаги:

1. Код ULP FSM, написанный на ассемблере, должен быть добавлен в проект в виде одного или нескольких файлов с расширением *.S. Эти файлы должны быть помещены в отдельный каталог внутри директории компонента, например в каталог ulp/.

2. Вызовите ulp_embed_binary из компонента CMakeLists.txt после регистрации. Например:

...
idf_component_register()
 
set(ulp_app_name ulp_${COMPONENT_NAME})
set(ulp_s_sources ulp/ulp_assembly_source_file.S)
set(ulp_exp_dep_srcs "ulp_c_source_file.c")
 
ulp_embed_binary(${ulp_app_name} "${ulp_s_sources}" "${ulp_exp_dep_srcs}")

Первый аргумент для ulp_embed_binary указывает имя двоичного файла ULP FSM. Указываемое здесь имя будет также использоваться другими генерируемыми артефактами, такими как файл ELF, map-файл, файл заголовка и файл экспорта линкера. Второй аргумент указывает файлы исходного кода ассемблера для ULP FSM. И наконец, третий аргумент задает список исходных файлов компонентов, которые подключают в себя генерируемый файл заголовка. Этот список нужен для корректной сборки зависимостей и обеспечения создания файла заголовка перед компиляцией любого из этих файлов. См. секцию ниже для описания концепции генерируемых файлов заголовка приложений ULP.

3. Выполните сборку приложения, как обычно (командой idf.py app). Внутри этого процесса система будет выполнять следующие шаги для сборки программы ULP FSM:

3.1. Обработка C-препроцессором каждого файла ассемблера (например foo.S). Этот шаг сгенерирует обработанные файлы ассемблера (foo.ulp.S) в директории сборки компонента. Также этот шаг генерирует файлы зависимостей (foo.ulp.d).

3.2. Каждый из обработанных препроцессором файлов ассемблера (foo.ulp.S) компилируется. Это на выходе даст файлы объектного кода (foo.ulp.o) и файлы листинга (foo.ulp.lst). Файлы листинга генерируются для отладки, и они не используются на дальнейших стадиях процесса сборки.

3.3. Запускается шаблон скрипта линкера через препроцессор C. Шаблон находится в директории components/ulp/ld.

3.4. Объектные файлы линкуются в выходной ELF-файл (ulp_app_name.elf). На этом шаге генерируется map-файл (ulp_app_name.map), который также полезен для отладки.

3.5. ELF-файл конвертируется в двоичный файл (ulp_app_name.bin), который может быть встроен в приложение.

3.6. Генерируется список глобальных символов (ulp_app_name.sym) в ELF-файле с помощью esp32ulp-elf-nm.

3.7. Создается LD export script и файл заголовка (ulp_app_name.ld и ulp_app_name.h), содержащий символы из ulp_app_name.sym. Это делается с помощью утилиты esp32ulp_mapgen.py.

3.8. Сгенерированный двоичный файл добавляется в список двоичных файлов для встраивания в приложение.

[Доступ к переменным программы ULP FSM]

Глобальные символы, определенные в программе ULP FSM, могут использоваться в основной программе.

Например, программа ULP FSM может определить переменную measurement_count, которая задает количество измерений ADC, которое программа должна произвести перед пробуждением чипа из режима deep sleep:

.global measurement_count
measurement_count:      .long 0
 
                        // после этого используется measurement_count:
                        move r3, measurement_count
                        ld r3, r3, 0

Основная программа должна инициализировать эту переменyую перед тем, как будет запущена программа ULP. Система сборки делает это возможным путем генерации файлов ${ULP_APP_NAME}.h и ${ULP_APP_NAME}.ld, которые определяют глобальные символы, присутствующие в программе ULP. Каждый глобальный символ в программе ULP включен в эти файлы с префиксом ulp_.

Заголовочный файл содержит декларацию символа:

extern uint32_t ulp_measurement_count;

Обратите внимание, что все символы (переменные, массивы, функции) декларируются как uint32_t. Для функций и массивов возьмите адрес их символа и сделайте приведение (type cast) к нужному типу.

Генерируемый файл скрипта линкера определяет ячейки для символов в памяти типа RTC_SLOW_MEM:

PROVIDE ( ulp_measurement_count = 0x50000060 );

Для доступа к переменным программы ULP из основной программы сгенерированный файл заголовка должен быть подключен директивой #include. Это также позволяет обращаться к переменным программы ULP так же, как и к обычным переменным:

#include "ulp_app_name.h"
 
void init_ulp_vars()
{
   ulp_measurement_count = 64;
}

Обратите внимание, что программа ULP FSM может использовать только младшие 16 бит каждого 32-битного слова в памяти RTC, потому что регистры 16-разрядные, и нет инструкции для загрузки регистра из старшей части 32-разрядного слова. Подобным образом инструкции сохранения ULP запишут только 16 младших бит в 32-битное слово памяти RTC. Старшие 16 бит записываются значением, которое зависит от адреса инструкции сохранения, таким образом, когда основная программа читает переменную, записанную сопроцессором ULP, необходимо маскировать (сбрасывать в 0) старшие 16 бит, например:

printf("Последнее измеренное значение: %d\n", ulp_last_measurement & UINT16_MAX);

[Запуск программы ULP FSM]

Для запуска программы ULP FSM основное приложение должно загрузить программу ULP в память RTC с помощью функции ulp_load_binary(), и затем запустить её вызовом функции ulp_run().

Обратите внимание, что для работы с ULP в menuconfig должна быть разрешена опция "Enable Ultra Low Power (ULP)". Для выбора типа используемого ULP должна быть установлена опция "ULP Co-processor type". Чтобы зарезервировать память для ULP, опция "RTC slow memory reserved for coprocessor" должна быть установлена в достаточно большое значение, чтобы в зарезервированную область поместились код ULP и данные. Если компоненты приложения содержат несколько программ ULP, то размер памяти RTC должен быть достаточен для хранения самой большой из них.

Каждая программа ULP встраивается в приложение ESP-IDF как двоичные данные BLOB. Приложение может обращаться к этому BLOB и загружать его следующим образом (предположим, что ULP_APP_NAME было определено как ulp_app_name):

extern const uint8_t bin_start[] asm("_binary_ulp_app_name_bin_start");
extern const uint8_t bin_end[]   asm("_binary_ulp_app_name_bin_end");
 
void start_ulp_program()
{
   ESP_ERROR_CHECK( ulp_load_binary(
      0, // адрес загрузки установлен в 0 при использовании скриптов линкера по умолчанию
      bin_start,
      (bin_end - bin_start) / sizeof(uint32_t)) );
}

Как только программа ULP была загружена в память RTC, приложение может запустить её передачей адреса точки входа в функцию ulp_run:

ESP_ERROR_CHECK (ulp_run(&ulp_entry - RTC_SLOW_MEM));

Объявление символа точки входа происходит из сгенерированного файла заголовка ${ULP_APP_NAME}.h, упомянутого выше. В исходном коде ассемблера приложения ULP FSM этот символ должен быть помечен как .global:

        .global entry
entry:
        // здесь начинается ULP-код

[Выполнение программы ESP32 ULP]

Сопроцессор ULP запускается таймером. Таймер стартует, как только был произведен вызов ulp_run(). Таймер считает количество тиков RTC_SLOW_CLK (по умолчанию тики генерируются внутренним RC-генератором 150 кГц). Количество тиков устанавливается с помощью регистров SENS_ULP_CP_SLEEP_CYCx_REG (здесь x = 0 .. 4). Когда ULP запускается первый раз, для количества тиков используется SENS_ULP_CP_SLEEP_CYC0_REG. Позже программа ULP может выбрать другой регистр SENS_ULP_CP_SLEEP_CYCx_REG с использованием инструкции SLEEP.

Приложение может установить значения периода таймера ULP (SENS_ULP_CP_SLEEP_CYCx_REG, x = 0 .. 4) с помощью функции ulp_set_wakeup_period.

Как только таймер отсчитает количество тиков, установленное выбранным регистром SENS_ULP_CP_SLEEP_CYCx_REG, сопроцессор ULP включится, и запустит программу с точки входа, установленной вызовом ulp_run().

Программа ULP работает до тех пор, пока она не встретит инструкцию halt, или недопустимую инструкцию. Программа зависнет, сопроцессор ULP выключится, и таймер запустится снова.

Для запрета таймера (что не даст программе ULP запуститься повторно), очистите бит RTC_CNTL_ULP_CP_SLP_TIMER_EN в регистре RTC_CNTL_STATE0_REG. Это может быть сделано как из кода ULP, так и из кода основной программы приложения.

[Примеры приложений]

system/ulp_fsm/ulp: сопроцессор ULP FSM подсчитывает импульсы на IO, когда основной CPU находится в режиме deep sleep.

system/ulp_fsm/ulp_adc: сопросцессор ULP FSM опрашивает ADC, когда основной CPU находится в режиме deep sleep.

[Справочник по API-функциям]

Заголовочный файл: components/ulp/ulp_fsm/include/ulp_fsm_common.h.

Функция Описание
ulp_process_macros_and_load Разрешает все ссылки на макросы в программе ULP, и загружает её в память RTC.
ulp_load_binary Загрузит бинарный код программы ULP в память RTC.
ulp_run Запустит программу ULP, загруженную в память RTC. Бинарник программы ULP должен иметь следующий формат (все значения с порядком следования байт little-endian):

a. MAGIC, (значение 0x00706c75, 4 байта).
b. TEXT_OFFSET, смещение секции .text от начала бинарника (2 байта).
c. TEXT_SIZE, размер секции .text (2 байта).
d. DATA_SIZE, размер секции .data (2 байта).
e. BSS_SIZE, размер секции .bss (2 байта).
f. (TEXT_OFFSET - 12) байт произвольных данных (они не будут загружены в память RTC).
g. Секция .text.
h. Секция .data.

Скрипт линкера в components/ulp/ld/esp32.ulp.ld генерирует ELF-файлы, которые соответствуют такому формату. Этот скрипт генерирует бинарники с load_addr == 0.

Заголовочный файл: components/ulp/ulp_common/include/ulp_common.h.

Функция Описание
ulp_set_wakeup_period Установит одно из значений периода пробуждения ULP. Сопроцессор ULP запустит свою программу, когда таймер пробуждения (wakeup timer) дойдет при счете до этого значения (оно называется периодом счета, параметр period_us). Для ESP32 могут быть запрограммированы 5 значений периода в регистры SENS_ULP_CP_SLEEP_CYCx_REG (здесь x = 0..4), и для ESP32-S2/ESP32-S3 одно значение периода в регистре RTC_CNTL_ULP_CP_TIMER_1_REG. По умолчанию для ESP32 таймер пробуждения будет использовать период, установленный в SENS_ULP_CP_SLEEP_CYC0_REG, т. е. период номер 0. Код программы ULP может использовать инструкцию SLEEP для выбора, какой из регистров SENS_ULP_CP_SLEEP_CYCx_REG должен использоваться для последующих пробуждений. Однако имейте в виду, что инструкция SLEEP, выданная (из программы ULP), когда система находится в режиме deep sleep, не даст эффекта, и будет использоваться счетчик циклов 0. Для ESP32-S2/ESP32-S3 инструкция SLEEP не существует, вместо неё используется инструкция WAKE.
ulp_timer_stop Остановит таймер ULP.
ulp_timer_resume Возобновит счет таймера ULP.

Примечание: полное описание макросов, типов данных, API-функций и их параметров см. в документации [1].

[Словарик]

BLOB binary large object, двоичные данные переменной длины.

FSM Finite State Machine, автомат конечных состояний.

ULP Ultra Low Power, режим с очень низким потреблением энергии.

[Ссылки]

1. ESP32 ULP Coprocessor programming site:docs.espressif.com.
2. ESP32: режимы пониженного энергопотребления.
3. ESP32: управление энергопотреблением.
4. Установка среды разработки ESP-IDF для ESP32.
5. espressif / binutils-esp32ulp site:github.com.
6. ESP32: система команд сопроцессора ULP.
7. Programming ULP FSM coprocessor using C macros (legacy) site:docs.espressif.com.

 

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


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

Top of Page