Merge branch 'feature/adds_main_ulp_page' into 'master'

feat(docs): Adds ULP main documentation page

Closes IDF-12901 and DOC-10873

See merge request espressif/esp-idf!38525
This commit is contained in:
Zhang Shu Xian
2025-04-27 10:26:43 +08:00
7 changed files with 453 additions and 399 deletions

View File

@@ -171,7 +171,7 @@ USB_SERIAL_JTAG_DOCS = [
]
ULP_FSM_DOCS = [
'api-reference/system/ulp.rst',
'api-reference/system/ulp-fsm.rst',
'api-reference/system/ulp_macros.rst',
'api-reference/system/ulp_instruction_set.rst',
]

View File

@@ -39,9 +39,7 @@ System API
system_time
:SOC_ASYNC_MEMCPY_SUPPORTED: async_memcpy
:esp32: himem
:SOC_ULP_FSM_SUPPORTED: ulp
:SOC_RISCV_COPROC_SUPPORTED: ulp-risc-v
:SOC_LP_CORE_SUPPORTED: ulp-lp-core
ulp
wdts

View File

@@ -0,0 +1,209 @@
ULP FSM Coprocessor Programming
===============================
:link_to_translation:`zh_CN:[中文]`
The Ultra Low Power (ULP) coprocessor is a simple finite state machine (FSM) which is designed to perform measurements using the ADC, temperature sensor, and external I2C sensors, while the main processors are in Deep-sleep mode. The ULP coprocessor can access the ``RTC_SLOW_MEM`` memory region, and registers in the ``RTC_CNTL``, ``RTC_IO``, and ``SARADC`` peripherals. The ULP coprocessor uses fixed-width 32-bit instructions, 32-bit memory addressing, and has 4 general-purpose 16-bit registers. This coprocessor is referred to as ``ULP FSM`` in ESP-IDF.
.. only:: esp32s2 or esp32s3
{IDF_TARGET_NAME} provides a second type of ULP coprocessor which is based on a RISC-V instruction set architecture. For details regarding ``ULP RISC-V``, please refer to :doc:`ULP-RISC-V Coprocessor <../../../api-reference/system/ulp-risc-v>`.
Installing the Toolchain
------------------------
The ULP FSM coprocessor code is written in assembly and compiled using the `binutils-esp32ulp toolchain`_.
If you have already set up ESP-IDF with CMake build system according to the :doc:`Getting Started Guide <../../../get-started/index>`, then the ULP FSM toolchain will already be installed.
Programming ULP FSM
-------------------
The ULP FSM can be programmed using the supported instruction set. Alternatively, the ULP FSM coprocessor can also be programmed using C Macros on the main CPU. These two methods are described in the following section:
.. toctree::
:maxdepth: 1
Instruction set reference for {IDF_TARGET_NAME} ULP <ulp_instruction_set>
Programming using macros (legacy) <ulp_macros>
Compiling the ULP Code
-----------------------
To compile the ULP FSM code as part of the component, the following steps must be taken:
1. The ULP FSM code, written in assembly, must be added to one or more files with ``.S`` extension. These files must be placed into a separate directory inside the component directory, for instance, ``ulp/``.
.. note::
When registering the component (via ``idf_component_register``), this directory should not be added to the ``SRC_DIRS`` argument. The logic behind this is that the ESP-IDF build system will compile files found in ``SRC_DIRS`` based on their extensions. For ``.S`` files, ``{IDF_TARGET_TOOLCHAIN_PREFIX}-as`` assembler is used. This is not desirable for ULP FSM assembly files, so the easiest way to achieve the distinction is by placing ULP FSM assembly files into a separate directory. The ULP FSM assembly source files should also **not** be added to ``SRCS`` for the same reason. See the steps below for how to properly add ULP FSM assembly source files.
2. Call ``ulp_embed_binary`` from the component CMakeLists.txt after registration. For example::
...
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}")
The first argument to ``ulp_embed_binary`` specifies the ULP FSM binary name. The name specified here will also be used by other generated artifacts such as the ELF file, map file, header file and linker export file. The second argument specifies the ULP FSM assembly source files. Finally, the third argument specifies the list of component source files which include the header file to be generated. This list is needed to build the dependencies correctly and ensure that the generated header file will be created before any of these files are compiled. See the section below for the concept of generated header files for ULP applications.
Variables in the ULP code will be prefixed with ``ulp_`` (default value) in this generated header file.
If you need to embed multiple ULP programs, you may add a custom prefix in order to avoid conflicting variable names like this:
.. code-block:: cmake
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::")
The additional PREFIX argument can be a C style prefix (like ``ulp2_``) or a C++ style prefix (like ``ULP::``).
3. Build the application as usual (e.g., ``idf.py app``).
Inside, the build system takes the following steps to build an ULP FSM program:
1. **Run each assembly file (foo.S) through the C preprocessor.** This step generates the preprocessed assembly files (foo.ulp.S) in the component build directory. This step also generates dependency files (foo.ulp.d).
2. **Run preprocessed assembly sources through the assembler.** This produces object (foo.ulp.o) and listing (foo.ulp.lst) files. Listing files are generated for debugging purposes and are not used at later stages of the build process.
3. **Run the linker script template through the C preprocessor.** The template is located in ``components/ulp/ld`` directory.
4. **Link the object files into an output ELF file** (``ulp_app_name.elf``). The Map file (``ulp_app_name.map``) generated at this stage may be useful for debugging purposes.
5. **Dump the contents of the ELF file into a binary** (``ulp_app_name.bin``) which can then be embedded into the application.
6. **Generate a list of global symbols** (``ulp_app_name.sym``) in the ELF file using ``esp32ulp-elf-nm``.
7. **Create an LD export script and a header file** (``ulp_app_name.ld`` and ``ulp_app_name.h``) containing the symbols from ``ulp_app_name.sym``. This is done using the ``esp32ulp_mapgen.py`` utility.
8. **Add the generated binary to the list of binary files** to be embedded into the application.
Accessing the ULP FSM Program Variables
---------------------------------------
Global symbols defined in the ULP FSM program may be used inside the main program.
For example, the ULP FSM program may define a variable ``measurement_count`` which will define the number of ADC measurements the program needs to make before waking up the chip from Deep-sleep::
.global measurement_count
measurement_count: .long 0
// later, use measurement_count
move r3, measurement_count
ld r3, r3, 0
The main program needs to initialize this variable before the ULP program is started. The build system makes this possible by generating the ``${ULP_APP_NAME}.h`` and ``${ULP_APP_NAME}.ld`` files which define the global symbols present in the ULP program. Each global symbol defined in the ULP program is included in these files and are prefixed with ``ulp_``.
The header file contains the declaration of the symbol::
extern uint32_t ulp_measurement_count;
Note that all symbols (variables, arrays, functions) are declared as ``uint32_t``. For functions and arrays, take the address of the symbol and cast it to the appropriate type.
The generated linker script file defines the locations of symbols in RTC_SLOW_MEM::
PROVIDE ( ulp_measurement_count = 0x50000060 );
To access the ULP program variables from the main program, the generated header file should be included using an ``include`` statement. This will allow the ULP program variables to be accessed as regular variables::
#include "ulp_app_name.h"
// later
void init_ulp_vars() {
ulp_measurement_count = 64;
}
.. only:: esp32
Note that the ULP FSM program can only use the lower 16 bits of each 32-bit word in RTC memory, because the registers are 16-bit, and there is no instruction to load from the high part of the word. Likewise, the ULP store instruction writes register values into the lower 16 bits of the 32-bit word in RTC memory. The upper 16 bits are written with a value which depends on the address of the store instruction, thus when reading variables written by the ULP coprocessor, the main application needs to mask the upper 16 bits, for example:
::
printf("Last measurement value: %d\n", ulp_last_measurement & UINT16_MAX);
Starting the ULP FSM Program
----------------------------
To run a ULP FSM program, the main application needs to load the ULP program into RTC memory using the :cpp:func:`ulp_load_binary` function, and then start it using the :cpp:func:`ulp_run` function.
Note that the ``Enable Ultra Low Power (ULP) Coprocessor`` option must be enabled in menuconfig to work with ULP. To select the type of ULP to be used, the ``ULP Co-processor type`` option must be set. To reserve memory for the ULP, the ``RTC slow memory reserved for coprocessor`` option must be set to a value big enough to store ULP code and data. If the application components contain multiple ULP programs, then the size of the RTC memory must be sufficient to hold the largest one.
Each ULP program is embedded into the ESP-IDF application as a binary blob. The application can reference this blob and load it in the following way (suppose ULP_APP_NAME was defined to ``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 // load address, set to 0 when using default linker scripts
bin_start,
(bin_end - bin_start) / sizeof(uint32_t)) );
}
Once the program is loaded into RTC memory, the application can start it by passing the address of the entry point to the ``ulp_run`` function::
ESP_ERROR_CHECK( ulp_run(&ulp_entry - RTC_SLOW_MEM) );
Declaration of the entry point symbol comes from the generated header file mentioned above, ``${ULP_APP_NAME}.h``. In the assembly source of the ULP FSM application, this symbol must be marked as ``.global``::
.global entry
entry:
// code starts here
.. only:: esp32
ESP32 ULP Program Flow
-----------------------
ESP32 ULP coprocessor is started by a timer. The timer is started once :cpp:func:`ulp_run` is called. The timer counts the number of RTC_SLOW_CLK ticks (by default, produced by an internal 150 kHz RC oscillator). The number of ticks is set using ``SENS_ULP_CP_SLEEP_CYCx_REG`` registers (x = 0..4). When starting the ULP for the first time, ``SENS_ULP_CP_SLEEP_CYC0_REG`` will be used to set the number of timer ticks. Later the ULP program can select another ``SENS_ULP_CP_SLEEP_CYCx_REG`` register using ``sleep`` instruction.
The application can set ULP timer period values (SENS_ULP_CP_SLEEP_CYCx_REG, x = 0..4) using ``ulp_set_wakeup_period`` function.
Once the timer counts the number of ticks set in the selected ``SENS_ULP_CP_SLEEP_CYCx_REG`` register, ULP coprocessor powers up and starts running the program from the entry point set in the call to :cpp:func:`ulp_run`.
The program runs until it encounters a ``halt`` instruction or an illegal instruction. Once the program halts the ULP coprocessor powers down and the timer is started again.
To disable the timer (effectively preventing the ULP program from running again), clear the ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` bit in the ``RTC_CNTL_STATE0_REG`` register. This can be done both from ULP code and from the main program.
.. only:: esp32s2 or esp32s3
{IDF_TARGET_NAME} ULP Program Flow
----------------------------------
{IDF_TARGET_NAME} ULP coprocessor is started by a timer. The timer is started once :cpp:func:`ulp_run` is called. The timer counts a number of RTC_SLOW_CLK ticks (by default, produced by an internal 90 kHz RC oscillator). The number of ticks is set using ``RTC_CNTL_ULP_CP_TIMER_1_REG`` register.
The application can set ULP timer period values by :cpp:func:`ulp_set_wakeup_period` function.
Once the timer counts the number of ticks set in the selected ``RTC_CNTL_ULP_CP_TIMER_1_REG`` register, ULP coprocessor powers up and starts running the program from the entry point set in the call to :cpp:func:`ulp_run`.
The program runs until it encounters a ``halt`` instruction or an illegal instruction. Once the program halts, ULP coprocessor powers down, and the timer is started again.
To disable the timer (effectively preventing the ULP program from running again), clear the ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` bit in the ``RTC_CNTL_ULP_CP_TIMER_REG`` register. This can be done both from ULP code and from the main program.
Application Examples
--------------------
* :example:`system/ulp/ulp_fsm/ulp` demonstrates how to program the ULP FSM coprocessor to count pulses on an IO while the main CPUs are running other code or are in deep sleep, with the pulse count saved into NVS upon wakeup.
.. only:: esp32 or esp32s3
* :example:`system/ulp/ulp_fsm/ulp_adc` demonstrates how to use the ULP FSM coprocessor to periodically measure input voltage on a specific ADC channel during deep sleep, compare it to the set threshold, and wake up the system if the voltage is outside the threshold.
API Reference
-------------
.. include-build-file:: inc/ulp_fsm_common.inc
.. include-build-file:: inc/ulp_common.inc
.. _binutils-esp32ulp toolchain: https://github.com/espressif/binutils-gdb

View File

@@ -1,207 +1,27 @@
ULP Coprocessor Programming
=============================
Ultra Low Power (ULP) coprocessor
===================================
:link_to_translation:`zh_CN:[中文]`
The Ultra Low Power (ULP) coprocessor is a simple finite state machine (FSM) which is designed to perform measurements using the ADC, temperature sensor, and external I2C sensors, while the main processors are in Deep-sleep mode. The ULP coprocessor can access the ``RTC_SLOW_MEM`` memory region, and registers in the ``RTC_CNTL``, ``RTC_IO``, and ``SARADC`` peripherals. The ULP coprocessor uses fixed-width 32-bit instructions, 32-bit memory addressing, and has 4 general-purpose 16-bit registers. This coprocessor is referred to as ``ULP FSM`` in ESP-IDF.
The Ultra Low Power (ULP) coprocessor is designed to perform tasks while the main CPU is in deep sleep mode, allowing for power savings and extended battery life in applications. The ULP coprocessor can monitor sensors, control peripherals, and handle other tasks that do not require the main CPU's computing power. It can also wake up the main CPU when specific conditions are met, such as when a sensor reading exceeds a predefined threshold.
.. only:: esp32s2 or esp32s3
.. only:: not SOC_ULP_SUPPORTED
{IDF_TARGET_NAME} provides a second type of ULP coprocessor which is based on a RISC-V instruction set architecture. For details regarding `ULP RISC-V` refer :doc:`ULP-RISC-V Coprocessor <../../../api-reference/system/ulp-risc-v>`.
.. note:: The ULP coprocessor is not supported on {IDF_TARGET_NAME}.
Installing the Toolchain
------------------------
.. only:: SOC_ULP_SUPPORTED
The ULP FSM coprocessor code is written in assembly and compiled using the `binutils-esp32ulp toolchain`_.
The ULP coprocessor on a chip may include one or more of the following types, but only one can operate at a time:
If you have already set up ESP-IDF with CMake build system according to the :doc:`Getting Started Guide <../../../get-started/index>`, then the ULP FSM toolchain will already be installed.
- **ULP FSM**: A finite state machine (FSM) based ULP coprocessor. It is suitable for simple tasks, as applications for this type must be written using assembly language or C macros. This type is supported by ESP32, ESP32-S2, and ESP32-S3.
- **ULP RISC-V**: A RISC-V based ULP coprocessor. It offers enhanced computational capabilities and flexibility, making it suitable for more complex applications written in C. This type is supported by ESP32-S2 and ESP32-S3.
- **ULP LP Core**: A RISC-V based ULP coprocessor that combines the advantages of the **ULP RISC-V** type with additional features, such as extended memory access, broader peripheral access, a debug module, and an interrupt controller. This coprocessor is capable of operating even when the entire system is active. This type is supported by ESP32-C5, ESP32-C6, ESP32-P4, and upcoming chips.
Programming ULP FSM
-------------------
.. toctree::
:maxdepth: 1
The ULP FSM can be programmed using the supported instruction set. Alternatively, the ULP FSM coprocessor can also be programmed using C Macros on the main CPU. These two methods are described in the following section:
:SOC_ULP_FSM_SUPPORTED: ulp-fsm
:SOC_RISCV_COPROC_SUPPORTED: ulp-risc-v
:SOC_LP_CORE_SUPPORTED: ulp-lp-core
.. toctree::
:maxdepth: 1
Instruction set reference for {IDF_TARGET_NAME} ULP <ulp_instruction_set>
Programming using macros (legacy) <ulp_macros>
Compiling the ULP Code
-----------------------
To compile the ULP FSM code as part of the component, the following steps must be taken:
1. The ULP FSM code, written in assembly, must be added to one or more files with ``.S`` extension. These files must be placed into a separate directory inside the component directory, for instance, ``ulp/``.
.. note::
When registering the component (via ``idf_component_register``), this directory should not be added to the ``SRC_DIRS`` argument. The logic behind this is that the ESP-IDF build system will compile files found in ``SRC_DIRS`` based on their extensions. For ``.S`` files, ``{IDF_TARGET_TOOLCHAIN_PREFIX}-as`` assembler is used. This is not desirable for ULP FSM assembly files, so the easiest way to achieve the distinction is by placing ULP FSM assembly files into a separate directory. The ULP FSM assembly source files should also **not** be added to ``SRCS`` for the same reason. See the steps below for how to properly add ULP FSM assembly source files.
2. Call ``ulp_embed_binary`` from the component CMakeLists.txt after registration. For example::
...
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}")
The first argument to ``ulp_embed_binary`` specifies the ULP FSM binary name. The name specified here will also be used by other generated artifacts such as the ELF file, map file, header file and linker export file. The second argument specifies the ULP FSM assembly source files. Finally, the third argument specifies the list of component source files which include the header file to be generated. This list is needed to build the dependencies correctly and ensure that the generated header file will be created before any of these files are compiled. See the section below for the concept of generated header files for ULP applications.
Variables in the ULP code will be prefixed with ``ulp_`` (default value) in this generated header file.
If you need to embed multiple ULP programs, you may add a custom prefix in order to avoid conflicting variable names like this:
.. code-block:: cmake
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::")
The additional PREFIX argument can be a C style prefix (like ``ulp2_``) or a C++ style prefix (like ``ULP::``).
3. Build the application as usual (e.g., ``idf.py app``).
Inside, the build system will take the following steps to build ULP FSM program:
1. **Run each assembly file (foo.S) through the C preprocessor.** This step generates the preprocessed assembly files (foo.ulp.S) in the component build directory. This step also generates dependency files (foo.ulp.d).
2. **Run preprocessed assembly sources through the assembler.** This produces object (foo.ulp.o) and listing (foo.ulp.lst) files. Listing files are generated for debugging purposes and are not used at later stages of the build process.
3. **Run the linker script template through the C preprocessor.** The template is located in ``components/ulp/ld`` directory.
4. **Link the object files into an output ELF file** (``ulp_app_name.elf``). The Map file (``ulp_app_name.map``) generated at this stage may be useful for debugging purposes.
5. **Dump the contents of the ELF file into a binary** (``ulp_app_name.bin``) which can then be embedded into the application.
6. **Generate a list of global symbols** (``ulp_app_name.sym``) in the ELF file using ``esp32ulp-elf-nm``.
7. **Create an LD export script and a header file** (``ulp_app_name.ld`` and ``ulp_app_name.h``) containing the symbols from ``ulp_app_name.sym``. This is done using the ``esp32ulp_mapgen.py`` utility.
8. **Add the generated binary to the list of binary files** to be embedded into the application.
Accessing the ULP FSM Program Variables
---------------------------------------
Global symbols defined in the ULP FSM program may be used inside the main program.
For example, the ULP FSM program may define a variable ``measurement_count`` which will define the number of ADC measurements the program needs to make before waking up the chip from Deep-sleep::
.global measurement_count
measurement_count: .long 0
// later, use measurement_count
move r3, measurement_count
ld r3, r3, 0
The main program needs to initialize this variable before the ULP program is started. The build system makes this possible by generating the ``${ULP_APP_NAME}.h`` and ``${ULP_APP_NAME}.ld`` files which define the global symbols present in the ULP program. Each global symbol defined in the ULP program is included in these files and are prefixed with ``ulp_``.
The header file contains the declaration of the symbol::
extern uint32_t ulp_measurement_count;
Note that all symbols (variables, arrays, functions) are declared as ``uint32_t``. For functions and arrays, take the address of the symbol and cast it to the appropriate type.
The generated linker script file defines the locations of symbols in RTC_SLOW_MEM::
PROVIDE ( ulp_measurement_count = 0x50000060 );
To access the ULP program variables from the main program, the generated header file should be included using an ``include`` statement. This will allow the ULP program variables to be accessed as regular variables::
#include "ulp_app_name.h"
// later
void init_ulp_vars() {
ulp_measurement_count = 64;
}
.. only:: esp32
Note that the ULP FSM program can only use the lower 16 bits of each 32-bit word in RTC memory, because the registers are 16-bit, and there is no instruction to load from the high part of the word. Likewise, the ULP store instruction writes register values into the lower 16 bits of the 32-bit word in RTC memory. The upper 16 bits are written with a value which depends on the address of the store instruction, thus when reading variables written by the ULP coprocessor, the main application needs to mask the upper 16 bits, e.g.,::
printf("Last measurement value: %d\n", ulp_last_measurement & UINT16_MAX);
Starting the ULP FSM Program
----------------------------
To run a ULP FSM program, the main application needs to load the ULP program into RTC memory using the :cpp:func:`ulp_load_binary` function, and then start it using the :cpp:func:`ulp_run` function.
Note that the ``Enable Ultra Low Power (ULP) Coprocessor`` option must be enabled in menuconfig to work with ULP. To select the type of ULP to be used, the ``ULP Co-processor type`` option must be set. To reserve memory for the ULP, the ``RTC slow memory reserved for coprocessor`` option must be set to a value big enough to store ULP code and data. If the application components contain multiple ULP programs, then the size of the RTC memory must be sufficient to hold the largest one.
Each ULP program is embedded into the ESP-IDF application as a binary blob. The application can reference this blob and load it in the following way (suppose ULP_APP_NAME was defined to ``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 // load address, set to 0 when using default linker scripts
bin_start,
(bin_end - bin_start) / sizeof(uint32_t)) );
}
Once the program is loaded into RTC memory, the application can start it by passing the address of the entry point to the ``ulp_run`` function::
ESP_ERROR_CHECK( ulp_run(&ulp_entry - RTC_SLOW_MEM) );
Declaration of the entry point symbol comes from the generated header file mentioned above, ``${ULP_APP_NAME}.h``. In the assembly source of the ULP FSM application, this symbol must be marked as ``.global``::
.global entry
entry:
// code starts here
.. only:: esp32
ESP32 ULP Program Flow
-----------------------
ESP32 ULP coprocessor is started by a timer. The timer is started once :cpp:func:`ulp_run` is called. The timer counts a number of RTC_SLOW_CLK ticks (by default, produced by an internal 150 kHz RC oscillator). The number of ticks is set using ``SENS_ULP_CP_SLEEP_CYCx_REG`` registers (x = 0..4). When starting the ULP for the first time, ``SENS_ULP_CP_SLEEP_CYC0_REG`` will be used to set the number of timer ticks. Later the ULP program can select another ``SENS_ULP_CP_SLEEP_CYCx_REG`` register using ``sleep`` instruction.
The application can set ULP timer period values (SENS_ULP_CP_SLEEP_CYCx_REG, x = 0..4) using ``ulp_set_wakeup_period`` function.
Once the timer counts the number of ticks set in the selected ``SENS_ULP_CP_SLEEP_CYCx_REG`` register, ULP coprocessor powers up and starts running the program from the entry point set in the call to :cpp:func:`ulp_run`.
The program runs until it encounters a ``halt`` instruction or an illegal instruction. Once the program halts the ULP coprocessor powers down and the timer is started again.
To disable the timer (effectively preventing the ULP program from running again), clear the ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` bit in the ``RTC_CNTL_STATE0_REG`` register. This can be done both from ULP code and from the main program.
.. only:: esp32s2 or esp32s3
{IDF_TARGET_NAME} ULP Program Flow
----------------------------------
{IDF_TARGET_NAME} ULP coprocessor is started by a timer. The timer is started once :cpp:func:`ulp_run` is called. The timer counts a number of RTC_SLOW_CLK ticks (by default, produced by an internal 90 kHz RC oscillator). The number of ticks is set using ``RTC_CNTL_ULP_CP_TIMER_1_REG`` register.
The application can set ULP timer period values by :cpp:func:`ulp_set_wakeup_period` function.
Once the timer counts the number of ticks set in the selected ``RTC_CNTL_ULP_CP_TIMER_1_REG`` register, ULP coprocessor powers up and starts running the program from the entry point set in the call to :cpp:func:`ulp_run`.
The program runs until it encounters a ``halt`` instruction or an illegal instruction. Once the program halts, ULP coprocessor powers down, and the timer is started again.
To disable the timer (effectively preventing the ULP program from running again), clear the ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` bit in the ``RTC_CNTL_ULP_CP_TIMER_REG`` register. This can be done both from ULP code and from the main program.
Application Examples
--------------------
* :example:`system/ulp/ulp_fsm/ulp` demonstrates how to program the ULP FSM coprocessor to count pulses on an IO while the main CPUs are running other code or are in deep sleep, with the pulse count saved into NVS upon wakeup.
.. only:: esp32 or esp32s3
* :example:`system/ulp/ulp_fsm/ulp_adc` demonstrates how to use the ULP FSM coprocessor to periodically measure input voltage on a specific ADC channel during deep sleep, compare it to the set threshold, and wake up the system if the voltage is outside the threshold.
API Reference
-------------
.. include-build-file:: inc/ulp_fsm_common.inc
.. include-build-file:: inc/ulp_common.inc
.. _binutils-esp32ulp toolchain: https://github.com/espressif/binutils-gdb
Code examples for this API section are provided in the :example:`system/ulp` directory of ESP-IDF examples.

View File

@@ -39,9 +39,7 @@
system_time
:SOC_ASYNC_MEMCPY_SUPPORTED: async_memcpy
:esp32: himem
:SOC_ULP_FSM_SUPPORTED: ulp
:SOC_RISCV_COPROC_SUPPORTED: ulp-risc-v
:SOC_LP_CORE_SUPPORTED: ulp-lp-core
ulp
wdts

View File

@@ -0,0 +1,209 @@
ULP FSM 协处理器编程
====================
:link_to_translation:`en:[English]`
ULPUltra Low Power超低功耗协处理器是一种简单的有限状态机 (FSM),可以在主处理器处于深度睡眠模式时,使用 ADC、温度传感器和外部 I2C 传感器执行测量操作。ULP 协处理器可以访问 ``RTC_SLOW_MEM`` 内存区域及 ``RTC_CNTL````RTC_IO````SARADC`` 外设中的寄存器。ULP 协处理器使用 32 位固定宽度的指令32 位内存寻址,配备 4 个 16 位通用寄存器。在 ESP-IDF 中,此协处理器被称作 ``ULP FSM``
.. only:: esp32s2 or esp32s3
{IDF_TARGET_NAME} 基于 RISC-V 指令集架构提供另一种 ULP 协处理器。关于 ``ULP RISC-V`` 的详细信息,请参考 :doc:`ULP-RISC-V Coprocessor <../../../api-reference/system/ulp-risc-v>`
安装工具链
----------
ULP FSM 协处理器代码由汇编语言编写,使用 `binutils-esp32ulp 工具链`_ 进行编译。
按照 :doc:`快速入门指南 <../../../get-started/index>` 的步骤配置 ESP-IDF 及其 CMake 构建系统时,默认安装 ULP FSM 工具链。
编写 ULP FSM
-------------------
使用受支持的指令集即可编写 ULP FSM 协处理器,此外也可使用主处理器上的 C 语言宏进行编程。以下小节分别介绍了这两种方法:
.. toctree::
:maxdepth: 1
{IDF_TARGET_NAME} ULP 指令集参考 <ulp_instruction_set>
使用宏进行编程(旧版方法) <ulp_macros>
编译 ULP 代码
--------------
要将 ULP FSM 代码作为某组件的一部分进行编译,须执行以下步骤:
1. 用汇编语言编写的 ULP FSM 代码必须写入到一个或多个 ``.S`` 扩展文件中,且这些文件必须放在组件目录下的一个独立子目录中,例如 ``ulp/``
.. note::
在注册组件(通过 ``idf_component_register``)时,不应将该目录添加到 ``SRC_DIRS`` 参数中,因为 ESP-IDF 构建系统会根据文件扩展名来编译 ``SRC_DIRS`` 中包含的文件。对于 ``.S`` 文件,将使用 ``{IDF_TARGET_TOOLCHAIN_PREFIX}-as`` 汇编器进行编译,但这并不适用于 ULP FSM 汇编文件。因此,区分这类文件最简单的方式就是将 ULP FSM 汇编文件放到单独的目录中。同样ULP FSM 汇编源文件也 **不应该** 添加到 ``SRCS`` 中。请参考如下步骤,查看如何正确添加 ULP FSM 汇编源文件。
2. 注册后从组件 CMakeLists.txt 中调用 ``ulp_embed_binary``。示例如下::
...
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 FSM 应用程序生成的头文件等相关概念,请参考下文。
在这个生成的头文件中ULP 代码中的变量默认以 ``ulp_`` 作为前缀。
如果需要嵌入多个 ULP 程序,可以添加自定义前缀,以避免变量名冲突,如下所示:
.. code-block:: cmake
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::``)。
3. 继续使用常规方法(例如 ``idf.py app``)编译应用程序。
在内部,构建系统将按照以下步骤编译 ULP FSM 程序:
1. **通过 C 预处理器运行每个程序集文件 (foo.S)。** 此步骤在组件编译目录中生成预处理的汇编文件 (foo.ulp.S),同时生成依赖文件 (foo.ulp.d)。
2. **通过汇编器运行预处理过的汇编源码。** 此步骤会生成目标文件 (foo.ulp.o) 和清单 (foo.ulp.lst)。清单文件仅用于调试,不用于编译进程的后续步骤。
3. **通过 C 预处理器运行链接器脚本模板。** 模板位于 ``components/ulp/ld`` 目录中。
4. **将目标文件链接到 ELF 输出文件** (``ulp_app_name.elf``)。此步骤生成的 .map 文件 (``ulp_app_name.map``) 默认用于调试。
5. **将 ELF 文件中的内容转储为二进制文件** (``ulp_app_name.bin``),以便嵌入到应用程序中。
6. 使用 ``esp32ulp-elf-nm`` 从 ELF 文件中 **生成全局符号列表** (``ulp_app_name.sym``)。
7. **创建 LD 导出脚本和头文件** ``ulp_app_name.ld````ulp_app_name.h``),包含来自 ``ulp_app_name.sym`` 的符号。此步骤使用 ``esp32ulp_mapgen.py`` 工具来完成。
8. **将生成的二进制文件添加到要嵌入应用程序的二进制文件列表中。**
访问 ULP FSM 程序变量
------------------------
在 ULP FSM 程序中定义的全局符号也可以在主程序中使用。
例如ULP FSM 程序可以定义 ``measurement_count`` 变量,此变量可以定义程序从深度睡眠中唤醒芯片之前需要进行的 ADC 测量的次数::
.global measurement_count
measurement_count: .long 0
// later, use measurement_count
move r3, measurement_count
ld r3, r3, 0
主程序需要在启动 ULP 程序之前初始化该 ``measurement_count`` 变量,构建系统通过生成定义 ULP 编程中全局符号的 ``${ULP_APP_NAME}.h````${ULP_APP_NAME}.ld`` 文件实现上述操作。在 ULP 程序中定义的所有全局符号都包含在这两个文件中,且都以 ``ulp_`` 开头。
头文件包含对此类符号的声明::
extern uint32_t ulp_measurement_count;
注意,所有符号(包括变量、数组、函数)均被声明为 ``uint32_t``。对于函数和数组,先获取符号地址,然后转换为适当的类型。
生成的链接器脚本文件定义了 RTC_SLOW_MEM 中的符号位置::
PROVIDE ( ulp_measurement_count = 0x50000060 );
如果要从主程序访问 ULP 程序变量,应先使用 ``include`` 语句包含生成的头文件,这样,就可以像访问常规变量一样访问 ulp 程序变量。操作如下::
#include "ulp_app_name.h"
// later
void init_ulp_vars() {
ulp_measurement_count = 64;
}
.. only:: esp32
注意ULP FSM 程序在 RTC 内存中只能使用 32 位字的低 16 位,因为寄存器是 16 位的并且不具备从字的高位加载的指令。同样ULP 储存指令将寄存器值写入 RTC 内存中 32 位字的低 16 位。高 16 位写入的值取决于储存指令的地址,因此在读取 ULP 协处理器写的变量时,主应用程序需要屏蔽高 16 位,例如:
::
printf("Last measurement value: %d\n", ulp_last_measurement & UINT16_MAX);
启动 ULP FSM 程序
--------------------
要运行 ULP FSM 程序,主应用程序需要调用 :cpp:func:`ulp_load_binary` 函数将 ULP 程序加载到 RTC 内存中,然后调用 :cpp:func:`ulp_run` 函数,启动 ULP 程序。
注意,在 menuconfig 中必须启用 ``Enable Ultra Low Power (ULP) Coprocessor`` 选项,以便正常运行 ULP并且必须设置 ``ULP Co-processor type`` 选项,以便选择要使用的 ULP 类型。 ``RTC slow memory reserved for coprocessor`` 选项设置的值必须足够储存 ULP 代码和数据。如果应用程序组件包含多个 ULP 程序,则 RTC 内存必须足以容纳最大的程序。
每个 ULP 程序均以二进制 BLOB 的形式嵌入到 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 // load address, set to 0 when using default linker scripts
bin_start,
(bin_end - bin_start) / sizeof(uint32_t)) );
}
一旦上述程序加载到 RTC 内存后,应用程序即可将入口点的地址传递给 ``ulp_run`` 函数,以启动此程序::
ESP_ERROR_CHECK( ulp_run(&ulp_entry - RTC_SLOW_MEM) );
入口点符号的声明来自上述生成的头文件 ``${ULP_APP_NAME}.h``。在 ULP FSM 应用程序的汇编源代码中,此符号必须标记为 ``.global``::
.global entry
entry:
// code starts here
.. only:: esp32
ESP32 ULP 程序流
-------------------
ESP32 ULP 协处理器由定时器启动,而调用 :cpp:func:`ulp_run` 则可启动此定时器。定时器为 RTC_SLOW_CLK 的 Tick 事件计数默认情况下Tick 由内部 150 kHz RC 振荡器生成)。使用 ``SENS_ULP_CP_SLEEP_CYCx_REG`` 寄存器 (x = 0..4) 设置 Tick 数值。第一次启动 ULP 时,使用 ``SENS_ULP_CP_SLEEP_CYC0_REG`` 设置定时器 Tick 数值之后ULP 程序可以使用 ``sleep`` 指令来选择另一个 ``SENS_ULP_CP_SLEEP_CYCx_REG`` 寄存器。
此应用程序可以调用 ``ulp_set_wakeup_period`` 函数来设置 ULP 定时器周期值 (SENS_ULP_CP_SLEEP_CYCx_REG, x = 0..4)。
一旦定时器计数到 ``SENS_ULP_CP_SLEEP_CYCx_REG`` 寄存器设定的 Tick 数值ULP 协处理器就会启动,并调用 :cpp:func:`ulp_run` 的入口点开始运行程序。
程序保持运行,直到遇到 ``halt`` 指令或非法指令。一旦程序停止ULP 协处理器电源关闭,定时器再次启动。
如果想禁用定时器(有效防止 ULP 程序再次运行),可在 ULP 代码或主程序中清除 ``RTC_CNTL_STATE0_REG`` 寄存器中的 ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` 位。
.. only:: esp32s2 or esp32s3
{IDF_TARGET_NAME} ULP 程序流
----------------------------
{IDF_TARGET_NAME} ULP 协处理器由定时器启动,调用 :cpp:func:`ulp_run` 则可启动此定时器。定时器为 RTC_SLOW_CLK 的 Tick 事件计数默认情况下Tick 由内部 90 KHz RC 振荡器生成)。使用 ``RTC_CNTL_ULP_CP_TIMER_1_REG`` 寄存器设置 Tick 数值。
此应用程序可以调用 :cpp:func:`ulp_set_wakeup_period` 函数来设置 ULP 定时器周期值。
一旦定时器计数到 ``RTC_CNTL_ULP_CP_TIMER_1_REG`` 寄存器设定的 Tick 数值ULP 协处理器就会启动,并调用 :cpp:func:`ulp_run` 的入口点开始运行程序。
程序保持运行,直到遇到 ``halt`` 指令或非法指令。一旦程序停止ULP 协处理器电源关闭,定时器再次启动。
如果想禁用定时器(有效防止 ULP 程序再次运行),可在 ULP 代码或主程序中清除 ``RTC_CNTL_ULP_CP_TIMER_REG`` 寄存器中的 ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` 位。
应用示例
--------------------
* :example:`system/ulp/ulp_fsm/ulp` 展示了主处理器运行其他代码或处于 Deep-sleep 状态时,使用 ULP FSM 协处理器对 IO 脉冲进行计数,脉冲计数会在唤醒时保存到 NVS 中。
.. only:: esp32 or esp32s3
* :example:`system/ulp/ulp_fsm/ulp_adc` 展示了主处理器处于 Deep-sleep 状态时ULP FSM 协处理器测量特定 ADC 通道上的输入电压,将其与设定的阈值进行比较,电压超出阈值时唤醒系统。
API 参考
-------------
.. include-build-file:: inc/ulp_fsm_common.inc
.. include-build-file:: inc/ulp_common.inc
.. _binutils-esp32ulp 工具链: https://github.com/espressif/binutils-gdb

View File

@@ -1,207 +1,27 @@
ULP 协处理器编程
================
ULP 协处理器
====================
:link_to_translation:`en:[English]`
ULPUltra Low Power超低功耗协处理器是一种简单的有限状态机 (FSM),可以在主处理器处于深度睡眠模式时,使用 ADC、温度传感器和外部 I2C 传感器执行测量操作。ULP 协处理器可以访问 ``RTC_SLOW_MEM`` 内存区域及 ``RTC_CNTL````RTC_IO````SARADC`` 外设中的寄存器。ULP 协处理器使用 32 位固定宽度的指令32 位内存寻址,配备 4 个 16 位通用寄存器。在 ESP-IDF 项目中,此协处理器被称作 ``ULP FSM``
ULPUltra Low Power超低功耗协处理器可在主 CPU 处于深度睡眠模式时执行任务从而实现功耗优化并延长电池使用寿命。ULP 协处理器能监测传感器、控制外设,并处理其他无需主 CPU 算力的任务。当满足特定条件(如传感器读数超过预设阈值)时,它还能唤醒主 CPU。
.. only:: esp32s2 or esp32s3
.. only:: not SOC_ULP_SUPPORTED
{IDF_TARGET_NAME} 基于 RISC-V 指令集架构提供另一种 ULP 协处理器。关于 ``ULP RISC-V`` 的详细信息,请参考 :doc:`ULP-RISC-V Coprocessor <../../../api-reference/system/ulp-risc-v>`
.. note:: {IDF_TARGET_NAME} 不支持 ULP 协处理器
安装工具链
----------
.. only:: SOC_ULP_SUPPORTED
ULP FSM 协处理器代码由汇编语言编写,使用 `binutils-esp32ulp 工具链`_ 进行编译。
芯片上的 ULP 协处理器可能包含以下一种或多种类型,但同一时间只能运行一种:
如果按照 :doc:`快速入门指南 <../../../get-started/index>` 中的介绍安装好了 ESP-IDF 及其 CMake 构建系统,那么 ULP 工具链已经默认安装到了你的开发环境中。
- **ULP FSM**:基于有限状态机 (FSM) 的协处理器。适用于简单任务,相关应用需使用汇编语言或 C 语言宏编写。ESP32、ESP32-S2 和 ESP32-S3 支持此类型
- **ULP RISC-V**:基于 RISC-V 架构的协处理器。提供更强的计算能力和灵活性,适合运行用 C 语言编写的复杂应用。ESP32-S2 和 ESP32-S3 支持此类型。
- **ULP LP Core**:基于 RISC-V 架构的增强型协处理器,兼具 **ULP RISC-V** 的优势并新增扩展内存访问、更广泛的外设访问、调试模块和中断控制器等功能。该处理器甚至可在全系统运行时工作。ESP32-C5、ESP32-C6、ESP32-P4 及后续芯片支持此类型。
编写 ULP FSM
-------------------
.. toctree::
:maxdepth: 1
使用受支持的指令集即可编写 ULP FSM 协处理器,此外也可使用主处理器上的 C 语言宏进行编程。以下小节分别介绍了这两种方法:
:SOC_ULP_FSM_SUPPORTED: ulp-fsm
:SOC_RISCV_COPROC_SUPPORTED: ulp-risc-v
:SOC_LP_CORE_SUPPORTED: ulp-lp-core
.. toctree::
:maxdepth: 1
{IDF_TARGET_NAME} ULP 指令集参考 <ulp_instruction_set>
使用宏进行编程(遗留) <ulp_macros>
编译 ULP 代码
--------------
若需要将 ULP FSM 代码编译为某组件的一部分,则必须执行以下步骤:
1. 用汇编语言编写的 ULP FSM 代码必须导入到一个或多个 ``.S`` 扩展文件中,且这些文件必须放在组件目录中一个独立的目录中,例如 ``ulp/``
.. note::
在注册组件(通过 ``idf_component_register``)时,不应将该目录添加到 ``SRC_DIRS`` 参数中。因为 ESP-IDF 构建系统将基于文件扩展名编译在 ``SRC_DIRS`` 中搜索到的文件。对于 ``.S`` 文件,使用的是 ``{IDF_TARGET_TOOLCHAIN_PREFIX}-as`` 汇编器。但这并不适用于 ULP FSM 程序集文件,因此体现这种区别最简单的方式就是将 ULP FSM 程序集文件放到单独的目录中。同样ULP FSM 程序集源文件也 **不应该** 添加到 ``SRCS`` 中。请参考如下步骤,查看如何正确添加 ULP FSM 程序集源文件。
2. 注册后从组件 CMakeLists.txt 中调用 ``ulp_embed_binary`` 示例如下::
...
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 二进制文件命名。指定的此名称也用于生成的其他文件ELF 文件、.map 文件、头文件和链接器导出文件。第二个参数指定 ULP FSM 程序集源文件。最后,第三个参数指定组件源文件列表,其中包括被生成的头文件。此列表用以建立正确的依赖项,并确保在编译这些文件之前先创建生成的头文件。有关 ULP FSM 应用程序生成的头文件等相关概念,请参考下文。
在这个生成的头文件中ULP 代码中的变量默认以 ``ulp_`` 作为前缀。
如果需要嵌入多个 ULP 程序,可以添加自定义前缀,以避免变量名冲突,如下所示:
.. code-block:: cmake
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::``)。
3. 使用常规方法(例如 ``idf.py app``)编译应用程序。
在内部,构建系统将按照以下步骤编译 ULP FSM 程序:
1. **通过 C 预处理器运行每个程序集文件 (foo.S)。** 此步骤在组件编译目录中生成预处理的程序集文件 (foo.ulp.S),同时生成依赖文件 (foo.ulp.d)。
2. **通过汇编器运行预处理过的汇编源码。** 此步骤会生成目标文件 (foo.ulp.o) 和清单 (foo.ulp.lst)。清单文件仅用于调试,不用于编译进程的后续步骤。
3. **通过 C 预处理器运行链接器脚本模板。** 模板位于 ``components/ulp/ld`` 目录中。
4. **将目标文件链接到 ELF 输出文件** (``ulp_app_name.elf``)。此步骤生成的 .map 文件 (``ulp_app_name.map``) 默认用于调试。
5. **将 ELF 文件中的内容转储为二进制文件** (``ulp_app_name.bin``),以便嵌入到应用程序中。
6. 使用 ``esp32ulp-elf-nm`` 在 ELF 文件中 **生成全局符号列表** (``ulp_app_name.sym``)。
7. **创建 LD 导出脚本和头文件** ``ulp_app_name.ld````ulp_app_name.h``),包含来自 ``ulp_app_name.sym`` 的符号。此步骤可借助 ``esp32ulp_mapgen.py`` 工具来完成。
8. **将生成的二进制文件添加到要嵌入应用程序的二进制文件列表中。**
访问 ULP FSM 程序变量
------------------------
在 ULP FSM 程序中定义的全局符号也可以在主程序中使用。
例如ULP FSM 程序可以定义 ``measurement_count`` 变量,此变量可以定义程序从深度睡眠中唤醒芯片之前需要进行的 ADC 测量的次数::
.global measurement_count
measurement_count: .long 0
// later, use measurement_count
move r3, measurement_count
ld r3, r3, 0
主程序需要在启动 ULP 程序之前初始化 ``measurement_count`` 变量,构建系统通过生成定义 ULP 编程中全局符号的 ``${ULP_APP_NAME}.h````${ULP_APP_NAME}.ld`` 文件实现上述操作。这些文件包含了在 ULP 程序中定义的所有全局符号,文件以 ``ulp_`` 开头。
头文件包含对此类符号的声明::
extern uint32_t ulp_measurement_count;
注意,所有符号(包括变量、数组、函数)均被声明为 ``uint32_t``。对于函数和数组,先获取符号地址,然后转换为适当的类型。
生成的链接器脚本文件定义了 RTC_SLOW_MEM 中的符号位置::
PROVIDE ( ulp_measurement_count = 0x50000060 );
如果要从主程序访问 ULP 程序变量,应先使用 ``include`` 语句包含生成的头文件,这样,就可以像访问常规变量一样访问 ulp 程序变量。操作如下::
#include "ulp_app_name.h"
// later
void init_ulp_vars() {
ulp_measurement_count = 64;
}
.. only:: esp32
注意ULP FSM 程序在 RTC 内存中只能使用 32 位字的低 16 位,因为寄存器是 16 位的并且不具备从字的高位加载的指令。同样ULP 储存指令将寄存器值写入 32 位字的低 16 位中。高 16 位写入的值取决于储存指令的地址,因此在读取 ULP 协处理器写的变量时,主应用程序需要屏蔽高 16 位,例如::
printf("Last measurement value: %d\n", ulp_last_measurement & UINT16_MAX);
启动 ULP FSM 程序
--------------------
要运行 ULP FSM 程序,主应用程序需要调用 :cpp:func:`ulp_load_binary` 函数将 ULP 程序加载到 RTC 内存中,然后调用 :cpp:func:`ulp_run` 函数,启动 ULP 程序。
注意,在 menuconfig 中必须启用 ``Enable Ultra Low Power (ULP) Coprocessor`` 选项,以便正常运行 ULP并且必须设置 ``ULP Co-processor type`` 选项,以便选择要使用的 ULP 类型。 ``RTC slow memory reserved for coprocessor`` 选项设置的值必须足够储存 ULP 代码和数据。如果应用程序组件包含多个 ULP 程序,则 RTC 内存必须足以容纳最大的程序。
每个 ULP 程序均以二进制 BLOB 的形式嵌入到 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 // load address, set to 0 when using default linker scripts
bin_start,
(bin_end - bin_start) / sizeof(uint32_t)) );
}
一旦上述程序加载到 RTC 内存后,应用程序即可启动此程序,并将入口点的地址传递给 ``ulp_run`` 函数::
ESP_ERROR_CHECK( ulp_run(&ulp_entry - RTC_SLOW_MEM) );
上述生成的头文件 ``${ULP_APP_NAME}.h`` 声明了入口点符号。在 ULP 应用程序的汇编源代码中,此符号必须标记为 ``.global``::
.global entry
entry:
// code starts here
.. only:: esp32
ESP32 ULP 程序流
-------------------
ESP32 ULP 协处理器由定时器启动,而调用 :cpp:func:`ulp_run` 则可启动此定时器。定时器为 RTC_SLOW_CLK 的 Tick 事件计数默认情况下Tick 由内部 150 KHz RC 振荡器生成)。使用 ``SENS_ULP_CP_SLEEP_CYCx_REG`` 寄存器 (x = 0..4) 设置 Tick 数值。第一次启动 ULP 时,使用 ``SENS_ULP_CP_SLEEP_CYC0_REG`` 设置定时器 Tick 数值之后ULP 程序可以使用 ``sleep`` 指令来选择另一个 ``SENS_ULP_CP_SLEEP_CYCx_REG`` 寄存器。
此应用程序可以调用 ``ulp_set_wakeup_period`` 函数来设置 ULP 定时器周期值 (SENS_ULP_CP_SLEEP_CYCx_REG, x = 0..4)。
一旦定时器计数到 ``SENS_ULP_CP_SLEEP_CYCx_REG`` 寄存器设定的 Tick 数值ULP 协处理器就会启动,并调用 :cpp:func:`ulp_run` 的入口点开始运行程序。
程序保持运行,直到遇到 ``halt`` 指令或非法指令。一旦程序停止ULP 协处理器电源关闭,定时器再次启动。
如果想禁用定时器(有效防止 ULP 程序再次运行),可在 ULP 代码或主程序中清除 ``RTC_CNTL_STATE0_REG`` 寄存器中的 ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` 位。
.. only:: esp32s2 or esp32s3
{IDF_TARGET_NAME} ULP 程序流
----------------------------
{IDF_TARGET_NAME} ULP 协处理器由定时器启动,调用 :cpp:func:`ulp_run` 则可启动此定时器。定时器为 RTC_SLOW_CLK 的 Tick 事件计数默认情况下Tick 由内部 90 KHz RC 振荡器生成)。使用 ``RTC_CNTL_ULP_CP_TIMER_1_REG`` 寄存器设置 Tick 数值。
此应用程序可以调用 :cpp:func:`ulp_set_wakeup_period` 函数来设置 ULP 定时器周期值。
一旦定时器计数到 ``RTC_CNTL_ULP_CP_TIMER_1_REG`` 寄存器设定的 Tick 数值ULP 协处理器就会启动,并调用 :cpp:func:`ulp_run` 的入口点开始运行程序。
程序保持运行,直到遇到 ``halt`` 指令或非法指令。一旦程序停止ULP 协处理器电源关闭,定时器再次启动。
如果想禁用定时器(有效防止 ULP 程序再次运行),可在 ULP 代码或主程序中清除 ``RTC_CNTL_ULP_CP_TIMER_REG`` 寄存器中的 ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` 位。
应用示例
--------------------
* :example:`system/ulp/ulp_fsm/ulp` 展示了主处理器运行其他代码或处于 Deep-sleep 状态时,使用 ULP FSM 协处理器对 IO 脉冲进行计数,脉冲计数会在唤醒时保存到 NVS 中。
.. only:: esp32 or esp32s3
* :example:`system/ulp/ulp_fsm/ulp_adc` 展示了主处理器处于 Deep-sleep 状态时ULP FSM 协处理器测量特定 ADC 通道上的输入电压,将其与设定的阈值进行比较,电压超出阈值时唤醒系统。
API 参考
-------------
.. include-build-file:: inc/ulp_fsm_common.inc
.. include-build-file:: inc/ulp_common.inc
.. _binutils-esp32ulp 工具链: https://github.com/espressif/binutils-gdb
本 API 章节的代码示例位于 ESP-IDF 示例项目的 :example:`system/ulp` 目录下。