diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index d9241d545c46b..6b639e342f239 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -279,13 +279,10 @@ For more details see Espressif's `ESP-IDF RMT documentation. Ultra-Low-Power co-processor ---------------------------- -This class gives access to the Ultra Low Power (ULP) co-processor on the ESP32, -ESP32-S2 and ESP32-S3 chips. - -.. warning:: - - This class does not provide access to the RISCV ULP co-processor available - on the ESP32-S2 and ESP32-S3 chips. +This class gives access to the Finite State Machine (FSM) Ultra Low Power (ULP) +co-processor on the ESP32, ESP32-S2 and ESP32-S3 chips. In addition the ESP32-S2 +and ESP32-S3 chips contain a RISCV co-processor which can be accessed through this +class. .. class:: ULP() @@ -299,9 +296,76 @@ ESP32-S2 and ESP32-S3 chips. Load a *program_binary* into the ULP at the given *load_addr*. +.. method:: ULP.load_riscv_binary() + + Load the pre-compiled ULP binary from memory. + .. method:: ULP.run(entry_point) - Start the ULP running at the given *entry_point*. + Start the FSM ULP running at the given *entry_point*. + +.. method:: ULP.riscv_run() + + Start the RISCV ULP running. + +Micropython currently supports the inclusion of one RISCV ULP program which is packaged +into the firmware image during compilation. The *load_riscv_binary* method will load the +binary from flash into the RTC memory and the the program can be started with the *riscv_run* method. + +The following steps are required to use the RISCV ULP. + +The RISCV ULP code should be contained in a file main.c and placed in a sub-folder ulp_riscv:: + + micropython/ports/esp32/ulp_riscv/main.c + +The RISCV ULP can be enabled by including the config file in your board mpconfigboard.cmake :: + + boards/sdkconfig.ulp_riscv + +Compile and flash firmware as per usual. + +The following limitations should be noted. + +.. Warning:: + - As detailed in the S2/S3 technical reference manuals, only RTC_GPIO0-3 can be utilised for + I2C communications using the ULP. + - ESP IDF V4.4 supports general GPIO operations and the ADC. + - I2C support is currently only available in ESP IDF V5.1. + +Example RISCV ULP code:: + + #include + #include + #include + #include "esp32s3/ulp_riscv.h" + #include "ulp_riscv/ulp_riscv_utils.h" + #include "ulp_riscv/ulp_riscv_gpio.h" + + /* this variable will be exported as a public symbol, visible from main CPU: */ + bool gpio_level = false; + + int main (void) + { + ulp_riscv_gpio_init(14); + ulp_riscv_gpio_set_output_mode(14, RTCIO_MODE_OUTPUT); + ulp_riscv_gpio_output_enable(14); + + while(1) { + // Turn on GPIO + ulp_riscv_gpio_output_level(14, 1); + gpio_level = true; + // Delay + ulp_riscv_delay_cycles(1000 * 1000); + // Turn off GPIO + ulp_riscv_gpio_output_level(14, 0); + gpio_level = false; + // Delay + ulp_riscv_delay_cycles(1000 * 1000); + } + + /* ulp_riscv_halt() is called automatically when main exits */ + return 0; + } Constants diff --git a/ports/esp32/boards/GENERIC_S3/sdkconfig.board b/ports/esp32/boards/GENERIC_S3/sdkconfig.board index c9726d4232ed4..6862e10bd8d0a 100644 --- a/ports/esp32/boards/GENERIC_S3/sdkconfig.board +++ b/ports/esp32/boards/GENERIC_S3/sdkconfig.board @@ -10,3 +10,7 @@ CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y CONFIG_ESPTOOLPY_FLASHSIZE_16MB= CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-8MiB.csv" + +CONFIG_ESP32S3_ULP_COPROC_ENABLED=y +CONFIG_ESP32S3_ULP_COPROC_RISCV=y +CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM=4096 diff --git a/ports/esp32/boards/sdkconfig.ulp_riscv b/ports/esp32/boards/sdkconfig.ulp_riscv new file mode 100644 index 0000000000000..64bc0bd57d3e3 --- /dev/null +++ b/ports/esp32/boards/sdkconfig.ulp_riscv @@ -0,0 +1,4 @@ +# Enables the RISCV ULP on the S2/S3 + +CONFIG_ESP32S3_ULP_COPROC_RISCV=y +CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM=4096 \ No newline at end of file diff --git a/ports/esp32/esp32_ulp.c b/ports/esp32/esp32_ulp.c index e7962ce1acb5b..abeb205cfae22 100644 --- a/ports/esp32/esp32_ulp.c +++ b/ports/esp32/esp32_ulp.c @@ -25,11 +25,22 @@ */ #include "py/runtime.h" +#include "py/parsenumbase.h" +#include "py/smallint.h" +#include "py/objint.h" +#if CONFIG_IDF_TARGET_ESP32 +#include "esp32/ulp.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/ulp.h" +#include "esp32s2/ulp_riscv.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/ulp.h" +#include "esp32s3/ulp_riscv.h" +#endif #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 -#include "esp32/ulp.h" #include "esp_err.h" typedef struct _esp32_ulp_obj_t { @@ -74,6 +85,35 @@ STATIC mp_obj_t esp32_ulp_load_binary(mp_obj_t self_in, mp_obj_t load_addr_in, m } STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_ulp_load_binary_obj, esp32_ulp_load_binary); +#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + + #if CONFIG_ESP32S3_ULP_COPROC_RISCV + + extern const uint8_t bin_start[] asm("_binary_ulp_main_bin_start"); + extern const uint8_t bin_end[] asm("_binary_ulp_main_bin_end"); + + STATIC mp_obj_t esp32_ulp_riscv_load_binary(mp_obj_t self_in) { + + int _errno = ulp_riscv_load_binary(bin_start, (bin_end - bin_start)); + if (_errno != ESP_OK) { + mp_raise_OSError(_errno); + } + return mp_const_none; + } + STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_ulp_riscv_load_binary_obj, esp32_ulp_riscv_load_binary); + + STATIC mp_obj_t esp32_ulp_riscv_run(mp_obj_t self_in) { + + int _errno = ulp_riscv_run(); + if (_errno != ESP_OK) { + mp_raise_OSError(_errno); + } + return mp_const_none; + } + STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_ulp_riscv_run_obj, esp32_ulp_riscv_run); + #endif +#endif + STATIC mp_obj_t esp32_ulp_run(mp_obj_t self_in, mp_obj_t entry_point_in) { mp_uint_t entry_point = mp_obj_get_int(entry_point_in); int _errno = ulp_run(entry_point / sizeof(uint32_t)); @@ -84,6 +124,12 @@ STATIC mp_obj_t esp32_ulp_run(mp_obj_t self_in, mp_obj_t entry_point_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_ulp_run_obj, esp32_ulp_run); +#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #if CONFIG_ESP32S3_ULP_COPROC_RISCV + #include "genhdr/esp32_ulpconst_qstr.h" + #endif +#endif + STATIC const mp_rom_map_elem_t esp32_ulp_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_wakeup_period), MP_ROM_PTR(&esp32_ulp_set_wakeup_period_obj) }, { MP_ROM_QSTR(MP_QSTR_load_binary), MP_ROM_PTR(&esp32_ulp_load_binary_obj) }, @@ -95,6 +141,14 @@ STATIC const mp_rom_map_elem_t esp32_ulp_locals_dict_table[] = { #elif CONFIG_IDF_TARGET_ESP32S3 { MP_ROM_QSTR(MP_QSTR_RESERVE_MEM), MP_ROM_INT(CONFIG_ESP32S3_ULP_COPROC_RESERVE_MEM) }, #endif + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #if CONFIG_ESP32S3_ULP_COPROC_RISCV + { MP_ROM_QSTR(MP_QSTR_riscv_load_binary), MP_ROM_PTR(&esp32_ulp_riscv_load_binary_obj) }, + { MP_ROM_QSTR(MP_QSTR_riscv_run), MP_ROM_PTR(&esp32_ulp_riscv_run_obj) }, + #include "genhdr/esp32_ulpconst_mpz.h" + #endif + #endif + }; STATIC MP_DEFINE_CONST_DICT(esp32_ulp_locals_dict, esp32_ulp_locals_dict_table); diff --git a/ports/esp32/main/CMakeLists.txt b/ports/esp32/main/CMakeLists.txt index 51e53c202fdfb..26a1b8e91b984 100644 --- a/ports/esp32/main/CMakeLists.txt +++ b/ports/esp32/main/CMakeLists.txt @@ -204,8 +204,8 @@ target_include_directories(${MICROPY_TARGET} PUBLIC ) # Add additional extmod and usermod components. -target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) -target_link_libraries(${MICROPY_TARGET} usermod) +target_link_libraries(${MICROPY_TARGET} PRIVATE micropy_extmod_btree) +target_link_libraries(${MICROPY_TARGET} PRIVATE usermod) # Collect all of the include directories and compile definitions for the IDF components. foreach(comp ${IDF_COMPONENTS}) @@ -225,3 +225,26 @@ endif() # Include the main MicroPython cmake rules. include(${MICROPY_DIR}/py/mkrules.cmake) + +# Include riscv ulp code if exists in ulp_riscv sub directory +if(EXISTS ${PROJECT_DIR}/ulp_riscv/main.c) + set(ulp_app_name ulp_${COMPONENT_NAME}) + set(ulp_sources "${PROJECT_DIR}/ulp_riscv/main.c") + set(ulp_exp_dep_srcs "${PROJECT_DIR}/main.c") + + ulp_embed_binary(${ulp_app_name} "${ulp_sources}" "${ulp_exp_dep_srcs}") +endif() + +# Generate ULP variable constants +if(EXISTS ${PROJECT_DIR}/ulp_riscv/main.c) + add_custom_command( + OUTPUT ${PROJECT_DIR}/build/esp32_ulpconst_qstr.h + COMMAND python ${PROJECT_DIR}/make-esp32ulpconst.py ../../esp-idf/main/ulp_main/ulp_main.ld + DEPENDS ${PROJECT_DIR}/build/esp-idf/main/ulp_main/ulp_main.ld + COMMENT "Parsing ULP headers" + VERBATIM + ) + + add_library(ULP_CONST INTERFACE ${PROJECT_DIR}/build/esp32_ulpconst_qstr.h) + +endif() \ No newline at end of file diff --git a/ports/esp32/make-esp32ulpconst.py b/ports/esp32/make-esp32ulpconst.py new file mode 100644 index 0000000000000..c7d73db986dab --- /dev/null +++ b/ports/esp32/make-esp32ulpconst.py @@ -0,0 +1,92 @@ +""" +This script reads in the given CMSIS device include file (eg stm32f405xx.h), +extracts relevant peripheral constants, and creates qstrs, mpz's and constants +for the stm module. +""" + +# Use a pattern rule here so that make will only call make-stmconst.py once to +# make both modstm_const.h and modstm_qstr.h +# $(HEADER_BUILD)/%_const.h $(BUILD)/%_qstr.h: $(CMSIS_MCU_HDR) make-stmconst.py | $(HEADER_BUILD) +# $(ECHO) "GEN stmconst $@" +# $(Q)$(PYTHON) make-stmconst.py --qstr $(GEN_STMCONST_QSTR) --mpz $(GEN_STMCONST_MPZ) $(CMSIS_MCU_HDR) > $(GEN_STMCONST_HDR) + +from __future__ import print_function + +import argparse +import re + +# Python 2/3 compatibility +import platform + +def parse_file(filename): + + regex_pattern = '(\w+)\s\=\s*(?:"([^"]*)"|(\S+))' + + with open(filename, 'r') as file: + content = file.read() + + shared_variables = {} + + matches = re.findall(regex_pattern, content) + + for m in matches: + + variable = m[0] + address = m[2] + # Check if the key starts with "ulp_" + if variable.startswith("ulp_"): + # Remove "ulp_" from the beginning of the key + variable = variable[4:].upper() + + shared_variables[variable.strip()] = address.strip() + + return shared_variables + +def main(): + cmd_parser = argparse.ArgumentParser(description="Extract ULP constants from a C header file.") + cmd_parser.add_argument("file", nargs=1, help="input file") + cmd_parser.add_argument( + "-q", + "--qstr", + dest="qstr_filename", + default="build/esp32_ulpconst_qstr.h", + help="Specified the name of the generated qstr header file", + ) + # cmd_parser.add_argument( + # "--mpz", + # dest="mpz_filename", + # default="build/esp32_ulpconst_mpz.h", + # help="the destination file of the generated mpz header", + # ) + args = cmd_parser.parse_args() + + shared_variables = parse_file(args.file[0]) + + print("// Automatically generated from %s by make-esp32ulpconst.py" % args.file[0]) + print("") + + with open("../../genhdr/esp32_ulpconst_qstr.h", "wt") as h_file: + for key, value in shared_variables.items(): + assert 0 <= int(value, 16) <= 0xFFFFFFFF + print( + "STATIC const mp_obj_int_t mpz_%08x = {{&mp_type_int}, " + "{.neg=0, .fixed_dig=1, .alloc=2, .len=2, " + ".dig=(uint16_t*)(const uint16_t[]){%#x, %#x}}};" + % (int(value, 16), int(value, 16) & 0xFFFF, (int(value, 16) >> 16) & 0xFFFF), + file=h_file, + ) + + with open("../../genhdr/esp32_ulpconst_mpz.h", "wt") as mpz_file: + for key, value in shared_variables.items(): + print( + "{MP_ROM_QSTR(MP_QSTR_%s), MP_ROM_PTR( & mpz_%08x)}," + % (key, int(value, 16)), + file=mpz_file, + ) + + #{MP_ROM_QSTR(MP_QSTR_VOLTAGE), MP_ROM_PTR( & mpz_50000130)}, + + return shared_variables + +if __name__ == "__main__": + main() diff --git a/ports/esp32/ulp_riscv/main.c b/ports/esp32/ulp_riscv/main.c new file mode 100644 index 0000000000000..3493bc9fe53f7 --- /dev/null +++ b/ports/esp32/ulp_riscv/main.c @@ -0,0 +1,43 @@ +/* ULP-RISC-V example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. + + This code runs on ULP-RISC-V coprocessor +*/ + +#include +#include +#include +#include "esp32s3/ulp_riscv.h" +#include "ulp_riscv/ulp_riscv_utils.h" +#include "ulp_riscv/ulp_riscv_gpio.h" + +/* this variable will be exported as a public symbol, visible from main CPU: */ +bool gpio_level = false; + +int main (void) +{ + ulp_riscv_gpio_init(14); + ulp_riscv_gpio_set_output_mode(14, RTCIO_MODE_OUTPUT); + ulp_riscv_gpio_output_enable(14); + + while(1) { + // Turn on GPIO + ulp_riscv_gpio_output_level(14, 1); + gpio_level = true; + // Delay + ulp_riscv_delay_cycles(1000 * 1000); + // Turn off GPIO + ulp_riscv_gpio_output_level(14, 0); + gpio_level = false; + // Delay + ulp_riscv_delay_cycles(1000 * 1000); + } + + /* ulp_riscv_halt() is called automatically when main exits */ + return 0; +} \ No newline at end of file