ULP LP-Core 协处理器编程

[English]

ULP LP-Core(低功耗内核)协处理器是 ESP32-C6 中 ULP 的一个变型。它具有超低功耗,同时还能在主 CPU 处于低功耗模式时保持运行。因此,LP-Core 协处理器能够在主 CPU 处于睡眠模式时处理 GPIO 或传感器读取等任务,从而显著降低整个系统的整体功耗。

ULP LP-Core 协处理器具有以下功能:

  • 利用基于 RISC-V ISA 的 32 位处理器,包括标准扩展整数 (I)、乘法/除法 (M)、原子 (A) 和压缩 (C)。

  • 中断控制器。

  • 包含一个调试模块,支持通过 JTAG 进行外部调试。

  • 当整个系统处于 active 模式时,可以访问所有的高功耗 (HP) SRAM 和外设。

  • 当 HP 系统处于睡眠模式时,可以访问低功耗 (LP) SRAM 和外设。

编译 ULP LP-Core 代码

ULP LP-Core 代码会与 ESP-IDF 项目共同编译,生成一个单独的二进制文件,并自动嵌入到主项目的二进制文件中。编译操作如下:

  1. 将用 C 语言或汇编语言编写的 ULP LP-Core 代码(带有 .S 扩展名)放在组件目录下的专用目录中,例如 ulp/

  2. 在 CMakeLists.txt 文件中注册组件后,调用 ulp_embed_binary 函数。例如:

    idf_component_register()

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

    ulp_embed_binary(${ulp_app_name} "${ulp_sources}" "${ulp_exp_dep_srcs}")

ulp_embed_binary 的第一个参数为 ULP 二进制文件的文件名,该文件名也用于其他生成的文件,如 ELF 文件、映射文件、头文件和链接器导出文件。第二个参数为 ULP 源文件。第三个参数为组件源文件列表,用于包含要生成的头文件。要正确构建依赖关系、确保在编译这些文件前创建要生成的头文件,都需要此文件列表。有关 ULP 应用程序生成头文件的概念,请参阅本文档后续章节。

  1. 在 menuconfig 中启用 CONFIG_ULP_COPROC_ENABLEDCONFIG_ULP_COPROC_TYPE 选项,并将后者设置为 CONFIG_ULP_COPROC_TYPE_LP_CORECONFIG_ULP_COPROC_RESERVE_MEM 选项为 ULP 保留 RTC 内存,因此必须设置为一个足够大的值,以存储 ULP LP-Core 代码和数据。如果应用程序组件包含多个 ULP 程序,那么 RTC 内存的大小必须足够容纳其中最大的程序。

  2. 按照常规步骤构建应用程序(例如 idf.py app)。

在构建过程中,采取以下步骤来构建 ULP 程序:

  1. 通过 C 编译器和汇编器运行每个源文件。 此步骤会在组件构建目录中生成目标文件 .obj.c.obj.S,具体取决于处理的源文件。

  2. 通过 C 预处理器运行链接器脚本模板。 模板位于 components/ulp/ld 目录中。

  3. 将对象文件链接到一个 ELF 输出文件中,ulp_app_name.elf。在此阶段生成的映射文件 ulp_app_name.map 可用于调试。

  4. 将 ELF 文件的内容转储到一个二进制文件中,ulp_app_name.bin。此二进制文件接下来可以嵌入到应用程序中。

  5. 使用 riscv32-esp-elf-nm 在 ELF 文件中 生成全局符号列表,ulp_app_name.sym

  6. 创建一个 LD 导出脚本和一个头文件,ulp_app_name.ldulp_app_name.h,并在其中包含 ulp_app_name.sym 中的符号。此步骤可以通过 esp32ulp_mapgen.py 实现。

  7. 将生成的二进制文件添加到要嵌入到应用程序中的二进制文件列表。

访问 ULP LP-Core 程序变量

在主程序中可以使用在 ULP LP-Core 程序中定义的全局符号。

例如,ULP LP-Core 程序定义了一个变量 measurement_count,用来表示程序从深度睡眠中唤醒芯片前所需的 GPIO 测量次数。

volatile int measurement_count;

int some_function()
{
    //读取测量次数以便后续使用。
    int temp = measurement_count;

    ...do something.
}

主程序可以访问 ULP LP-Core 程序全局变量,这是因为构建系统生成了 ${ULP_APP_NAME}.h${ULP_APP_NAME}.ld 文件,文件中定义了 ULP LP-Core 程序中现有的的全局符号。在 ULP LP-Core 程序中定义的每个全局符号都包含在这两个文件中,并具有前缀 ulp_

头文件中包含符号的声明:

extern uint32_t ulp_measurement_count;

注意,所有的符号(变量、数组、函数)都被声明为 uint32_t 类型。对于函数和数组,获取符号的地址并将其转换为合适的类型。

生成的链接器脚本文件定义了 LP_MEM 中符号的位置:

PROVIDE ( ulp_measurement_count = 0x50000060 );

要从主程序访问 ULP LP-Core 程序变量,应使用 include 语句将生成的头文件包含在主程序中,这样就可以像访问常规变量一样访问 ULP LP-Core 程序变量。

#include "ulp_app_name.h"

void init_ulp_vars() {
    ulp_measurement_count = 64;
}

启动 ULP LP-Core 程序

要运行 ULP LP-Core 程序,主应用程序需要先使用 ulp_lp_core_load_binary() 函数将 ULP 程序加载到 RTC 内存中,然后使用 ulp_lp_core_run() 函数进行启动。

每个 ULP LP-Core 程序以二进制 blob 的形式嵌入到 ESP-IDF 应用程序中。应用程序可以按照如下方式引用和加载该 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_lp_core_load_binary( bin_start,
        (bin_end - bin_start)) );
}

将程序加载到 LP 内存后,就可以调用 ulp_lp_core_run() 配置和启动应用程序:

ulp_lp_core_cfg_t cfg = {
    .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, // LP 内核会定期被 LP 定时器唤醒
    .lp_timer_sleep_duration_us = 10000,
};

ESP_ERROR_CHECK( ulp_lp_core_run(&cfg) );

ULP LP-Core 程序流程

ULP LP-Core 协处理器如何启动取决于 ulp_lp_core_cfg_t 中选择的唤醒源。最常见的用例是 ULP 定期唤醒,在进行一些测量后唤醒主 CPU,或者再次进入睡眠状态。

ULP 有以下唤醒源:

ULP 被唤醒时会经历以下步骤:

  1. 初始化系统功能,如中断

  2. 调用用户代码 main()

  3. main() 返回

  4. 如果指定了 lp_timer_sleep_duration_us,则配置下一个唤醒闹钟

  5. 调用 ulp_lp_core_halt()

ULP LP-Core 支持的外设

为了增强 ULP LP-Core 协处理器的功能,它可以访问在低功耗电源域运行的外设。ULP LP-Core 协处理器可以在主 CPU 处于睡眠模式时与这些外设进行交互,并在达到唤醒条件时唤醒主 CPU。以下为支持的外设:

  • LP IO

  • LP I2C

应用示例

  • 在示例 system/ulp/lp_core/gpio 中,ULP LP-Core 协处理器在主 CPU 深度睡眠时轮询 GPIO。

  • 在示例 system/ulp/lp_core/lp_i2c 中,ULP LP-Core 协处理器在主 CPU 深度睡眠时读取外部 I2C 环境光传感器 (BH1750),并在达到阈值时唤醒主 CPU。

API 参考

主 CPU API 参考

Header File

  • components/ulp/lp_core/include/ulp_lp_core.h

  • This header file can be included with:

    #include "ulp_lp_core.h"
    
  • This header file is a part of the API provided by the ulp component. To declare that your component depends on ulp, add the following to your CMakeLists.txt:

    REQUIRES ulp
    

    or

    PRIV_REQUIRES ulp
    

Functions

esp_err_t ulp_lp_core_run(ulp_lp_core_cfg_t *cfg)

Configure the ULP and run the program loaded into RTC memory.

返回

ESP_OK on success

esp_err_t ulp_lp_core_load_binary(const uint8_t *program_binary, size_t program_size_bytes)

Load the program binary into RTC memory.

参数
  • program_binary -- pointer to program binary

  • program_size_bytes -- size of the program binary

返回

  • ESP_OK on success

  • ESP_ERR_INVALID_SIZE if program_size_bytes is more than KiB

void ulp_lp_core_stop(void)

Puts the ulp to sleep and disables all wakeup sources. To restart the ULP call ulp_lp_core_run() with the desired wake up trigger.

Structures

struct ulp_lp_core_cfg_t

ULP LP core init parameters.

Public Members

uint32_t wakeup_source

Wakeup source flags

uint32_t lp_timer_sleep_duration_us

Sleep duration when ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER is specified. Measurement unit: us

Macros

ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU
ULP_LP_CORE_WAKEUP_SOURCE_LP_UART
ULP_LP_CORE_WAKEUP_SOURCE_LP_IO
ULP_LP_CORE_WAKEUP_SOURCE_ETM
ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER

Header File

  • components/ulp/lp_core/include/lp_core_i2c.h

  • This header file can be included with:

    #include "lp_core_i2c.h"
    
  • This header file is a part of the API provided by the ulp component. To declare that your component depends on ulp, add the following to your CMakeLists.txt:

    REQUIRES ulp
    

    or

    PRIV_REQUIRES ulp
    

Functions

esp_err_t lp_core_i2c_master_init(i2c_port_t lp_i2c_num, const lp_core_i2c_cfg_t *cfg)

Initialize and configure the LP I2C for use by the LP core Currently LP I2C can only be used in master mode.

备注

The internal pull-up resistors for SDA and SCL pins, if enabled, will provide a weak pull-up value of about 30-50 kOhm. Users are adviced to enable external pull-ups for better performance at higher SCL frequencies.

参数
  • lp_i2c_num -- LP I2C port number

  • cfg -- Configuration parameters

返回

esp_err_t ESP_OK when successful

Structures

struct lp_core_i2c_pin_cfg_t

LP Core I2C pin config parameters.

Public Members

gpio_num_t sda_io_num

GPIO pin for SDA signal. Only GPIO#6 can be used as the SDA pin.

gpio_num_t scl_io_num

GPIO pin for SCL signal. Only GPIO#7 can be used as the SCL pin.

bool sda_pullup_en

SDA line enable internal pullup. Can be configured if external pullup is not used.

bool scl_pullup_en

SCL line enable internal pullup. Can be configured if external pullup is not used.

struct lp_core_i2c_timing_cfg_t

LP Core I2C timing config parameters.

Public Members

uint32_t clk_speed_hz

LP I2C clock speed for master mode

struct lp_core_i2c_cfg_t

LP Core I2C config parameters.

Public Members

lp_core_i2c_pin_cfg_t i2c_pin_cfg

LP I2C pin configuration

lp_core_i2c_timing_cfg_t i2c_timing_cfg

LP I2C timing configuration

soc_periph_lp_i2c_clk_src_t i2c_src_clk

LP I2C source clock type

Macros

LP_I2C_DEFAULT_GPIO_CONFIG()
LP_I2C_FAST_MODE_CONFIG()
LP_I2C_STANDARD_MODE_CONFIG()
LP_I2C_DEFAULT_SRC_CLK()
LP_CORE_I2C_DEFAULT_CONFIG()

LP 内核 API 参考

Header File

Functions

void ulp_lp_core_update_wakeup_cause(void)

Traverse all possible wake-up sources and update the wake-up cause so that ulp_lp_core_get_wakeup_cause can obtain the bitmap of the wake-up reasons.

uint32_t ulp_lp_core_get_wakeup_cause(void)

Get the wakeup source which caused LP_CPU to wakeup from sleep.

返回

Wakeup cause in bit map, for the meaning of each bit, refer to the definition of wakeup source in lp_core_ll.h

void ulp_lp_core_wakeup_main_processor(void)

Wakeup main CPU from sleep or deep sleep.

This raises a software interrupt signal, if the main CPU has configured the ULP as a wakeup source calling this function will make the main CPU to exit from sleep or deep sleep.

void ulp_lp_core_delay_us(uint32_t us)

Makes the co-processor busy wait for a certain number of microseconds.

参数

us -- Number of microseconds to busy-wait for

void ulp_lp_core_delay_cycles(uint32_t cycles)

Makes the co-processor busy wait for a certain number of cycles.

参数

cycles -- Number of cycles to busy-wait for

void ulp_lp_core_halt(void)

Finishes the ULP program and powers down the ULP until next wakeup.

备注

This function does not return. After called it will fully reset the ULP.

备注

The program will automatically call this function when returning from main().

备注

To stop the ULP from waking up, call ulp_lp_core_lp_timer_disable() before halting.

void ulp_lp_core_stop_lp_core(void)

The LP core puts itself to sleep and disables all wakeup sources.

Header File

Functions

static inline void ulp_lp_core_gpio_init(lp_io_num_t lp_io_num)

Initialize a rtcio pin.

参数

lp_io_num -- The rtc io pin to initialize

static inline void ulp_lp_core_gpio_output_enable(lp_io_num_t lp_io_num)

Enable output.

参数

lp_io_num -- The rtc io pin to enable output for

static inline void ulp_lp_core_gpio_output_disable(lp_io_num_t lp_io_num)

Disable output.

参数

lp_io_num -- The rtc io pin to disable output for

static inline void ulp_lp_core_gpio_input_enable(lp_io_num_t lp_io_num)

Enable input.

参数

lp_io_num -- The rtc io pin to enable input for

static inline void ulp_lp_core_gpio_input_disable(lp_io_num_t lp_io_num)

Disable input.

参数

lp_io_num -- The rtc io pin to disable input for

static inline void ulp_lp_core_gpio_set_level(lp_io_num_t lp_io_num, uint8_t level)

Set rtcio output level.

参数
  • lp_io_num -- The rtc io pin to set the output level for

  • level -- 0: output low; 1: output high.

static inline uint32_t ulp_lp_core_gpio_get_level(lp_io_num_t lp_io_num)

Get rtcio output level.

参数

lp_io_num -- The rtc io pin to get the output level for

static inline void ulp_lp_core_gpio_set_output_mode(lp_io_num_t lp_io_num, rtcio_ll_out_mode_t mode)

Set rtcio output mode.

参数
  • lp_io_num -- The rtc io pin to set the output mode for

  • mode -- RTCIO_LL_OUTPUT_NORMAL: normal, RTCIO_LL_OUTPUT_OD: open drain

static inline void ulp_lp_core_gpio_pullup_enable(lp_io_num_t lp_io_num)

Enable internal pull-up resistor.

参数

lp_io_num -- The rtc io pin to enable pull-up for

static inline void ulp_lp_core_gpio_pullup_disable(lp_io_num_t lp_io_num)

Disable internal pull-up resistor.

参数

lp_io_num -- The rtc io pin to disable pull-up for

static inline void ulp_lp_core_gpio_pulldown_enable(lp_io_num_t lp_io_num)

Enable internal pull-down resistor.

参数

lp_io_num -- The rtc io pin to enable pull-down for

static inline void ulp_lp_core_gpio_pulldown_disable(lp_io_num_t lp_io_num)

Disable internal pull-down resistor.

参数

lp_io_num -- The rtc io pin to disable pull-down for

Macros

RTCIO_OUTPUT_NORMAL
RTCIO_OUTPUT_OD

Enumerations

enum lp_io_num_t

Values:

enumerator LP_IO_NUM_0

GPIO0, input and output

enumerator LP_IO_NUM_1

GPIO1, input and output

enumerator LP_IO_NUM_2

GPIO2, input and output

enumerator LP_IO_NUM_3

GPIO3, input and output

enumerator LP_IO_NUM_4

GPIO4, input and output

enumerator LP_IO_NUM_5

GPIO5, input and output

enumerator LP_IO_NUM_6

GPIO6, input and output

enumerator LP_IO_NUM_7

GPIO7, input and output

Header File

Functions

esp_err_t lp_core_i2c_master_read_from_device(i2c_port_t lp_i2c_num, uint16_t device_addr, uint8_t *data_rd, size_t size, int32_t ticks_to_wait)

Read from I2C device.

备注

The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API before invoking this API.

备注

the LP I2C does not support 10-bit I2C device addresses.

备注

the LP I2C port number is ignored at the moment.

参数
  • lp_i2c_num -- LP I2C port number

  • device_addr -- I2C device address (7-bit)

  • data_rd -- Buffer to hold data to be read

  • size -- Size of data to be read in bytes

  • ticks_to_wait -- Operation timeout in CPU cycles. Set to -1 to wait forever.

返回

esp_err_t ESP_OK when successful

esp_err_t lp_core_i2c_master_write_to_device(i2c_port_t lp_i2c_num, uint16_t device_addr, const uint8_t *data_wr, size_t size, int32_t ticks_to_wait)

Write to I2C device.

备注

The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API before invoking this API.

备注

the LP I2C does not support 10-bit I2C device addresses.

备注

the LP I2C port number is ignored at the moment.

参数
  • lp_i2c_num -- LP I2C port number

  • device_addr -- I2C device address (7-bit)

  • data_wr -- Buffer which holds the data to be written

  • size -- Size of data to be written in bytes

  • ticks_to_wait -- Operation timeout in CPU cycles. Set to -1 to wait forever.

返回

esp_err_t ESP_OK when successful

esp_err_t lp_core_i2c_master_write_read_device(i2c_port_t lp_i2c_num, uint16_t device_addr, const uint8_t *data_wr, size_t write_size, uint8_t *data_rd, size_t read_size, int32_t ticks_to_wait)

Write to and then read from an I2C device in a single transaction.

备注

The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API before invoking this API.

备注

the LP I2C does not support 10-bit I2C device addresses.

备注

the LP I2C port number is ignored at the moment.

参数
  • lp_i2c_num -- LP I2C port number

  • device_addr -- I2C device address (7-bit)

  • data_wr -- Buffer which holds the data to be written

  • write_size -- Size of data to be written in bytes

  • data_rd -- Buffer to hold data to be read

  • read_size -- Size of data to be read in bytes

  • ticks_to_wait -- Operation timeout in CPU cycles. Set to -1 to wait forever.

返回

esp_err_t ESP_OK when successful

void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en)

Enable or disable ACK checking by the LP_I2C controller during write operations.

When ACK checking is enabled, the hardware will check the ACK/NACK level received during write operations against the expected ACK/NACK level. If the received ACK/NACK level does not match the expected ACK/NACK level then the hardware will generate the I2C_NACK_INT and a STOP condition will be generated to stop the data transfer.

备注

ACK checking is enabled by default

备注

the LP I2C port number is ignored at the moment.

参数
  • lp_i2c_num -- LP I2C port number

  • ack_check_en -- true: enable ACK check false: disable ACK check