8000 ports/esp32/ulp: Initial draft PR for RISCV ULP support S2/S3. by ThinkTransit · Pull Request #11572 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

ports/esp32/ulp: Initial draft PR for RISCV ULP support S2/S3. #11572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 72 additions & 8 deletions docs/library/esp32.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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 <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#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
Expand Down
4 changes: 4 additions & 0 deletions ports/esp32/boards/GENERIC_S3/sdkconfig.board
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions ports/esp32/boards/sdkconfig.ulp_riscv
Original file line number Diff line number Diff line change
@@ -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
56 changes: 55 additions & 1 deletion ports/esp32/esp32_ulp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 6D40 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));
Expand All @@ -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) },
Expand All @@ -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);

Expand Down
27 changes: 25 additions & 2 deletions ports/esp32/main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand All @@ -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()
92 changes: 92 additions & 0 deletions ports/esp32/make-esp32ulpconst.py
Original file line number Diff line number Diff line change
@@ -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()
43 changes: 43 additions & 0 deletions ports/esp32/ulp_riscv/main.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#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;
}
0