十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ESP-IDF ULP RISC-V 协处理器编程指南:用 C 语言构建深度睡眠下的低功耗传感器任务

ESP-IDF ULP RISC-V 协处理器编程指南:用 C 语言构建深度睡眠下的低功耗传感器任务 ESP-IDF ULP RISC-V 协处理器编程指南用 C 语言构建深度睡眠下的低功耗传感器任务【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idfESP-IDF 的 ULP RISC-V 协处理器是 ULP 家族中可直接使用标准 GNU 工具链、以 C 语言编程的变体它让开发者能够在主 CPU 进入低功耗模式如深度睡眠时依然以极低功耗周期性地完成 ADC 采样、GPIO 监听、RTC I2C 读写等任务并在条件满足时唤醒主 CPU。本文以 ESP-IDF 官方文档docs/en/api-reference/system/ulp-risc-v.rst为核心骨架结合仓库中components/ulp的源码实现与examples/system/ulp/ulp_riscv下的可运行示例系统讲解 ULP RISC-V 的工具链安装、构建集成、变量共享、互斥锁、启动流程、RTC 外设I2C、中断处理与调试方法读完即可在自有工程中落地一套深度睡眠 ULP 周期采样 条件唤醒的低功耗方案。ULP RISC-V 协处理器是什么ULP RISC-V 协处理器是 ESP32-S2 / ESP32-S3 等芯片中 ULPUltra Low Power超低功耗协处理器的一种形态。与传统的 ULP FSM有限状态机用汇编编写相比ULP RISC-V 最大的区别在于可以用 C 语言编程使用标准 GNU 工具链基于 GCC 的 RISC-V 交叉编译器汇编.S文件同样受支持可访问RTC_SLOW_MEM内存区域以及RTC_CNTL、RTC_IO、SARADC外设的寄存器处理器为32 位定点机指令集基于RV32IMC——包含硬件乘法、硬件除法以及压缩指令compressed code不支持浮点指令。从仓库的 Kconfig 定义components/ulp/Kconfig可以看出ULP_COPROC_TYPE_RISCV选项依赖SOC_RISCV_COPROC_SUPPORTED并默认在ESP32S2 || ESP32S3目标上启用当未选择 FSM 时。这意味着 ULP RISC-V 是这两款芯片上主推的 ULP 形态。ULP RISC-V 与 ULP FSM 的核心差异可以这样理解FSM 使用专用的汇编指令集、编程门槛高而 RISC-V 形态则把协处理器变成一个迷你 CPU复用 RISC-V 生态的编译器、链接器与调试工具链代码可维护性大幅提升同时保留主 CPU 睡眠、协处理器工作的低功耗特性。安装 ULP RISC-V 工具链ULP RISC-V 代码使用基于 GCC 的 RISC-V 工具链编译。如果你已经按照 ESP-IDF 的 Getting Started Guide 配置好了带 CMake 构建系统的 ESP-IDF 环境那么工具链应该已经随环境安装完毕无需额外操作。需要注意的历史差异在较早版本的 ESP-IDF 中RISC-V 工具链前缀是riscv-none-embed-gcc当前版本统一为riscv32-esp-elf-例如符号导出阶段使用的riscv32-esp-elf-nm见下文构建流程第 5 步。若你参考的旧教程仍在使用旧前缀请按新前缀替换。编译 ULP RISC-V 代码两种构建集成方式ULP RISC-V 代码会随 ESP-IDF 工程一起编译成一个独立的二进制并自动嵌入到主工程二进制中。实现这一点有两种方式。方式一使用ulp_embed_binary这是最常用的方式步骤为将用 C 或汇编.S扩展名编写的 ULP RISC-V 源码放在组件目录下的专用子目录中例如ulp/在组件的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} TYPE riscv)参数说明参数含义第 1 个参数${ulp_app_name}ULP 二进制名称。该名称同时用于其他生成产物ELF 文件、map 文件、头文件.h和链接器导出文件.ld第 2 个参数${ulp_sources}ULP 源码文件列表第 3 个参数${ulp_exp_dep_srcs}包含了待生成头文件的组件源码文件列表。该列表用于正确构建依赖关系确保生成的头文件先于这些文件被编译第 4 个参数TYPE可选参数。当 menuconfig 的ULP Coprocessor types菜单中同时选中了CONFIG_ULP_COPROC_TYPE_FSM和CONFIG_ULP_COPROC_TYPE_RISCV时必须显式传入TYPE riscv以使用 RISC-V 工具链编译ULP 代码中的变量在生成的头文件中默认会带有ulp_前缀。如果你需要嵌入多个 ULP 程序可以添加自定义前缀以避免变量名冲突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} PREFIX ULP::)PREFIX参数既可以是 C 风格前缀如ulp2_也可以是 C 风格前缀如ULP::。仓库中的真实示例可以参考 examples/system/ulp/ulp_riscv/gpio/main/CMakeLists.txt它演示了把ulp/main.c作为 ULP 源码、ulp_riscv_example_main.c作为依赖生成头文件的组件源码并调用ulp_embed_binary的完整写法set(COMPONENT_SRCS ulp_riscv_example_main.c) set(COMPONENT_REQUIRES soc nvs_flash ulp esp_driver_gpio) register_component() set(ulp_app_name ulp_${COMPONENT_NAME}) set(ulp_riscv_sources ulp/main.c) set(ulp_exp_dep_srcs ulp_riscv_example_main.c) ulp_embed_binary(${ulp_app_name} ${ulp_riscv_sources} ${ulp_exp_dep_srcs})方式二自定义 CMake 工程ulp_embed_binary封装了大部分细节但如果你想对构建过程有更多控制——比如自定义编译选项、链接外部库——可以为 ULP RISC-V 创建独立的 CMake 工程。在组件CMakeLists.txt中把 ULP 工程作为外部工程引入ulp_add_project(ULP_APP_NAME ${CMAKE_SOURCE_DIR}/PATH_TO_DIR_WITH_ULP_PROJECT_FILE/)然后在上述路径对应的目录中创建 ULP 工程文件与CMakeLists.txt其模板如下cmake_minimum_required(VERSION 3.22) # Project/target name is passed from the main project to allow IDF to have a dependency on this target # as well as embed the binary into the main app project(${ULP_APP_NAME}) add_executable(${ULP_APP_NAME} main.c) # Import the ULP project helper functions include(IDFULPProject) # Apply default compile options ulp_apply_default_options(${ULP_APP_NAME}) # Apply default sources provided by the IDF ULP component ulp_apply_default_sources(${ULP_APP_NAME}) # Add targets for building the binary, as well as the linkerscript which exports ULP shared variables to the main app ulp_add_build_binary_targets(${ULP_APP_NAME}) # Everything below this line is optional and can be used to customize the build process # Create a custom library set(lib_path ${CMAKE_CURRENT_LIST_DIR}/lib) add_library(custom_lib STATIC ${lib_path}/lib_src.c) target_include_directories(custom_lib PUBLIC ${lib_path}/) # Link the library target_link_libraries(${ULP_APP_NAME} PRIVATE custom_lib) # Set custom compile flags target_compile_options(${ULP_APP_NAME} PRIVATE -msave-restore)其中IDFULPProject等辅助函数的实现位于 components/ulp/cmake/IDFULPProject.cmake 与 components/ulp/cmake/IDFULPProjectCommon.cmakeulp_add_build_binary_targets负责生成二进制构建目标以及把 ULP 共享变量导出给主应用的链接脚本。构建工程与配置项编译项目前需要完成以下配置在 menuconfig 中启用CONFIG_ULP_COPROC_ENABLED并在ULP Coprocessor types菜单中选择CONFIG_ULP_COPROC_TYPE_RISCV设置CONFIG_ULP_COPROC_RESERVE_MEM即 RTC slow memory reserved for coprocessor 选项为足够存放 ULP RISC-V 代码和数据的大小。如果应用组件包含多个 ULP 程序RTC 内存大小必须足以容纳其中最大的一个。从 components/ulp/Kconfig 的源码可以看到这些配置的默认值与取值范围配置项默认值取值范围 / 说明ULP_COPROC_ENABLEDn总开关启用后才会出现后续 ULP 配置菜单ULP_COPROC_TYPE_FSMESP32 默认y依赖SOC_ULP_FSM_SUPPORTEDULP_COPROC_TYPE_RISCVESP32S2/S3 且未选 FSM 时默认y依赖SOC_RISCV_COPROC_SUPPORTEDULP_COPROC_RESERVE_MEMESP32 为 512其他目标为 4096ESP32/S2/S3 范围 32~8176 字节ULP_RISCV_INTERRUPT_ENABLEn启用 ULP RISC-V 核上的中断ULP_RISCV_UART_BAUDRATE9600位拆bit-bangedUART 驱动波特率因精度限制不建议超过 19200ULP_RISCV_I2C_RW_TIMEOUT500ULP RISC-V I2C 读写超时tick 数设为 -1 表示永远等待注意 ULP 协处理器的 tick 速率与主核 OS tick 不同配置完成后按常规方式构建即可例如执行idf.py app。ULP 程序构建的 7 个内部步骤在构建过程中ESP-IDF 会依次执行以下步骤生成并嵌入 ULP 程序编译与汇编每个源文件每个源文件经 C 编译器和汇编器处理后在组件构建目录中生成.obj.c或.obj.S目标文件用 C 预处理器处理链接脚本模板模板位于components/ulp/ld目录例如 components/ulp/ld/ulp_riscv.ld.in链接目标文件为输出 ELF 文件ulp_app_name.elf此阶段生成的 map 文件ulp_app_name.map对调试很有用把 ELF 文件内容导出为二进制ulp_app_name.bin该二进制随后可嵌入到应用中用riscv32-esp-elf-nm生成 ELF 文件中的全局符号列表ulp_app_name.sym用esp32ulp_mapgen.py工具生成 LD 导出脚本和头文件ulp_app_name.ld与ulp_app_name.h其中包含ulp_app_name.sym中的符号该工具位于 components/ulp/esp32ulp_mapgen.py把生成的二进制加入待嵌入应用的二进制文件列表。从链接脚本 components/ulp/ld/ulp_riscv.ld.in 可以看到 ULP RISC-V 的内存布局入口为reset_vectorRAM 起始地址为 0长度即CONFIG_ULP_COPROC_RESERVE_MEM依次排布.text其中ulp_riscv_vectors.S的.text.vectors必须链接到偏移 0x0 处作为默认复位向量、.rodata、.data/.sdata、.bss/.sbss栈顶__stack_top位于 RAM 末端。这正是二进制加载地址为 0、与 RTC_SLOW_MEM 起始地址对齐这一设计的来源。访问 ULP RISC-V 程序变量ULP RISC-V 程序中定义的全局符号可以在主程序中使用。例如ULP 程序定义一个变量measurement_count用于记录程序在唤醒芯片退出深度睡眠前需要完成的 ADC 测量次数volatile int measurement_count; int some_function() { //read the measurement count for later use. int temp measurement_count; ...do something. }主程序之所以能访问这些全局变量是因为构建系统生成了${ULP_APP_NAME}.h和${ULP_APP_NAME}.ld两个文件其中定义了 ULP RISC-V 程序中的全部全局符号每个符号都带有ulp_前缀。头文件包含符号的声明extern uint32_t ulp_measurement_count;注意所有符号变量、函数都被声明为uint32_t数组被声明为uint32_t [SIZE]。对于函数需要取符号地址并转换为合适的函数指针类型。要在主程序中访问这些变量只需包含生成的头文件然后像普通变量一样使用#include ulp_app_name.h void init_ulp_vars() { ulp_measurement_count 64; }这里有两个重要的注意事项来自官方文档的原始提示ULP RISC-V 程序全局作用域中声明的变量位于二进制的.bss或.data段这些段是在ULP RISC-V 二进制被加载并执行时才完成初始化的。在主 CPU 上、ULP RISC-V首次运行之前访问这些变量可能产生未定义行为ulp_前缀是默认值。如果嵌入多个 ULP 程序可以用ulp_embed_binary的PREFIX参数指定不同前缀以避免命名冲突。这一点在源码层面也有印证ulp_riscv_load_binarycomponents/ulp/ulp_riscv/ulp_riscv.c先将CONFIG_ULP_COPROC_RESERVE_MEM大小的 RTC_SLOW_MEM 区域清零同时完成 bss 初始化再把程序二进制拷入——也就是说.bss/.data 的初始化发生在加载二进制这一刻而非编译期。主程序与 ULP 之间的互斥锁如果主程序与 ULP 需要互斥地访问某个共享变量可以使用 ULP RISC-V 锁 APIulp_riscv_lock_acquireulp_riscv_lock_releaseULP 没有任何硬件指令支持互斥因此锁 API 通过软件算法实现——Peterson 算法。从源码 components/ulp/ulp_riscv/ulp_riscv_lock.c 可以看到主 CPU 侧的实现先置位自己的critical_section_flag_main_cpu把turn交给 ULP然后自旋等待 ULP 释放critical_section_flag_ulp为 false 或 turn 回到主 CPU 时退出释放时清除自己的标志位。ULP 侧对应的实现在 components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c两侧共享的锁结构定义在 components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h。需要特别强调的是这些锁只应被主程序中的单一线程调用。如果多个线程同时使用锁无法提供互斥保证。此外锁结构本身需要放在 ULP 与主 CPU 都能访问的 RTC 内存中即 ULP 程序导出的共享变量。启动 ULP RISC-V 程序要运行 ULP RISC-V 程序主应用需要先把 ULP 程序加载到 RTC 内存再启动它使用ulp_riscv_load_binary加载程序使用ulp_riscv_run启动程序。每个 ULP RISC-V 程序都以二进制 blob 的形式嵌入 ESP-IDF 应用中。应用可以通过以下方式引用该 blob 并加载假设ULP_APP_NAME定义为ulp_app_nameextern 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_riscv_load_binary( bin_start, (bin_end - bin_start)) ); }程序加载进 RTC 内存后调用ulp_riscv_run启动ESP_ERROR_CHECK( ulp_riscv_run() );从源码实现components/ulp/ulp_riscv/ulp_riscv.c看ulp_riscv_run()实际使用默认配置ULP_RISCV_DEFAULT_CONFIG()唤醒源为定时器调用ulp_riscv_config_and_run()。该函数会完成协处理器的复位与时钟使能、选择 RISC-V 作为 ULP 定时器触发目标、清除虚假唤醒中断等初始化ESP32-S3 上还包含对 IDF-4510 挂起问题的时序处理最后根据配置的唤醒源ULP_RISCV_WAKEUP_SOURCE_TIMER定时器或ULP_RISCV_WAKEUP_SOURCE_GPIO设置对应的寄存器位。ulp_riscv_load_binary则会校验程序大小不超过CONFIG_ULP_COPROC_RESERVE_MEM超限返回ESP_ERR_INVALID_SIZE然后把二进制拷贝到RTC_SLOW_MEM起始处。另外文档要求在使用 ULP RISC-V 前必须在 menuconfig 中启用CONFIG_ULP_COPROC_ENABLED和CONFIG_ULP_COPROC_TYPE_RISCV并把 RTC slow memory reserved for coprocessorCONFIG_ULP_COPROC_RESERVE_MEM设置为足够容纳 ULP RISC-V 代码和数据的大小多 ULP 程序时需容纳最大者。ULP RISC-V 程序运行流程定时器驱动的周期执行ULP RISC-V 协处理器由定时器启动。定时器在调用ulp_riscv_run时启动它计数RTC_SLOW_CLK的 tick 数默认由内部 RC 振荡器产生tick 数通过RTC_CNTL_ULP_CP_TIMER_1_REG寄存器设置。不同目标芯片的 RTC_SLOW_CLK 频率不同目标芯片RTC_SLOW_CLK 默认频率默认150 kHzESP32-S290 kHzESP32-S3136 kHz应用可以使用ulp_set_wakeup_period函数设置 ULP 定时器周期值即RTC_CNTL_ULP_CP_TIMER_1_REG。整个运行周期的流程如下定时器计数达到RTC_CNTL_ULP_CP_TIMER_1_REG中设置的 tick 数ULP RISC-V 协处理器上电从ulp_riscv_run调用时设置的入口点开始运行程序程序持续运行直到RTC_CNTL_COCPU_CTRL_REG寄存器中的RTC_CNTL_COCPU_DONE字段被写入或发生非法处理器状态导致的 trap程序停止后ULP RISC-V 协处理器下电定时器再次启动进入下一个周期。若要禁用定时器即阻止 ULP 程序再次运行需要清除RTC_CNTL_ULP_CP_TIMER_REG寄存器中的RTC_CNTL_ULP_CP_SLP_TIMER_EN位。这个操作既可以在 ULP 代码中完成也可以在主程序中完成。与之配套的主 CPU 侧控制 API 包括ulp_riscv_timer_stop()停止定时器但不会中止正在执行的程序、ulp_riscv_timer_resume()恢复已配置的定时器、ulp_riscv_halt()停止定时器并置位 DONE、复位处理器、ulp_riscv_reset()从主 CPU 侧复位 ULP 核用于 ULP 因固件损坏等原因处于异常状态时重新初始化。这些函数的实现均位于 components/ulp/ulp_riscv/ulp_riscv.c。ULP RISC-V 外设支持RTC I2C为增强 ULP RISC-V 协处理器的能力它可以访问工作在低功耗RTC域的外设。当主 CPU 处于睡眠模式时ULP RISC-V 仍可与这些外设交互并在满足唤醒条件时唤醒主 CPU。RTC I2C 控制器在 RTC 域中提供 I2C 主机功能。ULP RISC-V 协处理器可以通过该控制器读写 I2C 从设备。使用方法在主核上运行的应用中在初始化 ULP RISC-V 核并进入睡眠前调用ulp_riscv_i2c_master_init初始化 RTC I2CRTC I2C 控制器初始化后在进行任何读写操作前必须通过ulp_riscv_i2c_master_set_slave_addr编程设置 I2C 从设备地址。RTC I2C 外设会发起两种 I2C 事务READ[start] → 写设备地址 → 写设备子寄存器地址 → [重复 start] → 写设备地址 → 读 N 字节 → [stop]WRITE[start] → 写设备地址 → 写设备子寄存器地址 → [重复 start] → 写设备地址 → 写 N 字节 → [stop]两种情况下发送子寄存器地址都是必需的且无法禁用。因此外设始终期望先用ulp_riscv_i2c_master_set_slave_reg_addr设置从设备的子寄存器地址如果未显式设置外设将使用SENS_SAR_I2C_CTRL_REG[18:11]的值作为后续事务的子寄存器地址。这一行为使 RTC I2C 外设不兼容以下设备在发起读/写事务前不希望收到子寄存器地址写入的设备需要 16 位或更宽寄存器地址的设备仅支持 8 位寻址。此外还有一个硬件层面的限制主 CPU 与 ULP RISC-V 核之间访问 RTC I2C 外设没有硬件原子性保护因此必须小心避免两个核同时访问该外设。RTC I2C 配置参数与默认值从 components/ulp/ulp_riscv/include/ulp_riscv_i2c.h 可以看到完整的配置结构体ulp_riscv_i2c_pin_cfg_t引脚配置sda_io_num、scl_io_num、sda_pullup_en、scl_pullup_enulp_riscv_i2c_timing_cfg_t时序配置单位微秒scl_low_periodSCL 低电平周期、scl_high_periodSCL 高电平周期、sda_duty_periodSDA 切换与 SCL 下降沿之间的周期、scl_start_periodSTART 条件后的等待时间、scl_stop_periodEND 条件前的等待时间、i2c_trans_timeoutI2C 事务超时。头文件提供的预置配置宏配置宏适用模式关键时序参数µsULP_RISCV_I2C_FAST_MODE_CONFIG()ESP32-S3Fast mode最大 SCL 400 kHzscl_low1.4, scl_high0.3, sda_duty1, start2, stop1.3, timeout20ULP_RISCV_I2C_FAST_MODE_CONFIG()ESP32-S2Fast mode受时序约束 SCL 最大约 233 kHzscl_low2, scl_high0.7, sda_duty1.7, start2.4, stop1.3, timeout20ULP_RISCV_I2C_STANDARD_MODE_CONFIG()Standard mode最大 SCL 100 kHzscl_low5, scl_high5, sda_duty2, start3, stop6, timeout20ULP_RISCV_I2C_DEFAULT_GPIO_CONFIG()默认引脚SDAGPIO_NUM_3SCLGPIO_NUM_2均使能内部上拉ULP_RISCV_I2C_DEFAULT_CONFIG()默认组合默认引脚 Fast modeRTC I2C 调试检查清单如果基于 RTC I2C 的 ULP RISC-V 程序运行不符合预期可以按以下清单排查SDA/SCL 引脚选择错误SDA 引脚只能是 GPIO1 或 GPIO3SCL 引脚只能是 GPIO0 或 GPIO2。请确认引脚配置正确I2C 时序参数错误RTC I2C 总线时序配置受标准 I2C 总线规范约束任何违反标准 I2C 总线规范的时序参数都会导致错误从设备不兼容如果 I2C 从设备或传感器不需要编程子寄存器地址它可能不兼容 RTC I2C 外设参见上文说明主 CPU 上运行时 RTC 驱动报Write Failed!或Read Failed!需确认I2C 从设备在 Espressif SoC 的标准 I2C 主机上工作正常排除从设备自身问题若 RTC I2C 中断状态日志报告TIMEOUT或ACK错误通常意味着 I2C 设备未响应 RTC I2C 控制器发出的START条件——可能是从设备未正确连接到控制器引脚或从设备处于异常状态若 RTC I2C 中断日志未报告任何错误状态则可能意味着驱动接收从设备数据的速度不够快。原因是 RTC I2C 控制器没有 TX/RX FIFO 来缓存多字节数据而是依赖中断状态轮询机制进行单字节传输。可通过让外设 SCL 时钟尽可能快运行来缓解即调整初始化配置参数中的 SCL 低电平周期与高电平周期值。另一个通用调试方法是只在主 CPU 上运行 RTC I2C 控制器不加载任何 ULP RISC-V 代码、不激活任何睡眠模式来验证其基本功能。这是 RTC I2C 外设必须工作的基础配置这样可以排除 ULP 或睡眠模式引入的问题。ULP RISC-V 中断处理ULP RISC-V 核支持来自特定内部与外部事件的中断处理。官方文档给出的中断源如下中断源类型IRQ内部定时器中断Internal0EBREAK 或 ECALL 或非法指令Internal1非对齐内存访问Internal2RTC 外设中断源External31中断处理通过特殊的 32 位寄存器Q0-Q3以及自定义 R 型指令启用。更多细节参见芯片技术参考手册Technical Reference Manual中ULP Coprocessor ULP-RISC-V ULP-RISC-V Interrupts章节。中断在启动时全局使能。当中断发生时处理器跳转到 IRQ 向量IRQ 向量负责保存寄存器上下文然后调用全局中断分发器。ULP RISC-V 驱动实现了一个弱weak中断分发器_ulp_riscv_interrupt_handler作为处理所有中断的中心点该全局分发器会依次调用通过ulp_riscv_intr_alloc分配的各中断处理函数。当前限制如下ULP RISC-V 的中断处理尚未完全实现内部中断源暂不支持目前支持两个 RTC 外设中断源软件触发中断和RTC IO 触发中断不支持嵌套中断如果需要自定义中断处理可以通过定义自己的_ulp_riscv_interrupt_handler覆盖默认的全局中断分发器。配套的源码包括 components/ulp/ulp_riscv/ulp_core/ulp_riscv_interrupt.c、向量表 components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S 以及头文件 components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt.h。此外主 CPU 侧还可以通过ulp_riscv_isr_register/ulp_riscv_isr_deregistercomponents/ulp/ulp_riscv/ulp_riscv.c注册 RTC 域的 ULP 中断信号软件中断ULP_RISCV_SW_INT与 trap 中断ULP_RISCV_TRAP_INT到主 CPU 的 ISR注意该 ISR 只在主 CPU 未进入深度睡眠时有效。调试 ULP RISC-V 程序由于 ULP RISC-V 核非常简单许多标准调试手段如 JTAG、printf都不可用。官方文档推荐以下几种调试途径通过共享变量传递程序状态如访问变量一节所述主 CPU 与 ULP 核都能轻松访问 RTC 内存中的全局变量。ULP 把状态信息写入此类变量、主 CPU 读取即可了解 ULP 核上发生的情况。缺点是这种方法要求主 CPU 保持唤醒并非总是可行而且保持主 CPU 唤醒有时反而会掩盖问题——某些问题只在特定电源域断电时才会出现使用位拆bit-bangedUART 驱动打印ULP RISC-V 组件自带一个低速位拆 UART TX 驱动可独立于主 CPU 状态进行打印输出。参考示例 examples/system/ulp/ulp_riscv/uart_print其波特率可通过CONFIG_ULP_RISCV_UART_BAUDRATE配置默认 9600不建议超过 19200trap 信号ULP RISC-V 有硬件 trap会在特定条件下如非法指令触发使主 CPU 以唤醒原因ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG被唤醒。应用示例一览仓库examples/system/ulp/ulp_riscv目录下提供了丰富的可运行示例覆盖了 ULP RISC-V 的典型用法示例演示内容gpio编程 ULP RISC-V 协处理器监听 GPIO 引脚状态变化时唤醒主 CPUuart_print在开发板上位拆 UART TX 线即使主 CPU 处于深度睡眠也能直接从 ULP RISC-V 输出日志ds18b20_onewireESP32-S2通过 1-Wire 从 DS18B20 传感器读取温度温度超过设定阈值时从深度睡眠唤醒主 CPUi2c深度睡眠模式下用 RTC I2C 外设周期测量 BMP180 传感器的温度与气压超过阈值时唤醒主 CPUinterrupts注册并处理软件触发与 RTC IO 触发的中断统计软件中断次数达到阈值或按键按下时从深度睡眠唤醒主处理器adc周期测量输入电压电压超过设定阈值时从深度睡眠唤醒系统gpio_interrupt使用 GPIO0 作为输入信号通过 RTC IO 中断唤醒 ULP RISC-V并配置运行协处理器、让芯片进入深度睡眠直到唤醒源引脚被拉低touch周期扫描并读取触摸板传感器触摸板被激活时唤醒主 CPUulp_fsm_riscv_combined/counterESP32-S2/S3在同一应用中依次使用 ULP FSM 与 ULP RISC-V 协处理器FSM 从 0 数到 100RISC-V 协处理器从 100 数到 500期间 HP 核保持睡眠直到计数完成以 gpio 示例的 ULP 程序为例可以看到一个典型的 ULP 任务结构循环读取 GPIO0 电平检测到状态变化后调用ulp_riscv_wakeup_main_processor()唤醒主 CPU 并退出其中gpio_level_previous是导出的共享变量主 CPU 可以通过ulp_gpio_level_previous访问它。进一步阅读ULP RISC-V 全部 API 的头文件位于 components/ulp/ulp_riscv/include主 CPU 侧与 components/ulp/ulp_riscv/ulp_core/includeULP 核侧包括锁ulp_riscv_lock.h、RTC I2Culp_riscv_i2c.h、GPIOulp_riscv_gpio.h、ADCulp_riscv_adc.h、寄存器操作ulp_riscv_register_ops.h、工具函数ulp_riscv_utils.h等ULP RISC-V 测试套件位于 components/ulp/test_apps/ulp_riscv包含协处理器崩溃、I2C、双固件等场景的自动化测试pytest_ulp_riscv.py更多 ULP 相关资料可参考 components/ulp/CMakeLists.txt 与 components/ulp/project_include.cmake 中的构建集成实现。ULP RISC-V 将标准 C 语言开发与超低功耗常驻任务结合起来是 ESP32-S2/S3 上实现电池供电 IoT 传感器节点、唤醒式采集、低功耗外设轮询等场景的核心技术路径。结合本文的构建集成、变量共享、定时器流程与 RTC I2C 细节你可以直接基于仓库示例快速构建自己的深度睡眠低功耗应用。【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表