diff --git a/microros_ws/extra_packages/micropython/CMakeLists.txt b/microros_ws/extra_packages/micropython/CMakeLists.txt new file mode 100644 index 0000000..6349ae6 --- /dev/null +++ b/microros_ws/extra_packages/micropython/CMakeLists.txt @@ -0,0 +1,268 @@ +message("Begining of MP CMake") + +# Set location of base MicroPython directory. +if(NOT MICROPY_DIR) + get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../../libs/micropython ABSOLUTE) +endif() + +get_filename_component(MICROPY_PORT_DIR ${PROJECT_DIR}/../../extra_packages/micropython/src ABSOLUTE) + +message ("\r\n Project Dir: ${PROJECT_DIR}") +message ("\r\n CMake Source Dir: ${CMAKE_SOURCE_DIR}") +message ("\r\n Micropython Dir: ${MICROPY_DIR}") +message ("\r\n Micropython Port Dir: ${MICROPY_PORT_DIR}") + +# Set the board if it's not already set. +if(NOT MICROPY_BOARD) + set(MICROPY_BOARD GENERIC) +endif() + +# Set the board directory and check that it exists. +if(NOT MICROPY_BOARD_DIR) + set(MICROPY_BOARD_DIR ${MICROPY_PORT_DIR}/boards/${MICROPY_BOARD}) +endif() + +message ("\r\n Micropython Board Dir: ${MICROPY_BOARD_DIR}") + +if(NOT EXISTS ${MICROPY_BOARD_DIR}/mpconfigboard.cmake) + message(FATAL_ERROR "Invalid MICROPY_BOARD specified: ${MICROPY_BOARD}") +endif() + +# Save the manifest file set from the cmake command line. +set(MICROPY_USER_FROZEN_MANIFEST ${MICROPY_FROZEN_MANIFEST}) + +# Include board config; this is expected to set SDKCONFIG_DEFAULTS (among other options). +include(${MICROPY_BOARD_DIR}/mpconfigboard.cmake) + +# Set the frozen manifest file. Note if MICROPY_FROZEN_MANIFEST is set from the cmake +# command line, then it will override the default and any manifest set by the board. +if (MICROPY_USER_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_USER_FROZEN_MANIFEST}) +elseif (NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) +endif() + +# Include core source components. +include(${MICROPY_DIR}/py/py.cmake) + + +if(NOT CMAKE_BUILD_EARLY_EXPANSION) + # Enable extmod components that will be configured by extmod.cmake. + # A board may also have enabled additional components. + set(MICROPY_PY_BTREE ON) + + include(${MICROPY_DIR}/py/usermod.cmake) + include(${MICROPY_DIR}/extmod/extmod.cmake) +endif() + + +set(MICROPY_QSTRDEFS_PORT + ${MICROPY_PORT_DIR}/qstrdefsport.h +) + +set(MICROPY_SOURCE_SHARED + ${MICROPY_DIR}/shared/readline/readline.c + ${MICROPY_DIR}/shared/netutils/netutils.c + ${MICROPY_DIR}/shared/timeutils/timeutils.c + ${MICROPY_DIR}/shared/runtime/interrupt_char.c + ${MICROPY_DIR}/shared/runtime/stdout_helpers.c + ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c + ${MICROPY_DIR}/shared/runtime/pyexec.c +) + +set(MICROPY_SOURCE_LIB + ${MICROPY_DIR}/lib/littlefs/lfs1.c + ${MICROPY_DIR}/lib/littlefs/lfs1_util.c + ${MICROPY_DIR}/lib/littlefs/lfs2.c + ${MICROPY_DIR}/lib/littlefs/lfs2_util.c + ${MICROPY_DIR}/lib/mbedtls_errors/mp_mbedtls_errors.c + ${MICROPY_DIR}/lib/oofatfs/ff.c + ${MICROPY_DIR}/lib/oofatfs/ffunicode.c +) +if(IDF_TARGET STREQUAL "esp32c3") + list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/runtime/gchelper_generic.c) +endif() + +set(MICROPY_SOURCE_DRIVERS + ${MICROPY_DIR}/drivers/bus/softspi.c + ${MICROPY_DIR}/drivers/dht/dht.c +) + +set(MICROPY_SOURCE_PORT + ${MICROPY_PORT_DIR}/main.c + ${MICROPY_PORT_DIR}/uart.c + ${MICROPY_PORT_DIR}/usb.c + ${MICROPY_PORT_DIR}/usb_serial_jtag.c + ${MICROPY_PORT_DIR}/gccollect.c + ${MICROPY_PORT_DIR}/mphalport.c + ${MICROPY_PORT_DIR}/fatfs_port.c + ${MICROPY_PORT_DIR}/help.c + ${MICROPY_PORT_DIR}/modutime.c + ${MICROPY_PORT_DIR}/machine_bitstream.c + ${MICROPY_PORT_DIR}/machine_timer.c + ${MICROPY_PORT_DIR}/machine_pin.c + ${MICROPY_PORT_DIR}/machine_touchpad.c + ${MICROPY_PORT_DIR}/machine_adc.c + ${MICROPY_PORT_DIR}/machine_adcblock.c + ${MICROPY_PORT_DIR}/machine_dac.c + ${MICROPY_PORT_DIR}/machine_i2c.c + ${MICROPY_PORT_DIR}/machine_i2s.c + ${MICROPY_PORT_DIR}/machine_uart.c + ${MICROPY_PORT_DIR}/modmachine.c + ${MICROPY_PORT_DIR}/modnetwork.c + ${MICROPY_PORT_DIR}/network_lan.c + ${MICROPY_PORT_DIR}/network_ppp.c + ${MICROPY_PORT_DIR}/network_wlan.c + ${MICROPY_PORT_DIR}/mpnimbleport.c + ${MICROPY_PORT_DIR}/modsocket.c + ${MICROPY_PORT_DIR}/modesp.c + ${MICROPY_PORT_DIR}/esp32_nvs.c + ${MICROPY_PORT_DIR}/esp32_partition.c + ${MICROPY_PORT_DIR}/esp32_rmt.c + ${MICROPY_PORT_DIR}/esp32_ulp.c + ${MICROPY_PORT_DIR}/modesp32.c + ${MICROPY_PORT_DIR}/machine_hw_spi.c + ${MICROPY_PORT_DIR}/machine_wdt.c + ${MICROPY_PORT_DIR}/mpthreadport.c + ${MICROPY_PORT_DIR}/machine_rtc.c + ${MICROPY_PORT_DIR}/machine_sdcard.c +) + +set(MICROPY_SOURCE_QSTR + ${MICROPY_SOURCE_PY} + ${MICROPY_SOURCE_EXTMOD} + ${MICROPY_SOURCE_USERMOD} + ${MICROPY_SOURCE_SHARED} + ${MICROPY_SOURCE_LIB} + ${MICROPY_SOURCE_PORT} + ${MICROPY_SOURCE_BOARD} +) + +set(IDF_COMPONENTS + app_update + bootloader_support + bt + driver + esp_adc_cal + esp_common + esp_eth + esp_event + esp_ringbuf + esp_rom + esp_wifi + freertos + heap + log + lwip + mbedtls + mdns + newlib + nvs_flash + sdmmc + soc + spi_flash + tcpip_adapter + ulp + vfs + xtensa +) + +if(IDF_VERSION_MINOR GREATER_EQUAL 1 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) + list(APPEND IDF_COMPONENTS esp_netif) +endif() + +if(IDF_VERSION_MINOR GREATER_EQUAL 2 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) + list(APPEND IDF_COMPONENTS esp_system) + list(APPEND IDF_COMPONENTS esp_timer) +endif() + +if(IDF_VERSION_MINOR GREATER_EQUAL 3 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) + list(APPEND IDF_COMPONENTS esp_hw_support) + list(APPEND IDF_COMPONENTS esp_pm) + list(APPEND IDF_COMPONENTS hal) +endif() + +if(IDF_TARGET STREQUAL "esp32") + list(APPEND IDF_COMPONENTS esp32) +elseif(IDF_TARGET STREQUAL "esp32c3") + list(APPEND IDF_COMPONENTS esp32c3) + list(APPEND IDF_COMPONENTS riscv) +elseif(IDF_TARGET STREQUAL "esp32s2") + list(APPEND IDF_COMPONENTS esp32s2) + list(APPEND IDF_COMPONENTS tinyusb) +elseif(IDF_TARGET STREQUAL "esp32s3") + list(APPEND IDF_COMPONENTS esp32s3) + list(APPEND IDF_COMPONENTS tinyusb) +endif() + +# Register the main IDF component. +idf_component_register( + SRCS + ${MICROPY_SOURCE_PY} + ${MICROPY_SOURCE_EXTMOD} + ${MICROPY_SOURCE_SHARED} + ${MICROPY_SOURCE_LIB} + ${MICROPY_SOURCE_DRIVERS} + ${MICROPY_SOURCE_PORT} + ${MICROPY_SOURCE_BOARD} + INCLUDE_DIRS + ${MICROPY_INC_CORE} + ${MICROPY_INC_USERMOD} + ${MICROPY_PORT_DIR} + ${MICROPY_BOARD_DIR} + ${CMAKE_BINARY_DIR} + REQUIRES + ${IDF_COMPONENTS} +) + +# Set the MicroPython target as the current (main) IDF component target. +set(MICROPY_TARGET ${COMPONENT_TARGET}) + +# Define mpy-cross flags, for use with frozen code. +set(MICROPY_CROSS_FLAGS -march=xtensawin) + +# Set compile options for this port. +target_compile_definitions(${MICROPY_TARGET} PUBLIC + ${MICROPY_DEF_CORE} + MICROPY_ESP_IDF_4=1 + MICROPY_VFS_FAT=1 + MICROPY_VFS_LFS2=1 + FFCONF_H=\"${MICROPY_OOFATFS_DIR}/ffconf.h\" + LFS1_NO_MALLOC LFS1_NO_DEBUG LFS1_NO_WARN LFS1_NO_ERROR LFS1_NO_ASSERT + LFS2_NO_MALLOC LFS2_NO_DEBUG LFS2_NO_WARN LFS2_NO_ERROR LFS2_NO_ASSERT +) + +# Disable some warnings to keep the build output clean. +target_compile_options(${MICROPY_TARGET} PUBLIC + -Wno-clobbered + -Wno-deprecated-declarations + -Wno-missing-field-initializers +) + +# Additional include directories needed for private NimBLE headers. +target_include_directories(${MICROPY_TARGET} PUBLIC + ${IDF_PATH}/components/bt/host/nimble/nimble +) + +# Add additional extmod and usermod components. +target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) +target_link_libraries(${MICROPY_TARGET} usermod) + +# Collect all of the include directories and compile definitions for the IDF components. +foreach(comp ${IDF_COMPONENTS}) + micropy_gather_target_properties(__idf_${comp}) +endforeach() + +if(IDF_VERSION_MINOR GREATER_EQUAL 2 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) + # These paths cannot currently be found by the IDF_COMPONENTS search loop above, + # so add them explicitly. + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/soc/soc/${IDF_TARGET}/include) + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/soc/soc/include) + if(IDF_VERSION_MINOR GREATER_EQUAL 3) + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/tinyusb/additions/include) + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/tinyusb/tinyusb/src) + endif() +endif() + +# Include the main MicroPython cmake rules. +include(${MICROPY_DIR}/py/mkrules.cmake) diff --git a/microros_ws/extra_packages/micropython/old.CMakeLists.txt b/microros_ws/extra_packages/micropython/old.CMakeLists.txt new file mode 100644 index 0000000..7052508 --- /dev/null +++ b/microros_ws/extra_packages/micropython/old.CMakeLists.txt @@ -0,0 +1,262 @@ +message("Begining of MP CMake") + +# Set location of base MicroPython directory. +if(NOT MICROPY_DIR) + get_filename_component(MICROPY_DIR ${PROJECT_DIR}/../../libs/micropython ABSOLUTE) +endif() + +get_filename_component(MICROPY_PORT_DIR ${PROJECT_DIR}/../../extra_packages/micropython/src ABSOLUTE) + +message ("\r\n Project Dir: ${PROJECT_DIR}") +message ("\r\n CMake Source Dir: ${CMAKE_SOURCE_DIR}") +message ("\r\n Micropython Dir: ${MICROPY_DIR}") +message ("\r\n Micropython Port Dir: ${MICROPY_PORT_DIR}") + + +set(IDF_COMPONENTS + app_update + bootloader_support + bt + driver + esp_adc_cal + esp_common + esp_eth + esp_event + esp_ringbuf + esp_rom + esp_wifi + freertos + heap + log + lwip + mbedtls + mdns + newlib + nvs_flash + sdmmc + soc + spi_flash + tcpip_adapter + ulp + vfs + xtensa +) + +# Set the board if it's not already set. +if(NOT MICROPY_BOARD) + set(MICROPY_BOARD GENERIC) +endif() + +# Set the board directory and check that it exists. +if(NOT MICROPY_BOARD_DIR) + set(MICROPY_BOARD_DIR ${MICROPY_PORT_DIR}/boards/${MICROPY_BOARD}) +endif() + +message ("\r\n Micropython Board Dir: ${MICROPY_BOARD_DIR}") + +if(NOT EXISTS ${MICROPY_BOARD_DIR}/mpconfigboard.cmake) + message(FATAL_ERROR "Invalid MICROPY_BOARD specified: ${MICROPY_BOARD}") +endif() + +# Define the output sdkconfig so it goes in the build directory. +#set(SDKCONFIG ${CMAKE_BINARY_DIR}/sdkconfig) + +# Save the manifest file set from the cmake command line. +set(MICROPY_USER_FROZEN_MANIFEST ${MICROPY_FROZEN_MANIFEST}) + +# Include board config; this is expected to set SDKCONFIG_DEFAULTS (among other options). +include(${MICROPY_BOARD_DIR}/mpconfigboard.cmake) + +# Set the frozen manifest file. Note if MICROPY_FROZEN_MANIFEST is set from the cmake +# command line, then it will override the default and any manifest set by the board. +if (MICROPY_USER_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_USER_FROZEN_MANIFEST}) +elseif (NOT MICROPY_FROZEN_MANIFEST) + set(MICROPY_FROZEN_MANIFEST ${MICROPY_PORT_DIR}/boards/manifest.py) +endif() + +#[[ +list(APPEND MICROPY_CPP_INC_EXTRA + ${MICROPY_DIR} + ${MICROPY_PORT_DIR} + ${CMAKE_BINARY_DIR} +) +]] + +list(APPEND MICROPY_CPP_DEF + NO_QSTR +) + +# Include core source components. +include(${MICROPY_DIR}/py/py.cmake) + +#f(NOT CMAKE_BUILD_EARLY_EXPANSION) + # Enable extmod components that will be configured by extmod.cmake. + # A board may also have enabled additional components. +# set(MICROPY_PY_BTREE ON) + +# include(${MICROPY_DIR}/py/usermod.cmake) +# include(${MICROPY_DIR}/extmod/extmod.cmake) +#endif() + +set(MICROPY_QSTRDEFS_PORT + ${MICROPY_PORT_DIR}/qstrdefsport.h +) + +set(MICROPY_SOURCE_SHARED + ${MICROPY_DIR}/shared/readline/readline.c + ${MICROPY_DIR}/shared/netutils/netutils.c + ${MICROPY_DIR}/shared/timeutils/timeutils.c + ${MICROPY_DIR}/shared/runtime/interrupt_char.c + ${MICROPY_DIR}/shared/runtime/stdout_helpers.c + ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c + ${MICROPY_DIR}/shared/runtime/pyexec.c + + ${MICROPY_DIR}/extmod/modwebrepl.c + ${MICROPY_PORT_DIR}/mpmain.c + +) + +set(MICROPY_SOURCE_LIB +# ${MICROPY_DIR}/lib/littlefs/lfs1.c +# ${MICROPY_DIR}/lib/littlefs/lfs1_util.c +# ${MICROPY_DIR}/lib/littlefs/lfs2.c +# ${MICROPY_DIR}/lib/littlefs/lfs2_util.c +# ${MICROPY_DIR}/lib/mbedtls_errors/mp_mbedtls_errors.c +# ${MICROPY_DIR}/lib/oofatfs/ff.c +# ${MICROPY_DIR}/lib/oofatfs/ffunicode.c +) +if(IDF_TARGET STREQUAL "esp32c3") + list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/shared/runtime/gchelper_generic.c) +endif() + +set(MICROPY_SOURCE_DRIVERS +# ${MICROPY_DIR}/drivers/bus/softspi.c +# ${MICROPY_DIR}/drivers/dht/dht.c + ${MICROPY_PORT_DIR}/uart_core.c + ${MICROPY_PORT_DIR}/mpthreadport.c + +) + +set(MICROPY_SOURCE_QSTR + ${MICROPY_SOURCE_PY} + ${MICROPY_SOURCE_EXTMOD} + ${MICROPY_SOURCE_USERMOD} + ${MICROPY_SOURCE_SHARED} + ${MICROPY_SOURCE_LIB} + ${MICROPY_SOURCE_BOARD} +) + +if(IDF_VERSION_MINOR GREATER_EQUAL 1 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) + list(APPEND IDF_COMPONENTS esp_netif) +endif() + +if(IDF_VERSION_MINOR GREATER_EQUAL 2 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) + list(APPEND IDF_COMPONENTS esp_system) + list(APPEND IDF_COMPONENTS esp_timer) +endif() + +if(IDF_VERSION_MINOR GREATER_EQUAL 3 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) + list(APPEND IDF_COMPONENTS esp_hw_support) + list(APPEND IDF_COMPONENTS esp_pm) + list(APPEND IDF_COMPONENTS hal) +endif() + +if(IDF_TARGET STREQUAL "esp32") + list(APPEND IDF_COMPONENTS esp32) +elseif(IDF_TARGET STREQUAL "esp32c3") + list(APPEND IDF_COMPONENTS esp32c3) + list(APPEND IDF_COMPONENTS riscv) +elseif(IDF_TARGET STREQUAL "esp32s2") + list(APPEND IDF_COMPONENTS esp32s2) + list(APPEND IDF_COMPONENTS tinyusb) +elseif(IDF_TARGET STREQUAL "esp32s3") + list(APPEND IDF_COMPONENTS esp32s3) + list(APPEND IDF_COMPONENTS tinyusb) +endif() + +# Register the main IDF component. + +idf_component_register( + SRCS + ${MICROPY_SOURCE_PY} + ${MICROPY_SOURCE_EXTMOD} + ${MICROPY_SOURCE_SHARED} + ${MICROPY_SOURCE_LIB} + ${MICROPY_SOURCE_DRIVERS} + ${MICROPY_SOURCE_PORT} + ${MICROPY_SOURCE_BOARD} + INCLUDE_DIRS + ${MICROPY_INC_CORE} + ${MICROPY_INC_USERMOD} + ${MICROPY_PORT_DIR} + ${MICROPY_BOARD_DIR} + ${CMAKE_BINARY_DIR} + REQUIRES + ${IDF_COMPONENTS} + nvs_flash + freertos +) + +INCLUDE_DIRECTORIES(AFTER + ${MICROPY_DIR} + ${MICROPY_PORT_DIR} + ${CMAKE_BINARY_DIR} +) + + +# Set the MicroPython target as the current (main) IDF component target. +set(MICROPY_TARGET ${COMPONENT_TARGET}) + +# Define mpy-cross flags, for use with frozen code. +set(MICROPY_CROSS_FLAGS -march=xtensawin) + +# Set compile options for this port. +target_compile_definitions(${MICROPY_TARGET} PUBLIC + ${MICROPY_DEF_CORE} + MICROPY_ESP_IDF_4=1 + MICROPY_VFS_FAT=1 + MICROPY_VFS_LFS2=1 + FFCONF_H=\"${MICROPY_OOFATFS_DIR}/ffconf.h\" + LFS1_NO_MALLOC LFS1_NO_DEBUG LFS1_NO_WARN LFS1_NO_ERROR LFS1_NO_ASSERT + LFS2_NO_MALLOC LFS2_NO_DEBUG LFS2_NO_WARN LFS2_NO_ERROR LFS2_NO_ASSERT +) + +# Disable some warnings to keep the build output clean. +target_compile_options(${MICROPY_TARGET} PUBLIC + -Wno-clobbered + -Wno-deprecated-declarations + -Wno-missing-field-initializers +) + + +# Additional include directories needed for private NimBLE headers. +#target_include_directories(${MICROPY_TARGET} PUBLIC +# ${IDF_PATH}/components/bt/host/nimble/nimble +#) + +# Add additional extmod and usermod components. +#target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) +#target_link_libraries(${MICROPY_TARGET} usermod) + +# Collect all of the include directories and compile definitions for the IDF components. +foreach(comp ${IDF_COMPONENTS}) + micropy_gather_target_properties(__idf_${comp}) +endforeach() + +if(IDF_VERSION_MINOR GREATER_EQUAL 2 OR IDF_VERSION_MAJOR GREATER_EQUAL 5) + # These paths cannot currently be found by the IDF_COMPONENTS search loop above, + # so add them explicitly. + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/soc/soc/${IDF_TARGET}/include) + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/soc/soc/include) + if(IDF_VERSION_MINOR GREATER_EQUAL 3) + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/tinyusb/additions/include) + list(APPEND MICROPY_CPP_INC_EXTRA ${IDF_PATH}/components/tinyusb/tinyusb/src) + endif() +endif() + + +# Include the main MicroPython cmake rules. +include(${MICROPY_DIR}/py/mkrules.cmake) + +message("End of MP CMake") \ No newline at end of file diff --git a/microros_ws/extra_packages/micropython/src/Makefile b/microros_ws/extra_packages/micropython/src/Makefile new file mode 100644 index 0000000..e43cad7 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/Makefile @@ -0,0 +1,61 @@ +# Makefile for MicroPython on ESP32. +# +# This is a simple, convenience wrapper around idf.py (which uses cmake). + +# Select the board to build for, defaulting to GENERIC. +BOARD ?= GENERIC + +# If the build directory is not given, make it reflect the board name. +BUILD ?= build-$(BOARD) + +# Device serial settings. +PORT ?= /dev/ttyUSB0 +BAUD ?= 460800 + +PYTHON ?= python3 + +# Would be good to use cmake to discover submodules (see how rp2/Makefile does +# it), but on ESP32 the same trick doesn't work because "idf.py build" fails +# on berkeley-db dependency before printing out the submodule list. +# For now just force the submodule dependencies here. +GIT_SUBMODULES += lib/berkeley-db-1.xx lib/micropython-lib + +.PHONY: all clean deploy erase submodules FORCE + +CMAKE_ARGS = + +ifdef USER_C_MODULES + CMAKE_ARGS += -DUSER_C_MODULES=${USER_C_MODULES} +endif + +IDFPY_FLAGS += -D MICROPY_BOARD=$(BOARD) -B $(BUILD) $(CMAKE_ARGS) + +ifdef FROZEN_MANIFEST + IDFPY_FLAGS += -D MICROPY_FROZEN_MANIFEST=$(FROZEN_MANIFEST) +endif + +HELP_BUILD_ERROR ?= "See \033[1;31mhttps://github.com/micropython/micropython/wiki/Build-Troubleshooting\033[0m" + +all: + idf.py $(IDFPY_FLAGS) build || (echo -e $(HELP_BUILD_ERROR); false) + @$(PYTHON) makeimg.py \ + $(BUILD)/sdkconfig \ + $(BUILD)/bootloader/bootloader.bin \ + $(BUILD)/partition_table/partition-table.bin \ + $(BUILD)/micropython.bin \ + $(BUILD)/firmware.bin \ + $(BUILD)/micropython.uf2 + +$(BUILD)/bootloader/bootloader.bin $(BUILD)/partition_table/partition-table.bin $(BUILD)/micropython.bin: FORCE + +clean: + idf.py $(IDFPY_FLAGS) fullclean + +deploy: + idf.py $(IDFPY_FLAGS) -p $(PORT) -b $(BAUD) flash + +erase: + idf.py $(IDFPY_FLAGS) -p $(PORT) -b $(BAUD) erase_flash + +submodules: + $(MAKE) -f ../../py/mkrules.mk GIT_SUBMODULES="$(GIT_SUBMODULES)" submodules diff --git a/microros_ws/extra_packages/micropython/src/README.md b/microros_ws/extra_packages/micropython/src/README.md new file mode 100644 index 0000000..c37213b --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/README.md @@ -0,0 +1,231 @@ +MicroPython port to the ESP32 +============================= + +This is a port of MicroPython to the Espressif ESP32 series of +microcontrollers. It uses the ESP-IDF framework and MicroPython runs as +a task under FreeRTOS. + +Supported features include: +- REPL (Python prompt) over UART0. +- 16k stack for the MicroPython task and approximately 100k Python heap. +- Many of MicroPython's features are enabled: unicode, arbitrary-precision + integers, single-precision floats, complex numbers, frozen bytecode, as + well as many of the internal modules. +- Internal filesystem using the flash (currently 2M in size). +- The machine module with GPIO, UART, SPI, software I2C, ADC, DAC, PWM, + TouchPad, WDT and Timer. +- The network module with WLAN (WiFi) support. +- Bluetooth low-energy (BLE) support via the bluetooth module. + +Initial development of this ESP32 port was sponsored in part by Microbric Pty Ltd. + +Setting up ESP-IDF and the build environment +-------------------------------------------- + +MicroPython on ESP32 requires the Espressif IDF version 4 (IoT development +framework, aka SDK). The ESP-IDF includes the libraries and RTOS needed to +manage the ESP32 microcontroller, as well as a way to manage the required +build environment and toolchains needed to build the firmware. + +The ESP-IDF changes quickly and MicroPython only supports certain versions. +Currently MicroPython supports v4.0.2, v4.1.1, v4.2.2, v4.3.2 and v4.4, +although other IDF v4 versions may also work. + +To install the ESP-IDF the full instructions can be found at the +[Espressif Getting Started guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#installation-step-by-step). + +If you are on a Windows machine then the [Windows Subsystem for +Linux](https://msdn.microsoft.com/en-au/commandline/wsl/install_guide) is the +most efficient way to install the ESP32 toolchain and build the project. If +you use WSL then follow the Linux instructions rather than the Windows +instructions. + +The Espressif instructions will guide you through using the `install.sh` +(or `install.bat`) script to download the toolchain and set up your environment. +The steps to take are summarised below. + +To check out a copy of the IDF use git clone: + +```bash +$ git clone -b v4.0.2 --recursive https://github.com/espressif/esp-idf.git +``` + +You can replace `v4.0.2` with `v4.2.2` or `v4.4` or any other supported version. +(You don't need a full recursive clone; see the `ci_esp32_setup` function in +`tools/ci.sh` in this repository for more detailed set-up commands.) + +If you already have a copy of the IDF then checkout a version compatible with +MicroPython and update the submodules using: + +```bash +$ cd esp-idf +$ git checkout v4.2 +$ git submodule update --init --recursive +``` + +After you've cloned and checked out the IDF to the correct version, run the +`install.sh` script: + +```bash +$ cd esp-idf +$ ./install.sh # (or install.bat on Windows) +$ source export.sh # (or export.bat on Windows) +``` + +The `install.sh` step only needs to be done once. You will need to source +`export.sh` for every new session. + +**Note:** If you are building MicroPython for the ESP32-S2, ESP32-C3 or ESP32-S3, +please ensure you are using the following required IDF versions: +- ESP32-S3 currently requires `v4.4` or later. +- ESP32-S2 and ESP32-C3 require `v4.3.1` or later. + +Building the firmware +--------------------- + +The MicroPython cross-compiler must be built to pre-compile some of the +built-in scripts to bytecode. This can be done by (from the root of +this repository): + +```bash +$ make -C mpy-cross +``` + +Then to build MicroPython for the ESP32 run: + +```bash +$ cd ports/esp32 +$ make submodules +$ make +``` + +This will produce a combined `firmware.bin` image in the `build-GENERIC/` +subdirectory (this firmware image is made up of: bootloader.bin, partitions.bin +and micropython.bin). + +To flash the firmware you must have your ESP32 module in the bootloader +mode and connected to a serial port on your PC. Refer to the documentation +for your particular ESP32 module for how to do this. +You will also need to have user permissions to access the `/dev/ttyUSB0` device. +On Linux, you can enable this by adding your user to the `dialout` group, and +rebooting or logging out and in again. (Note: on some distributions this may +be the `uucp` group, run `ls -la /dev/ttyUSB0` to check.) + +```bash +$ sudo adduser dialout +``` + +If you are installing MicroPython to your module for the first time, or +after installing any other firmware, you should first erase the flash +completely: + +```bash +$ make erase +``` + +To flash the MicroPython firmware to your ESP32 use: + +```bash +$ make deploy +``` + +The default ESP32 board build by the above commands is the `GENERIC` one, which +should work on most ESP32 modules. You can specify a different board by passing +`BOARD=` to the make commands, for example: + +```bash +$ make BOARD=GENERIC_SPIRAM +``` + +Note: the above "make" commands are thin wrappers for the underlying `idf.py` +build tool that is part of the ESP-IDF. You can instead use `idf.py` directly, +for example: + +```bash +$ idf.py build +$ idf.py -D MICROPY_BOARD=GENERIC_SPIRAM build +$ idf.py flash +``` + +Getting a Python prompt on the device +------------------------------------- + +You can get a prompt via the serial port, via UART0, which is the same UART +that is used for programming the firmware. The baudrate for the REPL is +115200 and you can use a command such as: + +```bash +$ picocom -b 115200 /dev/ttyUSB0 +``` + +or + +```bash +$ miniterm.py /dev/ttyUSB0 115200 +``` + +You can also use `idf.py monitor`. + +Configuring the WiFi and using the board +---------------------------------------- + +The ESP32 port is designed to be (almost) equivalent to the ESP8266 in +terms of the modules and user-facing API. There are some small differences, +notably that the ESP32 does not automatically connect to the last access +point when booting up. But for the most part the documentation and tutorials +for the ESP8266 should apply to the ESP32 (at least for the components that +are implemented). + +See http://docs.micropython.org/en/latest/esp8266/esp8266/quickref.html for +a quick reference, and http://docs.micropython.org/en/latest/esp8266/esp8266/tutorial/intro.html +for a tutorial. + +The following function can be used to connect to a WiFi access point (you can +either pass in your own SSID and password, or change the defaults so you can +quickly call `wlan_connect()` and it just works): +```python +def wlan_connect(ssid='MYSSID', password='MYPASS'): + import network + wlan = network.WLAN(network.STA_IF) + if not wlan.active() or not wlan.isconnected(): + wlan.active(True) + print('connecting to:', ssid) + wlan.connect(ssid, password) + while not wlan.isconnected(): + pass + print('network config:', wlan.ifconfig()) +``` + +Note that some boards require you to configure the WiFi antenna before using +the WiFi. On Pycom boards like the LoPy and WiPy 2.0 you need to execute the +following code to select the internal antenna (best to put this line in your +boot.py file): +```python +import machine +antenna = machine.Pin(16, machine.Pin.OUT, value=0) +``` + +Defining a custom ESP32 board +----------------------------- + +The default ESP-IDF configuration settings are provided by the `GENERIC` +board definition in the directory `boards/GENERIC`. For a custom configuration +you can define your own board directory. Start a new board configuration by +copying an existing one (like `GENERIC`) and modifying it to suit your board. + +MicroPython specific configuration values are defined in the board-specific +`mpconfigboard.h` file, which is included by `mpconfigport.h`. Additional +settings are put in `mpconfigboard.cmake`, including a list of `sdkconfig` +files that configure ESP-IDF settings. Some standard `sdkconfig` files are +provided in the `boards/` directory, like `boards/sdkconfig.ble`. You can +also define custom ones in your board directory. + +See existing board definitions for further examples of configuration. + +Configuration +Troubleshooting +--------------- + +* Continuous reboots after programming: Ensure `CONFIG_ESPTOOLPY_FLASHMODE` is + correct for your board (e.g. ESP-WROOM-32 should be DIO). Then perform a + `make clean`, rebuild, redeploy. diff --git a/microros_ws/extra_packages/micropython/src/README.ulp.md b/microros_ws/extra_packages/micropython/src/README.ulp.md new file mode 100644 index 0000000..cbc5771 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/README.ulp.md @@ -0,0 +1,126 @@ +# ULP + +To compile binarys for the ulp you need the ulp toolkit. Download it from https://github.com/espressif/binutils-esp32ulp/wiki#downloads +Then extract it, then add ```esp32ulp-elf-binutils/bin``` to your PATH + +## Example Makefile + +```make +ULP_S_SOURCES := main.S +ULP_APP_NAME := test +ULP_LD_SCRIPT := esp32.ulp.ld + +SRC_PATH := src +BUILD_PATH := build + +include $(ESPIDF)/components/ulp/Makefile.projbuild + +ULP_ELF := $(ULP_APP_NAME).elf +ULP_MAP := $(ULP_ELF:.elf=.map) +ULP_SYM := $(ULP_ELF:.elf=.sym) +ULP_BIN := $(ULP_ELF:.elf=.bin) +ULP_EXPORTS_LD := $(ULP_ELF:.elf=.ld) +ULP_EXPORTS_HEADER := $(ULP_ELF:.elf=.h) + +ULP_OBJECTS := $(notdir $(ULP_S_SOURCES:.S=.ulp.o)) +ULP_DEP := $(notdir $(ULP_S_SOURCES:.S=.ulp.d)) $(ULP_LD_SCRIPT:.ld=.d) +ULP_PREPROCESSED := $(notdir $(ULP_S_SOURCES:.S=.ulp.pS)) +ULP_LISTINGS := $(notdir $(ULP_S_SOURCES:.S=.ulp.lst)) + +.PHONY: all clean + +all: $(BUILD_PATH) $(BUILD_PATH)/$(ULP_BIN) + +clean: + rm -rf $(BUILD_PATH) + +$(BUILD_PATH): + mkdir $@ + +# Generate preprocessed linker file. +$(BUILD_PATH)/$(ULP_APP_NAME).ld: $(SRC_PATH)/$(ULP_LD_SCRIPT) + cpp -P $< -o $@ + +# Generate preprocessed assembly files. +# To inspect these preprocessed files, add a ".PRECIOUS: %.ulp.pS" rule. +$(BUILD_PATH)/%.ulp.pS: $(SRC_PATH)/%.S + cpp $< -o $@ + +# Compiled preprocessed files into object files. +$(BUILD_PATH)/%.ulp.o: $(BUILD_PATH)/%.ulp.pS + $(ULP_AS) -al=$(patsubst %.ulp.o,%.ulp.lst,$@) -o $@ $< + +# Link object files and generate map file +$(BUILD_PATH)/$(ULP_ELF): $(BUILD_PATH)/$(ULP_OBJECTS) $(BUILD_PATH)/$(ULP_APP_NAME).ld + $(ULP_LD) -o $@ -A elf32-esp32ulp -Map=$(BUILD_PATH)/$(ULP_MAP) -T $(BUILD_PATH)/$(ULP_APP_NAME).ld $< + +# Dump the list of global symbols in a convenient format. +$(ULP_SYM): $(ULP_ELF) + $(ULP_NM) -g -f posix $< > $@ + +# Dump the binary for inclusion into the project +$(BUILD_PATH)/$(ULP_BIN): $(BUILD_PATH)/$(ULP_ELF) + $(ULP_OBJCOPY) -O binary $< $@ +``` + +## Example linker script for the ulp +``` +#define ULP_BIN_MAGIC 0x00706c75 +#define HEADER_SIZE 12 +#define CONFIG_ULP_COPROC_RESERVE_MEM 4096 + +MEMORY +{ + ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM +} + +SECTIONS +{ + .text : AT(HEADER_SIZE) + { + *(.text) + } >ram + .data : + { + . = ALIGN(4); + *(.data) + } >ram + .bss : + { + . = ALIGN(4); + *(.bss) + } >ram + + .header : AT(0) + { + LONG(ULP_BIN_MAGIC) + SHORT(LOADADDR(.text)) + SHORT(SIZEOF(.text)) + SHORT(SIZEOF(.data)) + SHORT(SIZEOF(.bss)) + } +} +``` + +## Example ulp code +```asm +move R3, 99 +move R0, 10 + +# mem[R0+0] = R3 +st R3, R0, 0 + +HALT +``` + +## Example python code using the ulp +```python +import esp32 +import time + +u = esp32.ULP() +with open('test.bin', 'rb') as f: + b = f.read() +u.load_binary(0,b) +u.run(0) +``` diff --git a/microros_ws/extra_packages/micropython/src/boards/GENERIC/board.json b/microros_ws/extra_packages/micropython/src/boards/GENERIC/board.json new file mode 100644 index 0000000..a52d580 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/GENERIC/board.json @@ -0,0 +1,22 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "BLE", + "WiFi" + ], + "id": "esp32", + "images": [ + "esp32_devkitc.jpg" + ], + "mcu": "esp32", + "product": "ESP32", + "thumbnail": "", + "url": "https://www.espressif.com/en/products/modules", + "variants": { + "idf3": "Compiled with IDF 3.x" + }, + "vendor": "Espressif" +} diff --git a/microros_ws/extra_packages/micropython/src/boards/GENERIC/board.md b/microros_ws/extra_packages/micropython/src/boards/GENERIC/board.md new file mode 100644 index 0000000..576ea80 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/GENERIC/board.md @@ -0,0 +1,3 @@ +The following files are daily firmware for ESP32-based boards without external SPIRAM. + +This firmware is compiled using ESP-IDF v4.x. Some older releases are also provided that are compiled with ESP-IDF v3.x. diff --git a/microros_ws/extra_packages/micropython/src/boards/GENERIC/mpconfigboard.cmake b/microros_ws/extra_packages/micropython/src/boards/GENERIC/mpconfigboard.cmake new file mode 100644 index 0000000..b49945a --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/GENERIC/mpconfigboard.cmake @@ -0,0 +1,4 @@ +set(SDKCONFIG_DEFAULTS + boards/sdkconfig.base +# boards/sdkconfig.ble +) diff --git a/microros_ws/extra_packages/micropython/src/boards/GENERIC/mpconfigboard.h b/microros_ws/extra_packages/micropython/src/boards/GENERIC/mpconfigboard.h new file mode 100644 index 0000000..644807f --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/GENERIC/mpconfigboard.h @@ -0,0 +1,2 @@ +#define MICROPY_HW_BOARD_NAME "ESP32 module" +#define MICROPY_HW_MCU_NAME "ESP32" diff --git a/microros_ws/extra_packages/micropython/src/boards/deploy.md b/microros_ws/extra_packages/micropython/src/boards/deploy.md new file mode 100644 index 0000000..64e683e --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/deploy.md @@ -0,0 +1,14 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +```bash +esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash +``` + +From then on program the firmware starting at address 0x1000: + +```bash +esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-20190125-v1.10.bin +``` diff --git a/microros_ws/extra_packages/micropython/src/boards/deploy_c3.md b/microros_ws/extra_packages/micropython/src/boards/deploy_c3.md new file mode 100644 index 0000000..016ba7c --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/deploy_c3.md @@ -0,0 +1,14 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +```bash +esptool.py --chip esp32c3 --port /dev/ttyUSB0 erase_flash +``` + +From then on program the firmware starting at address 0x0: + +```bash +esptool.py --chip esp32c3 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x0 esp32c3-20220117-v1.18.bin +``` diff --git a/microros_ws/extra_packages/micropython/src/boards/deploy_s2.md b/microros_ws/extra_packages/micropython/src/boards/deploy_s2.md new file mode 100644 index 0000000..5b30570 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/deploy_s2.md @@ -0,0 +1,14 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 erase_flash +``` + +From then on program the firmware starting at address 0x1000: + +```bash +esptool.py --chip esp32s2 --port /dev/ttyACM0 write_flash -z 0x1000 board-20210902-v1.17.bin +``` diff --git a/microros_ws/extra_packages/micropython/src/boards/deploy_s3.md b/microros_ws/extra_packages/micropython/src/boards/deploy_s3.md new file mode 100644 index 0000000..7f564a9 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/deploy_s3.md @@ -0,0 +1,14 @@ +Program your board using the esptool.py program, found [here](https://github.com/espressif/esptool). + +If you are putting MicroPython on your board for the first time then you should +first erase the entire flash using: + +```bash +esptool.py --chip esp32s3 --port /dev/ttyACM0 erase_flash +``` + +From then on program the firmware starting at address 0: + +```bash +esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash -z 0 board-20210902-v1.17.bin +``` diff --git a/microros_ws/extra_packages/micropython/src/boards/manifest.py b/microros_ws/extra_packages/micropython/src/boards/manifest.py new file mode 100644 index 0000000..3f6c8cf --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/manifest.py @@ -0,0 +1,15 @@ +freeze("$(PORT_DIR)/modules") +include("$(MPY_DIR)/extmod/uasyncio") + +# Require some micropython-lib modules. +require("dht") +require("ds18x20") +require("mip") +require("neopixel") +require("ntptime") +require("onewire") +require("umqtt.robust") +require("umqtt.simple") +require("upysh") +require("urequests") +require("webrepl") diff --git a/microros_ws/extra_packages/micropython/src/boards/manifest_test.py b/microros_ws/extra_packages/micropython/src/boards/manifest_test.py new file mode 100644 index 0000000..87c5b7b --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/manifest_test.py @@ -0,0 +1,10 @@ +# Manifest for testing the build. + +# Include standard manifest. +include("manifest.py") + +# Test freezing @micropython.native code. +freeze("$(MPY_DIR)/tests/micropython", "native_misc.py") + +# Test freezing @micropython.viper code. +freeze("$(MPY_DIR)/tests/micropython", "viper_misc.py") diff --git a/microros_ws/extra_packages/micropython/src/boards/sdkconfig.240mhz b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.240mhz new file mode 100644 index 0000000..e368840 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.240mhz @@ -0,0 +1,5 @@ +# MicroPython on ESP32, ESP IDF configuration with 240MHz CPU +CONFIG_ESP32_DEFAULT_CPU_FREQ_80= +CONFIG_ESP32_DEFAULT_CPU_FREQ_160= +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 diff --git a/microros_ws/extra_packages/micropython/src/boards/sdkconfig.base b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.base new file mode 100644 index 0000000..6b891aa --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.base @@ -0,0 +1,66 @@ +# MicroPython on ESP32, ESP IDF configuration +# The following options override the defaults + +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 + +# Compiler options: use -O2 and disable assertions to improve performance +# (CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is for IDF 4.0.2) +CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y + +# Application manager +CONFIG_APP_EXCLUDE_PROJECT_VER_VAR=y +CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y + +# Bootloader config +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y + +# Change default log level to "ERROR" (instead of "INFO") +CONFIG_LOG_DEFAULT_LEVEL_INFO=n +CONFIG_LOG_DEFAULT_LEVEL_ERROR=y +CONFIG_LOG_DEFAULT_LEVEL=1 + +# ESP32-specific +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n +CONFIG_ESP32_XTAL_FREQ_AUTO=y + +# Power Management +CONFIG_PM_ENABLE=y + +# Memory protection +# This is required to allow allocating IRAM +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n + +# FreeRTOS +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2 +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y +CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y + +# UDP +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_PAP_SUPPORT=y +CONFIG_LWIP_PPP_CHAP_SUPPORT=y + +# SSL +# Use 4kiB output buffer instead of default 16kiB (because IDF heap is fragmented in 4.0) +CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y + +# ULP coprocessor support +CONFIG_ESP32_ULP_COPROC_ENABLED=y +CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=2040 + +# For cmake build +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" + +# To reduce iRAM usage +CONFIG_ESP32_WIFI_IRAM_OPT=n +CONFIG_ESP32_WIFI_RX_IRAM_OPT=n + +# ADC calibration +CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y +CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y +CONFIG_ADC_CAL_LUT_ENABLE=y diff --git a/microros_ws/extra_packages/micropython/src/boards/sdkconfig.ble b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.ble new file mode 100644 index 0000000..08d5e48 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.ble @@ -0,0 +1,9 @@ +# Note this requires building with IDF 4.x +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY= +CONFIG_BTDM_CTRL_MODE_BTDM= + +CONFIG_BT_NIMBLE_ENABLED=y + +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4 diff --git a/microros_ws/extra_packages/micropython/src/boards/sdkconfig.nimble_core0 b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.nimble_core0 new file mode 100644 index 0000000..cacaff1 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.nimble_core0 @@ -0,0 +1,6 @@ +# For IDF <4.2, we need NimBLE on core 0, and for synchronisation +# with the ringbuffer and scheduler MP needs to be on the same core. +# See https://github.com/micropython/micropython/issues/5489 +CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y +CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=n +CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 diff --git a/microros_ws/extra_packages/micropython/src/boards/sdkconfig.nimble_core1 b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.nimble_core1 new file mode 100644 index 0000000..33653cc --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.nimble_core1 @@ -0,0 +1,6 @@ +# For IDF >=4.2, we are able to put NimBLE on core 1, and for synchronisation +# with the ringbuffer and scheduler MP needs to be on the same core. +# MP on core 1 prevents interference with WiFi for time sensitive operations. +CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=n +CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y +CONFIG_BT_NIMBLE_PINNED_TO_CORE=1 diff --git a/microros_ws/extra_packages/micropython/src/boards/sdkconfig.spiram b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.spiram new file mode 100644 index 0000000..5b4ce11 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.spiram @@ -0,0 +1,6 @@ +# MicroPython on ESP32, ESP IDF configuration with SPIRAM support + +CONFIG_ESP32_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_CACHE_WORKAROUND=y +CONFIG_SPIRAM_IGNORE_NOTFOUND=y +CONFIG_SPIRAM_USE_MEMMAP=y diff --git a/microros_ws/extra_packages/micropython/src/boards/sdkconfig.spiram_sx b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.spiram_sx new file mode 100644 index 0000000..ef24e90 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.spiram_sx @@ -0,0 +1,15 @@ +# MicroPython on ESP32-S2 and ESP32-PAD1_subscript_3, ESP IDF configuration with SPIRAM support +CONFIG_ESP32S2_SPIRAM_SUPPORT=y +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_MODE_QUAD=y +CONFIG_SPIRAM_TYPE_AUTO=y +CONFIG_DEFAULT_PSRAM_CLK_IO=30 +CONFIG_DEFAULT_PSRAM_CS_IO=26 +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_IGNORE_NOTFOUND=y +CONFIG_SPIRAM_USE_MEMMAP=y +CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 diff --git a/microros_ws/extra_packages/micropython/src/boards/sdkconfig.usb b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.usb new file mode 100644 index 0000000..657edbc --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/boards/sdkconfig.usb @@ -0,0 +1,4 @@ +CONFIG_USB_ENABLED=y +CONFIG_USB_CDC_ENABLED=y +CONFIG_USB_CDC_RX_BUFSIZE=256 +CONFIG_USB_CDC_TX_BUFSIZE=256 diff --git a/microros_ws/extra_packages/micropython/src/esp32_nvs.c b/microros_ws/extra_packages/micropython/src/esp32_nvs.c new file mode 100644 index 0000000..0b36619 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/esp32_nvs.c @@ -0,0 +1,151 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 by Thorsten von Eicken + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "mphalport.h" +#include "modesp32.h" +#include "nvs_flash.h" +#include "nvs.h" + +// This file implements the NVS (Non-Volatile Storage) class in the esp32 module. +// It provides simple access to the NVS feature provided by ESP-IDF. + +// NVS python object that represents an NVS namespace. +typedef struct _esp32_nvs_obj_t { + mp_obj_base_t base; + nvs_handle_t namespace; +} esp32_nvs_obj_t; + +// *esp32_nvs_new allocates a python NVS object given a handle to an esp-idf namespace C obj. +STATIC esp32_nvs_obj_t *esp32_nvs_new(nvs_handle_t namespace) { + esp32_nvs_obj_t *self = mp_obj_malloc(esp32_nvs_obj_t, &esp32_nvs_type); + self->namespace = namespace; + return self; +} + +// esp32_nvs_print prints an NVS object, unfortunately it doesn't seem possible to extract the +// namespace string or anything else from the opaque handle provided by esp-idf. +STATIC void esp32_nvs_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + // esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, ""); +} + +// esp32_nvs_make_new constructs a handle to an NVS namespace. +STATIC mp_obj_t esp32_nvs_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Check args + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + // Get requested nvs namespace + const char *ns_name = mp_obj_str_get_str(all_args[0]); + nvs_handle_t namespace; + check_esp_err(nvs_open(ns_name, NVS_READWRITE, &namespace)); + return MP_OBJ_FROM_PTR(esp32_nvs_new(namespace)); +} + +// esp32_nvs_set_i32 sets a 32-bit integer value +STATIC mp_obj_t esp32_nvs_set_i32(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + int32_t value = mp_obj_get_int(value_in); + check_esp_err(nvs_set_i32(self->namespace, key, value)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_nvs_set_i32_obj, esp32_nvs_set_i32); + +// esp32_nvs_get_i32 reads a 32-bit integer value +STATIC mp_obj_t esp32_nvs_get_i32(mp_obj_t self_in, mp_obj_t key_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + int32_t value; + check_esp_err(nvs_get_i32(self->namespace, key, &value)); + return mp_obj_new_int(value); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_nvs_get_i32_obj, esp32_nvs_get_i32); + +// esp32_nvs_set_blob writes a buffer object into a binary blob value. +STATIC mp_obj_t esp32_nvs_set_blob(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + mp_buffer_info_t value; + mp_get_buffer_raise(value_in, &value, MP_BUFFER_READ); + check_esp_err(nvs_set_blob(self->namespace, key, value.buf, value.len)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_nvs_set_blob_obj, esp32_nvs_set_blob); + +// esp32_nvs_get_blob reads a binary blob value into a bytearray. Returns actual length. +STATIC mp_obj_t esp32_nvs_get_blob(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + // get buffer to be filled + mp_buffer_info_t value; + mp_get_buffer_raise(value_in, &value, MP_BUFFER_WRITE); + size_t length = value.len; + // fill the buffer with the value, will raise an esp-idf error if the length of + // the provided buffer (bytearray) is too small + check_esp_err(nvs_get_blob(self->namespace, key, value.buf, &length)); + return MP_OBJ_NEW_SMALL_INT(length); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_nvs_get_blob_obj, esp32_nvs_get_blob); + +// esp32_nvs_erase_key erases one key. +STATIC mp_obj_t esp32_nvs_erase_key(mp_obj_t self_in, mp_obj_t key_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const char *key = mp_obj_str_get_str(key_in); + check_esp_err(nvs_erase_key(self->namespace, key)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_nvs_erase_key_obj, esp32_nvs_erase_key); + +// esp32_nvs_commit commits any changes to flash. +STATIC mp_obj_t esp32_nvs_commit(mp_obj_t self_in) { + esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_esp_err(nvs_commit(self->namespace)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_nvs_commit_obj, esp32_nvs_commit); + +STATIC const mp_rom_map_elem_t esp32_nvs_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_get_i32), MP_ROM_PTR(&esp32_nvs_get_i32_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_i32), MP_ROM_PTR(&esp32_nvs_set_i32_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_blob), MP_ROM_PTR(&esp32_nvs_get_blob_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_blob), MP_ROM_PTR(&esp32_nvs_set_blob_obj) }, + { MP_ROM_QSTR(MP_QSTR_erase_key), MP_ROM_PTR(&esp32_nvs_erase_key_obj) }, + { MP_ROM_QSTR(MP_QSTR_commit), MP_ROM_PTR(&esp32_nvs_commit_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(esp32_nvs_locals_dict, esp32_nvs_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + esp32_nvs_type, + MP_QSTR_NVS, + MP_TYPE_FLAG_NONE, + make_new, esp32_nvs_make_new, + print, esp32_nvs_print, + locals_dict, &esp32_nvs_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/esp32_partition.c b/microros_ws/extra_packages/micropython/src/esp32_partition.c new file mode 100644 index 0000000..17aa34e --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/esp32_partition.c @@ -0,0 +1,294 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "extmod/vfs.h" +#include "mphalport.h" +#include "modesp32.h" +#include "esp_ota_ops.h" + +// esp_partition_read and esp_partition_write can operate on arbitrary bytes +// but esp_partition_erase_range operates on 4k blocks. The default block size +// for a Partition object is therefore 4k, to make writes efficient, and also +// make it work well with filesystems like littlefs. The Partition object also +// supports smaller block sizes, in which case a cache is used and writes may +// be less efficient. +#define NATIVE_BLOCK_SIZE_BYTES (4096) + +enum { + ESP32_PARTITION_BOOT, + ESP32_PARTITION_RUNNING, +}; + +typedef struct _esp32_partition_obj_t { + mp_obj_base_t base; + const esp_partition_t *part; + uint8_t *cache; + uint16_t block_size; +} esp32_partition_obj_t; + +STATIC esp32_partition_obj_t *esp32_partition_new(const esp_partition_t *part, uint16_t block_size) { + if (part == NULL) { + mp_raise_OSError(MP_ENOENT); + } + esp32_partition_obj_t *self = mp_obj_malloc(esp32_partition_obj_t, &esp32_partition_type); + self->part = part; + self->block_size = block_size; + if (self->block_size < NATIVE_BLOCK_SIZE_BYTES) { + self->cache = m_new(uint8_t, NATIVE_BLOCK_SIZE_BYTES); + } else { + self->cache = NULL; + } + return self; +} + +STATIC void esp32_partition_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", + self->part->type, self->part->subtype, + self->part->address, self->part->size, + &self->part->label[0], self->part->encrypted + ); +} + +STATIC mp_obj_t esp32_partition_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Check args + mp_arg_check_num(n_args, n_kw, 1, 2, false); + + // Get requested partition + const esp_partition_t *part; + if (mp_obj_is_int(all_args[0])) { + // Integer given, get that particular partition + switch (mp_obj_get_int(all_args[0])) { + case ESP32_PARTITION_BOOT: + part = esp_ota_get_boot_partition(); + break; + case ESP32_PARTITION_RUNNING: + part = esp_ota_get_running_partition(); + break; + default: + mp_raise_ValueError(NULL); + } + } else { + // String given, search for partition with that label + const char *label = mp_obj_str_get_str(all_args[0]); + part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, label); + if (part == NULL) { + part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, label); + } + } + + // Get block size if given + uint16_t block_size = NATIVE_BLOCK_SIZE_BYTES; + if (n_args == 2) { + block_size = mp_obj_get_int(all_args[1]); + } + + // Return new object + return MP_OBJ_FROM_PTR(esp32_partition_new(part, block_size)); +} + +STATIC mp_obj_t esp32_partition_find(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // Parse args + enum { ARG_type, ARG_subtype, ARG_label, ARG_block_size }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_type, MP_ARG_INT, {.u_int = ESP_PARTITION_TYPE_APP} }, + { MP_QSTR_subtype, MP_ARG_INT, {.u_int = ESP_PARTITION_SUBTYPE_ANY} }, + { MP_QSTR_label, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_block_size, MP_ARG_INT, {.u_int = NATIVE_BLOCK_SIZE_BYTES} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get optional label string + const char *label = NULL; + if (args[ARG_label].u_obj != mp_const_none) { + label = mp_obj_str_get_str(args[ARG_label].u_obj); + } + + // Get block size + uint16_t block_size = args[ARG_block_size].u_int; + + // Build list of matching partitions + mp_obj_t list = mp_obj_new_list(0, NULL); + esp_partition_iterator_t iter = esp_partition_find(args[ARG_type].u_int, args[ARG_subtype].u_int, label); + while (iter != NULL) { + mp_obj_list_append(list, MP_OBJ_FROM_PTR(esp32_partition_new(esp_partition_get(iter), block_size))); + iter = esp_partition_next(iter); + } + esp_partition_iterator_release(iter); + + return list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_partition_find_fun_obj, 0, esp32_partition_find); +STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_partition_find_obj, MP_ROM_PTR(&esp32_partition_find_fun_obj)); + +STATIC mp_obj_t esp32_partition_info(mp_obj_t self_in) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t tuple[] = { + MP_OBJ_NEW_SMALL_INT(self->part->type), + MP_OBJ_NEW_SMALL_INT(self->part->subtype), + mp_obj_new_int_from_uint(self->part->address), + mp_obj_new_int_from_uint(self->part->size), + mp_obj_new_str(&self->part->label[0], strlen(&self->part->label[0])), + mp_obj_new_bool(self->part->encrypted), + }; + return mp_obj_new_tuple(6, tuple); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_info_obj, esp32_partition_info); + +STATIC mp_obj_t esp32_partition_readblocks(size_t n_args, const mp_obj_t *args) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = mp_obj_get_int(args[1]) * self->block_size; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); + if (n_args == 4) { + offset += mp_obj_get_int(args[3]); + } + check_esp_err(esp_partition_read(self->part, offset, bufinfo.buf, bufinfo.len)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_partition_readblocks_obj, 3, 4, esp32_partition_readblocks); + +STATIC mp_obj_t esp32_partition_writeblocks(size_t n_args, const mp_obj_t *args) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = mp_obj_get_int(args[1]) * self->block_size; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); + if (n_args == 3) { + // A simple write, which requires erasing first. + if (self->block_size >= NATIVE_BLOCK_SIZE_BYTES) { + // Block size is at least native erase-page size, so do an efficient erase. + check_esp_err(esp_partition_erase_range(self->part, offset, bufinfo.len)); + } else { + // Block size is less than native erase-page size, so do erase in sections. + uint32_t addr = (offset / NATIVE_BLOCK_SIZE_BYTES) * NATIVE_BLOCK_SIZE_BYTES; + uint32_t o = offset % NATIVE_BLOCK_SIZE_BYTES; + uint32_t top_addr = offset + bufinfo.len; + while (addr < top_addr) { + if (o > 0 || top_addr < addr + NATIVE_BLOCK_SIZE_BYTES) { + check_esp_err(esp_partition_read(self->part, addr, self->cache, NATIVE_BLOCK_SIZE_BYTES)); + } + check_esp_err(esp_partition_erase_range(self->part, addr, NATIVE_BLOCK_SIZE_BYTES)); + if (o > 0) { + check_esp_err(esp_partition_write(self->part, addr, self->cache, o)); + } + if (top_addr < addr + NATIVE_BLOCK_SIZE_BYTES) { + check_esp_err(esp_partition_write(self->part, top_addr, self->cache, addr + NATIVE_BLOCK_SIZE_BYTES - top_addr)); + } + o = 0; + addr += NATIVE_BLOCK_SIZE_BYTES; + } + } + } else { + // An extended write, erasing must have been done explicitly before this write. + offset += mp_obj_get_int(args[3]); + } + check_esp_err(esp_partition_write(self->part, offset, bufinfo.buf, bufinfo.len)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_partition_writeblocks_obj, 3, 4, esp32_partition_writeblocks); + +STATIC mp_obj_t esp32_partition_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t cmd = mp_obj_get_int(cmd_in); + switch (cmd) { + case MP_BLOCKDEV_IOCTL_INIT: + return MP_OBJ_NEW_SMALL_INT(0); + case MP_BLOCKDEV_IOCTL_DEINIT: + return MP_OBJ_NEW_SMALL_INT(0); + case MP_BLOCKDEV_IOCTL_SYNC: + return MP_OBJ_NEW_SMALL_INT(0); + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: + return MP_OBJ_NEW_SMALL_INT(self->part->size / self->block_size); + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + return MP_OBJ_NEW_SMALL_INT(self->block_size); + case MP_BLOCKDEV_IOCTL_BLOCK_ERASE: { + if (self->block_size != NATIVE_BLOCK_SIZE_BYTES) { + return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); + } + uint32_t offset = mp_obj_get_int(arg_in) * NATIVE_BLOCK_SIZE_BYTES; + check_esp_err(esp_partition_erase_range(self->part, offset, NATIVE_BLOCK_SIZE_BYTES)); + return MP_OBJ_NEW_SMALL_INT(0); + } + default: + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_partition_ioctl_obj, esp32_partition_ioctl); + +STATIC mp_obj_t esp32_partition_set_boot(mp_obj_t self_in) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_esp_err(esp_ota_set_boot_partition(self->part)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_set_boot_obj, esp32_partition_set_boot); + +STATIC mp_obj_t esp32_partition_get_next_update(mp_obj_t self_in) { + esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_FROM_PTR(esp32_partition_new(esp_ota_get_next_update_partition(self->part), NATIVE_BLOCK_SIZE_BYTES)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_get_next_update_obj, esp32_partition_get_next_update); + +STATIC mp_obj_t esp32_partition_mark_app_valid_cancel_rollback(mp_obj_t cls_in) { + check_esp_err(esp_ota_mark_app_valid_cancel_rollback()); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_valid_cancel_rollback_fun_obj, + esp32_partition_mark_app_valid_cancel_rollback); +STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_partition_mark_app_valid_cancel_rollback_obj, + MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_fun_obj)); + +STATIC const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&esp32_partition_find_obj) }, + + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&esp32_partition_info_obj) }, + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&esp32_partition_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&esp32_partition_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&esp32_partition_ioctl_obj) }, + + { MP_ROM_QSTR(MP_QSTR_set_boot), MP_ROM_PTR(&esp32_partition_set_boot_obj) }, + { MP_ROM_QSTR(MP_QSTR_mark_app_valid_cancel_rollback), MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_next_update), MP_ROM_PTR(&esp32_partition_get_next_update_obj) }, + + { MP_ROM_QSTR(MP_QSTR_BOOT), MP_ROM_INT(ESP32_PARTITION_BOOT) }, + { MP_ROM_QSTR(MP_QSTR_RUNNING), MP_ROM_INT(ESP32_PARTITION_RUNNING) }, + { MP_ROM_QSTR(MP_QSTR_TYPE_APP), MP_ROM_INT(ESP_PARTITION_TYPE_APP) }, + { MP_ROM_QSTR(MP_QSTR_TYPE_DATA), MP_ROM_INT(ESP_PARTITION_TYPE_DATA) }, +}; +STATIC MP_DEFINE_CONST_DICT(esp32_partition_locals_dict, esp32_partition_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + esp32_partition_type, + MP_QSTR_Partition, + MP_TYPE_FLAG_NONE, + make_new, esp32_partition_make_new, + print, esp32_partition_print, + locals_dict, &esp32_partition_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/esp32_rmt.c b/microros_ws/extra_packages/micropython/src/esp32_rmt.c new file mode 100644 index 0000000..78c8c8a --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/esp32_rmt.c @@ -0,0 +1,382 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 "Matt Trentini" + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "modmachine.h" +#include "mphalport.h" +#include "modesp32.h" + +#include "esp_task.h" +#include "driver/rmt.h" + +// This exposes the ESP32's RMT module to MicroPython. RMT is provided by the Espressif ESP-IDF: +// +// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/rmt.html +// +// With some examples provided: +// +// https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/RMT +// +// RMT allows accurate (down to 12.5ns resolution) transmit - and receive - of pulse signals. +// Originally designed to generate infrared remote control signals, the module is very +// flexible and quite easy-to-use. +// +// This current MicroPython implementation lacks some major features, notably receive pulses +// and carrier output. + +// Last available RMT channel that can transmit. +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0) +#define RMT_LAST_TX_CHANNEL (RMT_CHANNEL_MAX - 1) +#else +#define RMT_LAST_TX_CHANNEL (SOC_RMT_TX_CANDIDATES_PER_GROUP - 1) +#endif + +// Forward declaration +extern const mp_obj_type_t esp32_rmt_type; + +typedef struct _esp32_rmt_obj_t { + mp_obj_base_t base; + uint8_t channel_id; + gpio_num_t pin; + uint8_t clock_div; + mp_uint_t num_items; + rmt_item32_t *items; + bool loop_en; +} esp32_rmt_obj_t; + +// Current channel used for machine.bitstream, in the machine_bitstream_high_low_rmt +// implementation. A value of -1 means do not use RMT. +int8_t esp32_rmt_bitstream_channel_id = RMT_LAST_TX_CHANNEL; + +#if MP_TASK_COREID == 0 + +typedef struct _rmt_install_state_t { + SemaphoreHandle_t handle; + uint8_t channel_id; + esp_err_t ret; +} rmt_install_state_t; + +STATIC void rmt_install_task(void *pvParameter) { + rmt_install_state_t *state = pvParameter; + state->ret = rmt_driver_install(state->channel_id, 0, 0); + xSemaphoreGive(state->handle); + vTaskDelete(NULL); + for (;;) { + } +} + +// Call rmt_driver_install on core 1. This ensures that the RMT interrupt handler is +// serviced on core 1, so that WiFi (if active) does not interrupt it and cause glitches. +esp_err_t rmt_driver_install_core1(uint8_t channel_id) { + TaskHandle_t th; + rmt_install_state_t state; + state.handle = xSemaphoreCreateBinary(); + state.channel_id = channel_id; + xTaskCreatePinnedToCore(rmt_install_task, "rmt_install_task", 2048 / sizeof(StackType_t), &state, ESP_TASK_PRIO_MIN + 1, &th, 1); + xSemaphoreTake(state.handle, portMAX_DELAY); + vSemaphoreDelete(state.handle); + return state.ret; +} + +#else + +// MicroPython runs on core 1, so we can call the RMT installer directly and its +// interrupt handler will also run on core 1. +esp_err_t rmt_driver_install_core1(uint8_t channel_id) { + return rmt_driver_install(channel_id, 0, 0); +} + +#endif + +STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_clock_div, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, // 100ns resolution + { MP_QSTR_idle_level, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, // low voltage + { MP_QSTR_tx_carrier, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // no carrier + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + mp_uint_t channel_id = args[0].u_int; + gpio_num_t pin_id = machine_pin_get_id(args[1].u_obj); + mp_uint_t clock_div = args[2].u_int; + mp_uint_t idle_level = args[3].u_bool; + mp_obj_t tx_carrier_obj = args[4].u_obj; + + if (esp32_rmt_bitstream_channel_id >= 0 && channel_id == esp32_rmt_bitstream_channel_id) { + mp_raise_ValueError(MP_ERROR_TEXT("channel used by bitstream")); + } + + if (clock_div < 1 || clock_div > 255) { + mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255")); + } + + esp32_rmt_obj_t *self = m_new_obj_with_finaliser(esp32_rmt_obj_t); + self->base.type = &esp32_rmt_type; + self->channel_id = channel_id; + self->pin = pin_id; + self->clock_div = clock_div; + self->loop_en = false; + + rmt_config_t config = {0}; + config.rmt_mode = RMT_MODE_TX; + config.channel = (rmt_channel_t)self->channel_id; + config.gpio_num = self->pin; + config.mem_block_num = 1; + config.tx_config.loop_en = 0; + + if (tx_carrier_obj != mp_const_none) { + mp_obj_t *tx_carrier_details = NULL; + mp_obj_get_array_fixed_n(tx_carrier_obj, 3, &tx_carrier_details); + mp_uint_t frequency = mp_obj_get_int(tx_carrier_details[0]); + mp_uint_t duty = mp_obj_get_int(tx_carrier_details[1]); + mp_uint_t level = mp_obj_is_true(tx_carrier_details[2]); + + if (frequency == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("tx_carrier frequency must be >0")); + } + if (duty > 100) { + mp_raise_ValueError(MP_ERROR_TEXT("tx_carrier duty must be 0..100")); + } + + config.tx_config.carrier_en = 1; + config.tx_config.carrier_freq_hz = frequency; + config.tx_config.carrier_duty_percent = duty; + config.tx_config.carrier_level = level; + } else { + config.tx_config.carrier_en = 0; + } + + config.tx_config.idle_output_en = 1; + config.tx_config.idle_level = idle_level; + + config.clk_div = self->clock_div; + + check_esp_err(rmt_config(&config)); + check_esp_err(rmt_driver_install_core1(config.channel)); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->pin != -1) { + bool idle_output_en; + rmt_idle_level_t idle_level; + check_esp_err(rmt_get_idle_level(self->channel_id, &idle_output_en, &idle_level)); + mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u, idle_level=%u)", + self->channel_id, self->pin, APB_CLK_FREQ, self->clock_div, idle_level); + } else { + mp_printf(print, "RMT()"); + } +} + +STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { + // fixme: check for valid channel. Return exception if error occurs. + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->pin != -1) { // Check if channel has already been deinitialised. + rmt_driver_uninstall(self->channel_id); + self->pin = -1; // -1 to indicate RMT is unused + m_free(self->items); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); + +// Return the source frequency. +// Currently only the APB clock (80MHz) can be used but it is possible other +// clock sources will added in the future. +STATIC mp_obj_t esp32_rmt_source_freq(mp_obj_t self_in) { + return mp_obj_new_int(APB_CLK_FREQ); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_source_freq_obj, esp32_rmt_source_freq); + +// Return the clock divider. +STATIC mp_obj_t esp32_rmt_clock_div(mp_obj_t self_in) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->clock_div); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_clock_div_obj, esp32_rmt_clock_div); + +// Query whether the channel has finished sending pulses. Takes an optional +// timeout (in milliseconds), returning true if the pulse stream has +// completed or false if they are still transmitting (or timeout is reached). +STATIC mp_obj_t esp32_rmt_wait_done(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_self, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0].u_obj); + + esp_err_t err = rmt_wait_tx_done(self->channel_id, args[1].u_int / portTICK_PERIOD_MS); + return err == ESP_OK ? mp_const_true : mp_const_false; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_wait_done_obj, 1, esp32_rmt_wait_done); + +STATIC mp_obj_t esp32_rmt_loop(mp_obj_t self_in, mp_obj_t loop) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + self->loop_en = mp_obj_get_int(loop); + if (!self->loop_en) { + bool loop_en; + check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); + if (loop_en) { + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); + check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); + } + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_obj, esp32_rmt_loop); + +STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_obj_t duration_obj = args[1]; + mp_obj_t data_obj = n_args > 2 ? args[2] : mp_const_true; + + mp_uint_t duration = 0; + size_t duration_length = 0; + mp_obj_t *duration_ptr = NULL; + mp_uint_t data = 0; + size_t data_length = 0; + mp_obj_t *data_ptr = NULL; + mp_uint_t num_pulses = 0; + + if (!(mp_obj_is_type(data_obj, &mp_type_tuple) || mp_obj_is_type(data_obj, &mp_type_list))) { + // Mode 1: array of durations, toggle initial data value + mp_obj_get_array(duration_obj, &duration_length, &duration_ptr); + data = mp_obj_is_true(data_obj); + num_pulses = duration_length; + } else if (mp_obj_is_int(duration_obj)) { + // Mode 2: constant duration, array of data values + duration = mp_obj_get_int(duration_obj); + mp_obj_get_array(data_obj, &data_length, &data_ptr); + num_pulses = data_length; + } else { + // Mode 3: arrays of durations and data values + mp_obj_get_array(duration_obj, &duration_length, &duration_ptr); + mp_obj_get_array(data_obj, &data_length, &data_ptr); + if (duration_length != data_length) { + mp_raise_ValueError(MP_ERROR_TEXT("duration and data must have same length")); + } + num_pulses = duration_length; + } + + if (num_pulses == 0) { + mp_raise_ValueError(MP_ERROR_TEXT("No pulses")); + } + if (self->loop_en && num_pulses > 126) { + mp_raise_ValueError(MP_ERROR_TEXT("Too many pulses for loop")); + } + + mp_uint_t num_items = (num_pulses / 2) + (num_pulses % 2); + if (num_items > self->num_items) { + self->items = (rmt_item32_t *)m_realloc(self->items, num_items * sizeof(rmt_item32_t *)); + self->num_items = num_items; + } + + for (mp_uint_t item_index = 0, pulse_index = 0; item_index < num_items; item_index++) { + self->items[item_index].duration0 = duration_length ? mp_obj_get_int(duration_ptr[pulse_index]) : duration; + self->items[item_index].level0 = data_length ? mp_obj_is_true(data_ptr[pulse_index]) : data++; + pulse_index++; + if (pulse_index < num_pulses) { + self->items[item_index].duration1 = duration_length ? mp_obj_get_int(duration_ptr[pulse_index]) : duration; + self->items[item_index].level1 = data_length ? mp_obj_is_true(data_ptr[pulse_index]) : data++; + pulse_index++; + } else { + self->items[item_index].duration1 = 0; + self->items[item_index].level1 = 0; + } + } + + if (self->loop_en) { + bool loop_en; + check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); + if (loop_en) { + check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); + } + check_esp_err(rmt_wait_tx_done(self->channel_id, portMAX_DELAY)); + } + + check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false)); + + if (self->loop_en) { + check_esp_err(rmt_set_tx_intr_en(self->channel_id, false)); + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, true)); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_write_pulses_obj, 2, 3, esp32_rmt_write_pulses); + +STATIC mp_obj_t esp32_rmt_bitstream_channel(size_t n_args, const mp_obj_t *args) { + if (n_args > 0) { + if (args[0] == mp_const_none) { + esp32_rmt_bitstream_channel_id = -1; + } else { + mp_int_t channel_id = mp_obj_get_int(args[0]); + if (channel_id < 0 || channel_id > RMT_LAST_TX_CHANNEL) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid channel")); + } + esp32_rmt_bitstream_channel_id = channel_id; + } + } + if (esp32_rmt_bitstream_channel_id < 0) { + return mp_const_none; + } else { + return MP_OBJ_NEW_SMALL_INT(esp32_rmt_bitstream_channel_id); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_bitstream_channel_fun_obj, 0, 1, esp32_rmt_bitstream_channel); +STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_bitstream_channel_obj, MP_ROM_PTR(&esp32_rmt_bitstream_channel_fun_obj)); + +STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_source_freq), MP_ROM_PTR(&esp32_rmt_source_freq_obj) }, + { MP_ROM_QSTR(MP_QSTR_clock_div), MP_ROM_PTR(&esp32_rmt_clock_div_obj) }, + { MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) }, + { MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&esp32_rmt_loop_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_pulses), MP_ROM_PTR(&esp32_rmt_write_pulses_obj) }, + + // Static methods + { MP_ROM_QSTR(MP_QSTR_bitstream_channel), MP_ROM_PTR(&esp32_rmt_bitstream_channel_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(esp32_rmt_locals_dict, esp32_rmt_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + esp32_rmt_type, + MP_QSTR_RMT, + MP_TYPE_FLAG_NONE, + make_new, esp32_rmt_make_new, + print, esp32_rmt_print, + locals_dict, &esp32_rmt_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/esp32_ulp.c b/microros_ws/extra_packages/micropython/src/esp32_ulp.c new file mode 100644 index 0000000..843bdb2 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/esp32_ulp.c @@ -0,0 +1,102 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 "Andreas Valder" + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" + +#if CONFIG_IDF_TARGET_ESP32 + +#include "esp32/ulp.h" +#include "esp_err.h" + +typedef struct _esp32_ulp_obj_t { + mp_obj_base_t base; +} esp32_ulp_obj_t; + +const mp_obj_type_t esp32_ulp_type; + +// singleton ULP object +STATIC const esp32_ulp_obj_t esp32_ulp_obj = {{&esp32_ulp_type}}; + +STATIC mp_obj_t esp32_ulp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // return constant object + return (mp_obj_t)&esp32_ulp_obj; +} + +STATIC mp_obj_t esp32_ulp_set_wakeup_period(mp_obj_t self_in, mp_obj_t period_index_in, mp_obj_t period_us_in) { + mp_uint_t period_index = mp_obj_get_int(period_index_in); + mp_uint_t period_us = mp_obj_get_int(period_us_in); + int _errno = ulp_set_wakeup_period(period_index, period_us); + if (_errno != ESP_OK) { + mp_raise_OSError(_errno); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_ulp_set_wakeup_period_obj, esp32_ulp_set_wakeup_period); + +STATIC mp_obj_t esp32_ulp_load_binary(mp_obj_t self_in, mp_obj_t load_addr_in, mp_obj_t program_binary_in) { + mp_uint_t load_addr = mp_obj_get_int(load_addr_in); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(program_binary_in, &bufinfo, MP_BUFFER_READ); + + int _errno = ulp_load_binary(load_addr, bufinfo.buf, bufinfo.len / sizeof(uint32_t)); + if (_errno != ESP_OK) { + mp_raise_OSError(_errno); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_ulp_load_binary_obj, esp32_ulp_load_binary); + +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)); + if (_errno != ESP_OK) { + mp_raise_OSError(_errno); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_ulp_run_obj, esp32_ulp_run); + +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) }, + { MP_ROM_QSTR(MP_QSTR_run), MP_ROM_PTR(&esp32_ulp_run_obj) }, + { MP_ROM_QSTR(MP_QSTR_RESERVE_MEM), MP_ROM_INT(CONFIG_ESP32_ULP_COPROC_RESERVE_MEM) }, +}; +STATIC MP_DEFINE_CONST_DICT(esp32_ulp_locals_dict, esp32_ulp_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + esp32_ulp_type, + MP_QSTR_ULP, + MP_TYPE_FLAG_NONE, + make_new, esp32_ulp_make_new, + locals_dict, &esp32_ulp_locals_dict + ); + +#endif // CONFIG_IDF_TARGET_ESP32 diff --git a/microros_ws/extra_packages/micropython/src/fatfs_port.c b/microros_ws/extra_packages/micropython/src/fatfs_port.c new file mode 100644 index 0000000..b9ad30a --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/fatfs_port.c @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014, 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "lib/oofatfs/ff.h" +#include "shared/timeutils/timeutils.h" + +DWORD get_fattime(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + timeutils_struct_time_t tm; + timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); + + return ((DWORD)(tm.tm_year - 1980) << 25) | ((DWORD)tm.tm_mon << 21) | ((DWORD)tm.tm_mday << 16) | + ((DWORD)tm.tm_hour << 11) | ((DWORD)tm.tm_min << 5) | ((DWORD)tm.tm_sec >> 1); +} diff --git a/microros_ws/extra_packages/micropython/src/gccollect.c b/microros_ws/extra_packages/micropython/src/gccollect.c new file mode 100644 index 0000000..403a3c7 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/gccollect.c @@ -0,0 +1,83 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * Copyright (c) 2017 Pycom Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/mpconfig.h" +#include "py/mpstate.h" +#include "py/gc.h" +#include "py/mpthread.h" +#include "gccollect.h" +#include "soc/cpu.h" + +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + +#include "xtensa/hal.h" + +static void gc_collect_inner(int level) { + if (level < XCHAL_NUM_AREGS / 8) { + gc_collect_inner(level + 1); + if (level != 0) { + return; + } + } + + if (level == XCHAL_NUM_AREGS / 8) { + // get the sp + volatile uint32_t sp = (uint32_t)get_sp(); + gc_collect_root((void **)sp, ((mp_uint_t)MP_STATE_THREAD(stack_top) - sp) / sizeof(uint32_t)); + return; + } + + // trace root pointers from any threads + #if MICROPY_PY_THREAD + mp_thread_gc_others(); + #endif +} + +void gc_collect(void) { + gc_collect_start(); + gc_collect_inner(0); + gc_collect_end(); +} + +#elif CONFIG_IDF_TARGET_ESP32C3 + +#include "shared/runtime/gchelper.h" + +void gc_collect(void) { + gc_collect_start(); + gc_helper_collect_regs_and_stack(); + #if MICROPY_PY_THREAD + mp_thread_gc_others(); + #endif + gc_collect_end(); +} + +#endif diff --git a/microros_ws/extra_packages/micropython/src/gccollect.h b/microros_ws/extra_packages/micropython/src/gccollect.h new file mode 100644 index 0000000..fe02cc6 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/gccollect.h @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +extern uint32_t _text_start; +extern uint32_t _text_end; +extern uint32_t _irom0_text_start; +extern uint32_t _irom0_text_end; +extern uint32_t _data_start; +extern uint32_t _data_end; +extern uint32_t _rodata_start; +extern uint32_t _rodata_end; +extern uint32_t _bss_start; +extern uint32_t _bss_end; +extern uint32_t _heap_start; +extern uint32_t _heap_end; + +void gc_collect(void); diff --git a/microros_ws/extra_packages/micropython/src/help.c b/microros_ws/extra_packages/micropython/src/help.c new file mode 100644 index 0000000..2336d97 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/help.c @@ -0,0 +1,65 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/builtin.h" + +const char esp32_help_text[] = + "Welcome to MicroPython on the ESP32!\n" + "\n" + "For generic online docs please visit http://docs.micropython.org/\n" + "\n" + "For access to the hardware use the 'machine' module:\n" + "\n" + "import machine\n" + "pin12 = machine.Pin(12, machine.Pin.OUT)\n" + "pin12.value(1)\n" + "pin13 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_UP)\n" + "print(pin13.value())\n" + "i2c = machine.I2C(scl=machine.Pin(21), sda=machine.Pin(22))\n" + "i2c.scan()\n" + "i2c.writeto(addr, b'1234')\n" + "i2c.readfrom(addr, 4)\n" + "\n" + "Basic WiFi configuration:\n" + "\n" + "import network\n" + "sta_if = network.WLAN(network.STA_IF); sta_if.active(True)\n" + "sta_if.scan() # Scan for available access points\n" + "sta_if.connect(\"\", \"\") # Connect to an AP\n" + "sta_if.isconnected() # Check for successful connection\n" + "\n" + "Control commands:\n" + " CTRL-A -- on a blank line, enter raw REPL mode\n" + " CTRL-B -- on a blank line, enter normal REPL mode\n" + " CTRL-C -- interrupt a running program\n" + " CTRL-D -- on a blank line, do a soft reset of the board\n" + " CTRL-E -- on a blank line, enter paste mode\n" + "\n" + "For further help on a specific object, type help(obj)\n" + "For a list of available modules, type help('modules')\n" +; diff --git a/microros_ws/extra_packages/micropython/src/machine_adc.c b/microros_ws/extra_packages/micropython/src/machine_adc.c new file mode 100644 index 0000000..1e20186 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_adc.c @@ -0,0 +1,266 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Nick Moore + * Copyright (c) 2021 Jonathan Hogg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "esp_log.h" + +#include "driver/gpio.h" +#include "driver/adc.h" + +#include "py/runtime.h" +#include "py/mphal.h" +#include "modmachine.h" +#include "machine_adc.h" + +#define ADCBLOCK1 (&madcblock_obj[0]) +#define ADCBLOCK2 (&madcblock_obj[1]) + +STATIC const madc_obj_t madc_obj[] = { + #if CONFIG_IDF_TARGET_ESP32 + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_36}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_37}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_38}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_39}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_32}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_33}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_6, GPIO_NUM_34}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_7, GPIO_NUM_35}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_4}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_1, GPIO_NUM_0}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_2, GPIO_NUM_2}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_3, GPIO_NUM_15}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_4, GPIO_NUM_13}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_5, GPIO_NUM_12}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_6, GPIO_NUM_14}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_27}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_25}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_26}, + #elif CONFIG_IDF_TARGET_ESP32C3 + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_0}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_1}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_2}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_3}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_4}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_5}, + #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_1}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_2}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_3}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_4}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_5}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_6}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_6, GPIO_NUM_7}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_7, GPIO_NUM_8}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_8, GPIO_NUM_9}, + {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_9, GPIO_NUM_10}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_11}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_1, GPIO_NUM_12}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_2, GPIO_NUM_13}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_3, GPIO_NUM_14}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_4, GPIO_NUM_15}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_5, GPIO_NUM_16}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_6, GPIO_NUM_17}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_18}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_19}, + {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_20}, + #endif +}; + +// These values are initialised to 0, which means the corresponding ADC channel is not initialised. +// The madc_atten_get/madc_atten_set functions store (atten+1) here so that the uninitialised state +// can be distinguished from the initialised state. +STATIC uint8_t madc_obj_atten[MP_ARRAY_SIZE(madc_obj)]; + +static inline adc_atten_t madc_atten_get(const madc_obj_t *self) { + uint8_t value = madc_obj_atten[self - &madc_obj[0]]; + return value == 0 ? ADC_ATTEN_MAX : value - 1; +} + +static inline void madc_atten_set(const madc_obj_t *self, adc_atten_t atten) { + madc_obj_atten[self - &madc_obj[0]] = atten + 1; +} + +const madc_obj_t *madc_search_helper(madcblock_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id) { + for (int i = 0; i < MP_ARRAY_SIZE(madc_obj); i++) { + const madc_obj_t *adc = &madc_obj[i]; + if ((block == NULL || block == adc->block) && (channel_id == -1 || channel_id == adc->channel_id) && (gpio_id == -1 || gpio_id == adc->gpio_id)) { + return adc; + } + } + return NULL; +} + +STATIC void madc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "ADC(Pin(%u), atten=%u)", self->gpio_id, madc_atten_get(self)); +} + +STATIC void madc_atten_helper(const madc_obj_t *self, mp_int_t atten) { + esp_err_t err; + if (self->block->unit_id == ADC_UNIT_1) { + err = adc1_config_channel_atten(self->channel_id, atten); + } else { + err = adc2_config_channel_atten(self->channel_id, atten); + } + if (err != ESP_OK) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid atten")); + } + madc_atten_set(self, atten); +} + +void madc_init_helper(const madc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_atten, + }; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_atten, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_int_t atten = args[ARG_atten].u_int; + if (atten != -1) { + madc_atten_helper(self, atten); + } else if (madc_atten_get(self) == ADC_ATTEN_MAX) { + madc_atten_helper(self, ADC_ATTEN_DB_0); + } +} + +STATIC mp_obj_t madc_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { + mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); + gpio_num_t gpio_id = machine_pin_get_id(args[0]); + const madc_obj_t *self = madc_search_helper(NULL, -1, gpio_id); + if (!self) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); + } + + if (self->block->width == -1) { + madcblock_bits_helper(self->block, self->block->bits); + } + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); + madc_init_helper(self, n_pos_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t madc_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + const madc_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + madc_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(madc_init_obj, 1, madc_init); + +STATIC mp_obj_t madc_block(mp_obj_t self_in) { + const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_FROM_PTR(self->block); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_block_obj, madc_block); + +STATIC mp_obj_t madc_read(mp_obj_t self_in) { + const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t raw = madcblock_read_helper(self->block, self->channel_id); + return MP_OBJ_NEW_SMALL_INT(raw); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_read_obj, madc_read); + +STATIC mp_obj_t madc_read_u16(mp_obj_t self_in) { + const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_uint_t raw = madcblock_read_helper(self->block, self->channel_id); + // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) + mp_int_t bits = self->block->bits; + mp_uint_t u16 = raw << (16 - bits) | raw >> (2 * bits - 16); + return MP_OBJ_NEW_SMALL_INT(u16); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_read_u16_obj, madc_read_u16); + +STATIC mp_obj_t madc_read_uv(mp_obj_t self_in) { + const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); + adc_atten_t atten = madc_atten_get(self); + return MP_OBJ_NEW_SMALL_INT(madcblock_read_uv_helper(self->block, self->channel_id, atten)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_read_uv_obj, madc_read_uv); + +STATIC mp_obj_t madc_atten(mp_obj_t self_in, mp_obj_t atten_in) { + const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t atten = mp_obj_get_int(atten_in); + madc_atten_helper(self, atten); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(madc_atten_obj, madc_atten); + +STATIC mp_obj_t madc_width(mp_obj_t self_in, mp_obj_t bits_in) { + const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t bits = mp_obj_get_int(bits_in); + madcblock_bits_helper(self->block, bits); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(madc_width_obj, madc_width); + +STATIC const mp_rom_map_elem_t madc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&madc_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_block), MP_ROM_PTR(&madc_block_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&madc_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_u16), MP_ROM_PTR(&madc_read_u16_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_uv), MP_ROM_PTR(&madc_read_uv_obj) }, + + // Legacy API methods: + { MP_ROM_QSTR(MP_QSTR_atten), MP_ROM_PTR(&madc_atten_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&madc_width_obj) }, + + { MP_ROM_QSTR(MP_QSTR_ATTN_0DB), MP_ROM_INT(ADC_ATTEN_DB_0) }, + { MP_ROM_QSTR(MP_QSTR_ATTN_2_5DB), MP_ROM_INT(ADC_ATTEN_DB_2_5) }, + { MP_ROM_QSTR(MP_QSTR_ATTN_6DB), MP_ROM_INT(ADC_ATTEN_DB_6) }, + { MP_ROM_QSTR(MP_QSTR_ATTN_11DB), MP_ROM_INT(ADC_ATTEN_DB_11) }, + + #if CONFIG_IDF_TARGET_ESP32 + { MP_ROM_QSTR(MP_QSTR_WIDTH_9BIT), MP_ROM_INT(9) }, + { MP_ROM_QSTR(MP_QSTR_WIDTH_10BIT), MP_ROM_INT(10) }, + { MP_ROM_QSTR(MP_QSTR_WIDTH_11BIT), MP_ROM_INT(11) }, + #endif + #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 + { MP_ROM_QSTR(MP_QSTR_WIDTH_12BIT), MP_ROM_INT(12) }, + #endif + #if CONFIG_IDF_TARGET_ESP32S2 + { MP_ROM_QSTR(MP_QSTR_WIDTH_13BIT), MP_ROM_INT(13) }, + #endif + +}; +STATIC MP_DEFINE_CONST_DICT(madc_locals_dict, madc_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_adc_type, + MP_QSTR_ADC, + MP_TYPE_FLAG_NONE, + make_new, madc_make_new, + print, madc_print, + locals_dict, &madc_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/machine_adc.h b/microros_ws/extra_packages/micropython/src/machine_adc.h new file mode 100644 index 0000000..0f229a2 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_adc.h @@ -0,0 +1,16 @@ +#ifndef MICROPY_INCLUDED_MACHINE_ADC_H +#define MICROPY_INCLUDED_MACHINE_ADC_H + +#include "machine_adcblock.h" + +typedef struct _madc_obj_t { + mp_obj_base_t base; + madcblock_obj_t *block; + adc_channel_t channel_id; + gpio_num_t gpio_id; +} madc_obj_t; + +extern const madc_obj_t *madc_search_helper(madcblock_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id); +extern void madc_init_helper(const madc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args); + +#endif // MICROPY_INCLUDED_MACHINE_ADC_H diff --git a/microros_ws/extra_packages/micropython/src/machine_adcblock.c b/microros_ws/extra_packages/micropython/src/machine_adcblock.c new file mode 100644 index 0000000..afe8fde --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_adcblock.c @@ -0,0 +1,204 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jonathan Hogg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "esp_log.h" + +#include "driver/gpio.h" +#include "driver/adc.h" + +#include "py/runtime.h" +#include "py/mphal.h" +#include "modmachine.h" +#include "machine_adc.h" +#include "machine_adcblock.h" + +#define DEFAULT_VREF 1100 + +madcblock_obj_t madcblock_obj[] = { + #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 + {{&machine_adcblock_type}, ADC_UNIT_1, 12, -1, {0}}, + {{&machine_adcblock_type}, ADC_UNIT_2, 12, -1, {0}}, + #elif CONFIG_IDF_TARGET_ESP32S2 + {{&machine_adcblock_type}, ADC_UNIT_1, 13, -1, {0}}, + {{&machine_adcblock_type}, ADC_UNIT_2, 13, -1, {0}}, + #endif +}; + +STATIC void madcblock_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + madcblock_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "ADCBlock(%u, bits=%u)", self->unit_id, self->bits); +} + +void madcblock_bits_helper(madcblock_obj_t *self, mp_int_t bits) { + switch (bits) { + #if CONFIG_IDF_TARGET_ESP32 + case 9: + self->width = ADC_WIDTH_BIT_9; + break; + case 10: + self->width = ADC_WIDTH_BIT_10; + break; + case 11: + self->width = ADC_WIDTH_BIT_11; + break; + #endif + #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 + case 12: + self->width = ADC_WIDTH_BIT_12; + break; + #endif + #if CONFIG_IDF_TARGET_ESP32S2 + case 13: + self->width = ADC_WIDTH_BIT_13; + break; + #endif + default: + mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); + } + self->bits = bits; + + if (self->unit_id == ADC_UNIT_1) { + adc1_config_width(self->width); + } + for (adc_atten_t atten = ADC_ATTEN_DB_0; atten < ADC_ATTEN_MAX; atten++) { + if (self->characteristics[atten] != NULL) { + esp_adc_cal_characterize(self->unit_id, atten, self->width, DEFAULT_VREF, self->characteristics[atten]); + } + } +} + +STATIC void madcblock_init_helper(madcblock_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_bits, + }; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_int_t bits = args[ARG_bits].u_int; + if (bits != -1) { + madcblock_bits_helper(self, bits); + } else if (self->width == -1) { + madcblock_bits_helper(self, self->bits); + } +} + +STATIC mp_obj_t madcblock_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { + mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); + adc_unit_t unit = mp_obj_get_int(args[0]); + madcblock_obj_t *self = NULL; + for (int i = 0; i < MP_ARRAY_SIZE(madcblock_obj); i++) { + if (unit == madcblock_obj[i].unit_id) { + self = &madcblock_obj[i]; + break; + } + } + if (!self) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid block id")); + } + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); + madcblock_init_helper(self, n_pos_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t madcblock_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + madcblock_obj_t *self = pos_args[0]; + madcblock_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(madcblock_init_obj, 1, madcblock_init); + +STATIC mp_obj_t madcblock_connect(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + madcblock_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + adc_channel_t channel_id = -1; + gpio_num_t gpio_id = -1; + if (n_pos_args == 2) { + if (mp_obj_is_int(pos_args[1])) { + channel_id = mp_obj_get_int(pos_args[1]); + } else { + gpio_id = machine_pin_get_id(pos_args[1]); + } + } else if (n_pos_args == 3) { + channel_id = mp_obj_get_int(pos_args[1]); + gpio_id = machine_pin_get_id(pos_args[2]); + } else { + mp_raise_TypeError(MP_ERROR_TEXT("too many positional args")); + } + + const madc_obj_t *adc = madc_search_helper(self, channel_id, gpio_id); + if (adc != NULL) { + madc_init_helper(adc, 0, pos_args + n_pos_args, kw_args); + return MP_OBJ_FROM_PTR(adc); + } + mp_raise_ValueError(MP_ERROR_TEXT("no matching ADC")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(madcblock_connect_obj, 2, madcblock_connect); + +mp_int_t madcblock_read_helper(madcblock_obj_t *self, adc_channel_t channel_id) { + int raw; + if (self->unit_id == ADC_UNIT_1) { + raw = adc1_get_raw(channel_id); + } else { + check_esp_err(adc2_get_raw(channel_id, self->width, &raw)); + } + return raw; +} + +mp_int_t madcblock_read_uv_helper(madcblock_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) { + int raw = madcblock_read_helper(self, channel_id); + esp_adc_cal_characteristics_t *adc_chars = self->characteristics[atten]; + if (adc_chars == NULL) { + adc_chars = malloc(sizeof(esp_adc_cal_characteristics_t)); + esp_adc_cal_characterize(self->unit_id, atten, self->width, DEFAULT_VREF, adc_chars); + self->characteristics[atten] = adc_chars; + } + mp_int_t uv = esp_adc_cal_raw_to_voltage(raw, adc_chars) * 1000; + return uv; +} + +STATIC const mp_rom_map_elem_t madcblock_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&madcblock_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&madcblock_connect_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(madcblock_locals_dict, madcblock_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_adcblock_type, + MP_QSTR_ADCBlock, + MP_TYPE_FLAG_NONE, + make_new, madcblock_make_new, + print, madcblock_print, + locals_dict, &madcblock_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/machine_adcblock.h b/microros_ws/extra_packages/micropython/src/machine_adcblock.h new file mode 100644 index 0000000..0500726 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_adcblock.h @@ -0,0 +1,20 @@ +#ifndef MICROPY_INCLUDED_MACHINE_ADCBLOCK_H +#define MICROPY_INCLUDED_MACHINE_ADCBLOCK_H + +#include "esp_adc_cal.h" + +typedef struct _madcblock_obj_t { + mp_obj_base_t base; + adc_unit_t unit_id; + mp_int_t bits; + adc_bits_width_t width; + esp_adc_cal_characteristics_t *characteristics[ADC_ATTEN_MAX]; +} madcblock_obj_t; + +extern madcblock_obj_t madcblock_obj[]; + +extern void madcblock_bits_helper(madcblock_obj_t *self, mp_int_t bits); +extern mp_int_t madcblock_read_helper(madcblock_obj_t *self, adc_channel_t channel_id); +extern mp_int_t madcblock_read_uv_helper(madcblock_obj_t *self, adc_channel_t channel_id, adc_atten_t atten); + +#endif // MICROPY_INCLUDED_MACHINE_ADCBLOCK_H diff --git a/microros_ws/extra_packages/micropython/src/machine_bitstream.c b/microros_ws/extra_packages/micropython/src/machine_bitstream.c new file mode 100644 index 0000000..4284b5f --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_bitstream.c @@ -0,0 +1,210 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mpconfig.h" +#include "py/mphal.h" +#include "modesp32.h" + +#if MICROPY_PY_MACHINE_BITSTREAM + +/******************************************************************************/ +// Bit-bang implementation + +#define NS_TICKS_OVERHEAD (6) + +// This is a translation of the cycle counter implementation in ports/stm32/machine_bitstream.c. +STATIC void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { + uint32_t pin_mask, gpio_reg_set, gpio_reg_clear; + #if !CONFIG_IDF_TARGET_ESP32C3 + if (pin >= 32) { + pin_mask = 1 << (pin - 32); + gpio_reg_set = GPIO_OUT1_W1TS_REG; + gpio_reg_clear = GPIO_OUT1_W1TC_REG; + } else + #endif + { + pin_mask = 1 << pin; + gpio_reg_set = GPIO_OUT_W1TS_REG; + gpio_reg_clear = GPIO_OUT_W1TC_REG; + } + + // Convert ns to cpu ticks [high_time_0, period_0, high_time_1, period_1]. + uint32_t fcpu_mhz = ets_get_cpu_frequency(); + for (size_t i = 0; i < 4; ++i) { + timing_ns[i] = fcpu_mhz * timing_ns[i] / 1000; + if (timing_ns[i] > NS_TICKS_OVERHEAD) { + timing_ns[i] -= NS_TICKS_OVERHEAD; + } + if (i % 2 == 1) { + // Convert low_time to period (i.e. add high_time). + timing_ns[i] += timing_ns[i - 1]; + } + } + + uint32_t irq_state = mp_hal_quiet_timing_enter(); + + for (size_t i = 0; i < len; ++i) { + uint8_t b = buf[i]; + for (size_t j = 0; j < 8; ++j) { + GPIO_REG_WRITE(gpio_reg_set, pin_mask); + uint32_t start_ticks = mp_hal_ticks_cpu(); + uint32_t *t = &timing_ns[b >> 6 & 2]; + while (mp_hal_ticks_cpu() - start_ticks < t[0]) { + ; + } + GPIO_REG_WRITE(gpio_reg_clear, pin_mask); + b <<= 1; + while (mp_hal_ticks_cpu() - start_ticks < t[1]) { + ; + } + } + } + + mp_hal_quiet_timing_exit(irq_state); +} + +/******************************************************************************/ +// RMT implementation + +#include "driver/rmt.h" + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) +// This convenience macro was not available in earlier IDF versions. +#define RMT_DEFAULT_CONFIG_TX(gpio, channel_id) \ + { \ + .rmt_mode = RMT_MODE_TX, \ + .channel = channel_id, \ + .clk_div = 80, \ + .gpio_num = gpio, \ + .mem_block_num = 1, \ + .tx_config = { \ + .loop_en = false, \ + .carrier_freq_hz = 38000, \ + .carrier_duty_percent = 33, \ + .carrier_level = RMT_CARRIER_LEVEL_HIGH, \ + .carrier_en = false, \ + .idle_level = RMT_IDLE_LEVEL_LOW, \ + .idle_output_en = true, \ + } \ + } +#endif + +// Logical 0 and 1 values (encoded as a rmt_item32_t). +// The duration fields will be set later. +STATIC rmt_item32_t bitstream_high_low_0 = {{{ 0, 1, 0, 0 }}}; +STATIC rmt_item32_t bitstream_high_low_1 = {{{ 0, 1, 0, 0 }}}; + +// See https://github.com/espressif/esp-idf/blob/master/examples/common_components/led_strip/led_strip_rmt_ws2812.c +// This is called automatically by the IDF during rmt_write_sample in order to +// convert the byte stream to rmt_item32_t's. +STATIC void IRAM_ATTR bitstream_high_low_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num) { + if (src == NULL || dest == NULL) { + *translated_size = 0; + *item_num = 0; + return; + } + + size_t size = 0; + size_t num = 0; + uint8_t *psrc = (uint8_t *)src; + rmt_item32_t *pdest = dest; + while (size < src_size && num < wanted_num) { + for (int i = 0; i < 8; i++) { + // MSB first + if (*psrc & (1 << (7 - i))) { + pdest->val = bitstream_high_low_1.val; + } else { + pdest->val = bitstream_high_low_0.val; + } + num++; + pdest++; + } + size++; + psrc++; + } + + *translated_size = size; + *item_num = num; +} + +// Use the reserved RMT channel to stream high/low data on the specified pin. +STATIC void machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len, uint8_t channel_id) { + rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel_id); + + // Use 40MHz clock (although 2MHz would probably be sufficient). + config.clk_div = 2; + + // Install the driver on this channel & pin. + check_esp_err(rmt_config(&config)); + check_esp_err(rmt_driver_install_core1(config.channel)); + + // Get the tick rate in kHz (this will likely be 40000). + uint32_t counter_clk_khz = 0; + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) + uint8_t div_cnt; + check_esp_err(rmt_get_clk_div(config.channel, &div_cnt)); + counter_clk_khz = APB_CLK_FREQ / div_cnt; + #else + check_esp_err(rmt_get_counter_clock(config.channel, &counter_clk_khz)); + #endif + + counter_clk_khz /= 1000; + + // Convert nanoseconds to pulse duration. + bitstream_high_low_0.duration0 = (counter_clk_khz * timing_ns[0]) / 1e6; + bitstream_high_low_0.duration1 = (counter_clk_khz * timing_ns[1]) / 1e6; + bitstream_high_low_1.duration0 = (counter_clk_khz * timing_ns[2]) / 1e6; + bitstream_high_low_1.duration1 = (counter_clk_khz * timing_ns[3]) / 1e6; + + // Install the bits->highlow translator. + rmt_translator_init(config.channel, bitstream_high_low_rmt_adapter); + + // Stream the byte data using the translator. + check_esp_err(rmt_write_sample(config.channel, buf, len, true)); + + // Wait 50% longer than we expect (if every bit takes the maximum time). + uint32_t timeout_ms = (3 * len / 2) * (1 + (8 * MAX(timing_ns[0] + timing_ns[1], timing_ns[2] + timing_ns[3])) / 1000); + check_esp_err(rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(timeout_ms))); + + // Uninstall the driver. + check_esp_err(rmt_driver_uninstall(config.channel)); + + // Cancel RMT output to GPIO pin. + gpio_matrix_out(pin, SIG_GPIO_OUT_IDX, false, false); +} + +/******************************************************************************/ +// Interface to machine.bitstream + +void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { + if (esp32_rmt_bitstream_channel_id < 0) { + machine_bitstream_high_low_bitbang(pin, timing_ns, buf, len); + } else { + machine_bitstream_high_low_rmt(pin, timing_ns, buf, len, esp32_rmt_bitstream_channel_id); + } +} + +#endif // MICROPY_PY_MACHINE_BITSTREAM diff --git a/microros_ws/extra_packages/micropython/src/machine_dac.c b/microros_ws/extra_packages/micropython/src/machine_dac.c new file mode 100644 index 0000000..0e85dc9 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_dac.c @@ -0,0 +1,116 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Nick Moore + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include + +#include "py/runtime.h" +#include "py/mphal.h" +#include "modmachine.h" + +#if MICROPY_PY_MACHINE_DAC + +#include "driver/gpio.h" +#include "driver/dac.h" + +typedef struct _mdac_obj_t { + mp_obj_base_t base; + gpio_num_t gpio_id; + dac_channel_t dac_id; +} mdac_obj_t; + +STATIC const mdac_obj_t mdac_obj[] = { + #if CONFIG_IDF_TARGET_ESP32 + {{&machine_dac_type}, GPIO_NUM_25, DAC_CHANNEL_1}, + {{&machine_dac_type}, GPIO_NUM_26, DAC_CHANNEL_2}, + #else + {{&machine_dac_type}, GPIO_NUM_17, DAC_CHANNEL_1}, + {{&machine_dac_type}, GPIO_NUM_18, DAC_CHANNEL_2}, + #endif +}; + +STATIC mp_obj_t mdac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, + const mp_obj_t *args) { + + mp_arg_check_num(n_args, n_kw, 1, 1, true); + gpio_num_t pin_id = machine_pin_get_id(args[0]); + const mdac_obj_t *self = NULL; + for (int i = 0; i < MP_ARRAY_SIZE(mdac_obj); i++) { + if (pin_id == mdac_obj[i].gpio_id) { + self = &mdac_obj[i]; + break; + } + } + if (!self) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid Pin for DAC")); + } + + esp_err_t err = dac_output_enable(self->dac_id); + if (err == ESP_OK) { + err = dac_output_voltage(self->dac_id, 0); + } + if (err == ESP_OK) { + return MP_OBJ_FROM_PTR(self); + } + mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); +} + +STATIC void mdac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + mdac_obj_t *self = self_in; + mp_printf(print, "DAC(Pin(%u))", self->gpio_id); +} + +STATIC mp_obj_t mdac_write(mp_obj_t self_in, mp_obj_t value_in) { + mdac_obj_t *self = self_in; + int value = mp_obj_get_int(value_in); + if (value < 0 || value > 255) { + mp_raise_ValueError(MP_ERROR_TEXT("value out of range")); + } + + esp_err_t err = dac_output_voltage(self->dac_id, value); + if (err == ESP_OK) { + return mp_const_none; + } + mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); +} +MP_DEFINE_CONST_FUN_OBJ_2(mdac_write_obj, mdac_write); + +STATIC const mp_rom_map_elem_t mdac_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mdac_write_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mdac_locals_dict, mdac_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_dac_type, + MP_QSTR_DAC, + MP_TYPE_FLAG_NONE, + make_new, mdac_make_new, + print, mdac_print, + locals_dict, &mdac_locals_dict + ); + +#endif // MICROPY_PY_MACHINE_DAC diff --git a/microros_ws/extra_packages/micropython/src/machine_hw_spi.c b/microros_ws/extra_packages/micropython/src/machine_hw_spi.c new file mode 100644 index 0000000..36f8d3f --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_hw_spi.c @@ -0,0 +1,558 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 "Eric Poulsen" + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mphal.h" +#include "extmod/machine_spi.h" +#include "modmachine.h" + +#include "driver/spi_master.h" + +// SPI mappings by device, naming used by IDF old/new +// upython | ESP32 | ESP32S2 | ESP32S3 | ESP32C3 +// ----------+-----------+-----------+---------+--------- +// SPI(id=1) | HSPI/SPI2 | FSPI/SPI2 | SPI2 | SPI2 +// SPI(id=2) | VSPI/SPI3 | HSPI/SPI3 | SPI3 | err + +// Default pins for SPI(id=1) aka IDF SPI2, can be overridden by a board +#ifndef MICROPY_HW_SPI1_SCK +#ifdef SPI2_IOMUX_PIN_NUM_CLK +// Use IO_MUX pins by default. +// If SPI lines are routed to other pins through GPIO matrix +// routing adds some delay and lower limit applies to SPI clk freq +#define MICROPY_HW_SPI1_SCK SPI2_IOMUX_PIN_NUM_CLK // pin 14 on ESP32 +#define MICROPY_HW_SPI1_MOSI SPI2_IOMUX_PIN_NUM_MOSI // pin 13 on ESP32 +#define MICROPY_HW_SPI1_MISO SPI2_IOMUX_PIN_NUM_MISO // pin 12 on ESP32 +// Only for compatibility with IDF 4.2 and older +#elif CONFIG_IDF_TARGET_ESP32S2 +#define MICROPY_HW_SPI1_SCK FSPI_IOMUX_PIN_NUM_CLK +#define MICROPY_HW_SPI1_MOSI FSPI_IOMUX_PIN_NUM_MOSI +#define MICROPY_HW_SPI1_MISO FSPI_IOMUX_PIN_NUM_MISO +#else +#define MICROPY_HW_SPI1_SCK HSPI_IOMUX_PIN_NUM_CLK +#define MICROPY_HW_SPI1_MOSI HSPI_IOMUX_PIN_NUM_MOSI +#define MICROPY_HW_SPI1_MISO HSPI_IOMUX_PIN_NUM_MISO +#endif +#endif + +// Default pins for SPI(id=2) aka IDF SPI3, can be overridden by a board +#ifndef MICROPY_HW_SPI2_SCK +#if CONFIG_IDF_TARGET_ESP32 +// ESP32 has IO_MUX pins for VSPI/SPI3 lines, use them as defaults +#define MICROPY_HW_SPI2_SCK VSPI_IOMUX_PIN_NUM_CLK // pin 18 +#define MICROPY_HW_SPI2_MOSI VSPI_IOMUX_PIN_NUM_MOSI // pin 23 +#define MICROPY_HW_SPI2_MISO VSPI_IOMUX_PIN_NUM_MISO // pin 19 +#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +// ESP32S2 and S3 uses GPIO matrix for SPI3 pins, no IO_MUX possible +// Set defaults to the pins used by SPI2 in Octal mode +#define MICROPY_HW_SPI2_SCK (36) +#define MICROPY_HW_SPI2_MOSI (35) +#define MICROPY_HW_SPI2_MISO (37) +#endif +#endif + +#define MP_HW_SPI_MAX_XFER_BYTES (4092) +#define MP_HW_SPI_MAX_XFER_BITS (MP_HW_SPI_MAX_XFER_BYTES * 8) // Has to be an even multiple of 8 + +#if CONFIG_IDF_TARGET_ESP32C3 +#define HSPI_HOST SPI2_HOST +#elif CONFIG_IDF_TARGET_ESP32S3 +#define HSPI_HOST SPI3_HOST +#define FSPI_HOST SPI2_HOST +#endif + +typedef struct _machine_hw_spi_default_pins_t { + int8_t sck; + int8_t mosi; + int8_t miso; +} machine_hw_spi_default_pins_t; + +typedef struct _machine_hw_spi_obj_t { + mp_obj_base_t base; + spi_host_device_t host; + uint32_t baudrate; + uint8_t polarity; + uint8_t phase; + uint8_t bits; + uint8_t firstbit; + int8_t sck; + int8_t mosi; + int8_t miso; + spi_device_handle_t spi; + enum { + MACHINE_HW_SPI_STATE_NONE, + MACHINE_HW_SPI_STATE_INIT, + MACHINE_HW_SPI_STATE_DEINIT + } state; +} machine_hw_spi_obj_t; + +// Default pin mappings for the hardware SPI instances +STATIC const machine_hw_spi_default_pins_t machine_hw_spi_default_pins[2] = { + { .sck = MICROPY_HW_SPI1_SCK, .mosi = MICROPY_HW_SPI1_MOSI, .miso = MICROPY_HW_SPI1_MISO }, + #ifdef MICROPY_HW_SPI2_SCK + { .sck = MICROPY_HW_SPI2_SCK, .mosi = MICROPY_HW_SPI2_MOSI, .miso = MICROPY_HW_SPI2_MISO }, + #endif +}; + +// Static objects mapping to HSPI and VSPI hardware peripherals +STATIC machine_hw_spi_obj_t machine_hw_spi_obj[2]; + +STATIC void machine_hw_spi_deinit_internal(machine_hw_spi_obj_t *self) { + switch (spi_bus_remove_device(self->spi)) { + case ESP_ERR_INVALID_ARG: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid configuration")); + return; + + case ESP_ERR_INVALID_STATE: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SPI device already freed")); + return; + } + + switch (spi_bus_free(self->host)) { + case ESP_ERR_INVALID_ARG: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid configuration")); + return; + + case ESP_ERR_INVALID_STATE: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SPI bus already freed")); + return; + } + + int8_t pins[3] = {self->miso, self->mosi, self->sck}; + + for (int i = 0; i < 3; i++) { + if (pins[i] != -1) { + gpio_pad_select_gpio(pins[i]); + gpio_matrix_out(pins[i], SIG_GPIO_OUT_IDX, false, false); + gpio_set_direction(pins[i], GPIO_MODE_INPUT); + } + } +} + +STATIC void machine_hw_spi_init_internal( + machine_hw_spi_obj_t *self, + int8_t host, + int32_t baudrate, + int8_t polarity, + int8_t phase, + int8_t bits, + int8_t firstbit, + int8_t sck, + int8_t mosi, + int8_t miso) { + + // if we're not initialized, then we're + // implicitly 'changed', since this is the init routine + bool changed = self->state != MACHINE_HW_SPI_STATE_INIT; + + esp_err_t ret; + + machine_hw_spi_obj_t old_self = *self; + + if (host != -1 && host != self->host) { + self->host = host; + changed = true; + } + + if (baudrate != -1) { + // calculate the actual clock frequency that the SPI peripheral can produce + baudrate = spi_get_actual_clock(APB_CLK_FREQ, baudrate, 0); + if (baudrate != self->baudrate) { + self->baudrate = baudrate; + changed = true; + } + } + + if (polarity != -1 && polarity != self->polarity) { + self->polarity = polarity; + changed = true; + } + + if (phase != -1 && phase != self->phase) { + self->phase = phase; + changed = true; + } + + if (bits != -1 && bits != self->bits) { + self->bits = bits; + changed = true; + } + + if (firstbit != -1 && firstbit != self->firstbit) { + self->firstbit = firstbit; + changed = true; + } + + if (sck != -2 && sck != self->sck) { + self->sck = sck; + changed = true; + } + + if (mosi != -2 && mosi != self->mosi) { + self->mosi = mosi; + changed = true; + } + + if (miso != -2 && miso != self->miso) { + self->miso = miso; + changed = true; + } + + if (self->host != HSPI_HOST + #ifdef FSPI_HOST + && self->host != FSPI_HOST + #endif + #ifdef VSPI_HOST + && self->host != VSPI_HOST + #endif + ) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), self->host); + } + + if (changed) { + if (self->state == MACHINE_HW_SPI_STATE_INIT) { + self->state = MACHINE_HW_SPI_STATE_DEINIT; + machine_hw_spi_deinit_internal(&old_self); + } + } else { + return; // no changes + } + + spi_bus_config_t buscfg = { + .miso_io_num = self->miso, + .mosi_io_num = self->mosi, + .sclk_io_num = self->sck, + .quadwp_io_num = -1, + .quadhd_io_num = -1 + }; + + spi_device_interface_config_t devcfg = { + .clock_speed_hz = self->baudrate, + .mode = self->phase | (self->polarity << 1), + .spics_io_num = -1, // No CS pin + .queue_size = 2, + .flags = self->firstbit == MICROPY_PY_MACHINE_SPI_LSB ? SPI_DEVICE_TXBIT_LSBFIRST | SPI_DEVICE_RXBIT_LSBFIRST : 0, + .pre_cb = NULL + }; + + // Initialize the SPI bus + + // Select DMA channel based on the hardware SPI host + int dma_chan = 0; + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 + dma_chan = SPI_DMA_CH_AUTO; + #else + if (self->host == HSPI_HOST) { + dma_chan = 1; + } else { + dma_chan = 2; + } + #endif + + ret = spi_bus_initialize(self->host, &buscfg, dma_chan); + switch (ret) { + case ESP_ERR_INVALID_ARG: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid configuration")); + return; + + case ESP_ERR_INVALID_STATE: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SPI host already in use")); + return; + } + + ret = spi_bus_add_device(self->host, &devcfg, &self->spi); + switch (ret) { + case ESP_ERR_INVALID_ARG: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid configuration")); + spi_bus_free(self->host); + return; + + case ESP_ERR_NO_MEM: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("out of memory")); + spi_bus_free(self->host); + return; + + case ESP_ERR_NOT_FOUND: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("no free slots")); + spi_bus_free(self->host); + return; + } + self->state = MACHINE_HW_SPI_STATE_INIT; +} + +STATIC void machine_hw_spi_deinit(mp_obj_base_t *self_in) { + machine_hw_spi_obj_t *self = (machine_hw_spi_obj_t *)self_in; + if (self->state == MACHINE_HW_SPI_STATE_INIT) { + self->state = MACHINE_HW_SPI_STATE_DEINIT; + machine_hw_spi_deinit_internal(self); + } +} + +STATIC mp_uint_t gcd(mp_uint_t x, mp_uint_t y) { + while (x != y) { + if (x > y) { + x -= y; + } else { + y -= x; + } + } + return x; +} + +STATIC void machine_hw_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest) { + machine_hw_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->state == MACHINE_HW_SPI_STATE_DEINIT) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("transfer on deinitialized SPI")); + return; + } + + // Round to nearest whole set of bits + int bits_to_send = len * 8 / self->bits * self->bits; + + if (!bits_to_send) { + mp_raise_ValueError(MP_ERROR_TEXT("buffer too short")); + } + + if (len <= 4) { + spi_transaction_t transaction = { 0 }; + + if (src != NULL) { + memcpy(&transaction.tx_data, src, len); + } + + transaction.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA; + transaction.length = bits_to_send; + spi_device_transmit(self->spi, &transaction); + + if (dest != NULL) { + memcpy(dest, &transaction.rx_data, len); + } + } else { + int offset = 0; + int bits_remaining = bits_to_send; + int optimum_word_size = 8 * self->bits / gcd(8, self->bits); + int max_transaction_bits = MP_HW_SPI_MAX_XFER_BITS / optimum_word_size * optimum_word_size; + spi_transaction_t *transaction, *result, transactions[2]; + int i = 0; + + spi_device_acquire_bus(self->spi, portMAX_DELAY); + + while (bits_remaining) { + transaction = transactions + i++ % 2; + memset(transaction, 0, sizeof(spi_transaction_t)); + + transaction->length = + bits_remaining > max_transaction_bits ? max_transaction_bits : bits_remaining; + + if (src != NULL) { + transaction->tx_buffer = src + offset; + } + if (dest != NULL) { + transaction->rx_buffer = dest + offset; + } + + spi_device_queue_trans(self->spi, transaction, portMAX_DELAY); + bits_remaining -= transaction->length; + + if (offset > 0) { + // wait for previously queued transaction + MP_THREAD_GIL_EXIT(); + spi_device_get_trans_result(self->spi, &result, portMAX_DELAY); + MP_THREAD_GIL_ENTER(); + } + + // doesn't need ceil(); loop ends when bits_remaining is 0 + offset += transaction->length / 8; + } + + // wait for last transaction + MP_THREAD_GIL_EXIT(); + spi_device_get_trans_result(self->spi, &result, portMAX_DELAY); + MP_THREAD_GIL_ENTER(); + spi_device_release_bus(self->spi); + } +} + +/******************************************************************************/ +// MicroPython bindings for hw_spi + +STATIC void machine_hw_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_hw_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "SPI(id=%u, baudrate=%u, polarity=%u, phase=%u, bits=%u, firstbit=%u, sck=%d, mosi=%d, miso=%d)", + self->host, self->baudrate, self->polarity, + self->phase, self->bits, self->firstbit, + self->sck, self->mosi, self->miso); +} + +STATIC void machine_hw_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + machine_hw_spi_obj_t *self = (machine_hw_spi_obj_t *)self_in; + + enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), + allowed_args, args); + int8_t sck, mosi, miso; + + if (args[ARG_sck].u_obj == MP_OBJ_NULL) { + sck = -2; + } else if (args[ARG_sck].u_obj == mp_const_none) { + sck = -1; + } else { + sck = machine_pin_get_id(args[ARG_sck].u_obj); + } + + if (args[ARG_miso].u_obj == MP_OBJ_NULL) { + miso = -2; + } else if (args[ARG_miso].u_obj == mp_const_none) { + miso = -1; + } else { + miso = machine_pin_get_id(args[ARG_miso].u_obj); + } + + if (args[ARG_mosi].u_obj == MP_OBJ_NULL) { + mosi = -2; + } else if (args[ARG_mosi].u_obj == mp_const_none) { + mosi = -1; + } else { + mosi = machine_pin_get_id(args[ARG_mosi].u_obj); + } + + machine_hw_spi_init_internal(self, args[ARG_id].u_int, args[ARG_baudrate].u_int, + args[ARG_polarity].u_int, args[ARG_phase].u_int, args[ARG_bits].u_int, + args[ARG_firstbit].u_int, sck, mosi, miso); +} + +mp_obj_t machine_hw_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args); + + enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 500000} }, + { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, + { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = MICROPY_PY_MACHINE_SPI_MSB} }, + { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + machine_hw_spi_obj_t *self; + const machine_hw_spi_default_pins_t *default_pins; + if (args[ARG_id].u_int == 1) { // SPI2_HOST which is FSPI_HOST on ESP32Sx, HSPI_HOST on others + self = &machine_hw_spi_obj[0]; + default_pins = &machine_hw_spi_default_pins[0]; + } else { + self = &machine_hw_spi_obj[1]; + default_pins = &machine_hw_spi_default_pins[1]; + } + self->base.type = &machine_spi_type; + + int8_t sck, mosi, miso; + + if (args[ARG_sck].u_obj == MP_OBJ_NULL) { + sck = default_pins->sck; + } else if (args[ARG_sck].u_obj == mp_const_none) { + sck = -1; + } else { + sck = machine_pin_get_id(args[ARG_sck].u_obj); + } + + if (args[ARG_mosi].u_obj == MP_OBJ_NULL) { + mosi = default_pins->mosi; + } else if (args[ARG_mosi].u_obj == mp_const_none) { + mosi = -1; + } else { + mosi = machine_pin_get_id(args[ARG_mosi].u_obj); + } + + if (args[ARG_miso].u_obj == MP_OBJ_NULL) { + miso = default_pins->miso; + } else if (args[ARG_miso].u_obj == mp_const_none) { + miso = -1; + } else { + miso = machine_pin_get_id(args[ARG_miso].u_obj); + } + + machine_hw_spi_init_internal( + self, + args[ARG_id].u_int, + args[ARG_baudrate].u_int, + args[ARG_polarity].u_int, + args[ARG_phase].u_int, + args[ARG_bits].u_int, + args[ARG_firstbit].u_int, + sck, + mosi, + miso); + + return MP_OBJ_FROM_PTR(self); +} + +spi_host_device_t machine_hw_spi_get_host(mp_obj_t in) { + if (mp_obj_get_type(in) != &machine_spi_type) { + mp_raise_ValueError(MP_ERROR_TEXT("expecting a SPI object")); + } + machine_hw_spi_obj_t *self = (machine_hw_spi_obj_t *)in; + return self->host; +} + +STATIC const mp_machine_spi_p_t machine_hw_spi_p = { + .init = machine_hw_spi_init, + .deinit = machine_hw_spi_deinit, + .transfer = machine_hw_spi_transfer, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + machine_spi_type, + MP_QSTR_SPI, + MP_TYPE_FLAG_NONE, + make_new, machine_hw_spi_make_new, + print, machine_hw_spi_print, + protocol, &machine_hw_spi_p, + locals_dict, &mp_machine_spi_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/machine_i2c.c b/microros_ws/extra_packages/micropython/src/machine_i2c.c new file mode 100644 index 0000000..c805dab --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_i2c.c @@ -0,0 +1,219 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/mperrno.h" +#include "extmod/machine_i2c.h" +#include "modmachine.h" + +#include "driver/i2c.h" + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) +#include "hal/i2c_ll.h" +#else +#include "soc/i2c_reg.h" +#define I2C_LL_MAX_TIMEOUT I2C_TIME_OUT_REG_V +#endif + +#ifndef MICROPY_HW_I2C0_SCL +#define MICROPY_HW_I2C0_SCL (GPIO_NUM_18) +#define MICROPY_HW_I2C0_SDA (GPIO_NUM_19) +#endif + +#ifndef MICROPY_HW_I2C1_SCL +#if CONFIG_IDF_TARGET_ESP32 +#define MICROPY_HW_I2C1_SCL (GPIO_NUM_25) +#define MICROPY_HW_I2C1_SDA (GPIO_NUM_26) +#else +#define MICROPY_HW_I2C1_SCL (GPIO_NUM_9) +#define MICROPY_HW_I2C1_SDA (GPIO_NUM_8) +#endif +#endif + +#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 +#define I2C_SCLK_FREQ XTAL_CLK_FREQ +#elif CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 +#define I2C_SCLK_FREQ I2C_APB_CLK_FREQ +#else +#error "unsupported I2C for ESP32 SoC variant" +#endif + +#define I2C_DEFAULT_TIMEOUT_US (10000) // 10ms + +typedef struct _machine_hw_i2c_obj_t { + mp_obj_base_t base; + i2c_port_t port : 8; + gpio_num_t scl : 8; + gpio_num_t sda : 8; +} machine_hw_i2c_obj_t; + +STATIC machine_hw_i2c_obj_t machine_hw_i2c_obj[I2C_NUM_MAX]; + +STATIC void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, uint32_t freq, uint32_t timeout_us, bool first_init) { + if (!first_init) { + i2c_driver_delete(self->port); + } + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = self->sda, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = self->scl, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = freq, + }; + i2c_param_config(self->port, &conf); + int timeout = I2C_SCLK_FREQ / 1000000 * timeout_us; + i2c_set_timeout(self->port, (timeout > I2C_LL_MAX_TIMEOUT) ? I2C_LL_MAX_TIMEOUT : timeout); + i2c_driver_install(self->port, I2C_MODE_MASTER, 0, 0, 0); +} + +int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { + machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + int data_len = 0; + + if (flags & MP_MACHINE_I2C_FLAG_WRITE1) { + i2c_master_start(cmd); + i2c_master_write_byte(cmd, addr << 1, true); + i2c_master_write(cmd, bufs->buf, bufs->len, true); + data_len += bufs->len; + --n; + ++bufs; + } + + i2c_master_start(cmd); + i2c_master_write_byte(cmd, addr << 1 | (flags & MP_MACHINE_I2C_FLAG_READ), true); + + for (; n--; ++bufs) { + if (flags & MP_MACHINE_I2C_FLAG_READ) { + i2c_master_read(cmd, bufs->buf, bufs->len, n == 0 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK); + } else { + if (bufs->len != 0) { + i2c_master_write(cmd, bufs->buf, bufs->len, true); + } + } + data_len += bufs->len; + } + + if (flags & MP_MACHINE_I2C_FLAG_STOP) { + i2c_master_stop(cmd); + } + + // TODO proper timeout + esp_err_t err = i2c_master_cmd_begin(self->port, cmd, 100 * (1 + data_len) / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + + if (err == ESP_FAIL) { + return -MP_ENODEV; + } else if (err == ESP_ERR_TIMEOUT) { + return -MP_ETIMEDOUT; + } else if (err != ESP_OK) { + return -abs(err); + } + + return data_len; +} + +/******************************************************************************/ +// MicroPython bindings for machine API + +STATIC void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + int h, l; + i2c_get_period(self->port, &h, &l); + mp_printf(print, "I2C(%u, scl=%u, sda=%u, freq=%u)", + self->port, self->scl, self->sda, I2C_SCLK_FREQ / (h + l)); +} + +mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); + + // Parse args + enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = I2C_DEFAULT_TIMEOUT_US} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get I2C bus + mp_int_t i2c_id = mp_obj_get_int(args[ARG_id].u_obj); + if (!(I2C_NUM_0 <= i2c_id && i2c_id < I2C_NUM_MAX)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); + } + + // Get static peripheral object + machine_hw_i2c_obj_t *self = (machine_hw_i2c_obj_t *)&machine_hw_i2c_obj[i2c_id]; + + bool first_init = false; + if (self->base.type == NULL) { + // Created for the first time, set default pins + self->base.type = &machine_i2c_type; + self->port = i2c_id; + if (self->port == I2C_NUM_0) { + self->scl = MICROPY_HW_I2C0_SCL; + self->sda = MICROPY_HW_I2C0_SDA; + } else { + self->scl = MICROPY_HW_I2C1_SCL; + self->sda = MICROPY_HW_I2C1_SDA; + } + first_init = true; + } + + // Set SCL/SDA pins if given + if (args[ARG_scl].u_obj != MP_OBJ_NULL) { + self->scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); + } + if (args[ARG_sda].u_obj != MP_OBJ_NULL) { + self->sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); + } + + // Initialise the I2C peripheral + machine_hw_i2c_init(self, args[ARG_freq].u_int, args[ARG_timeout].u_int, first_init); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC const mp_machine_i2c_p_t machine_hw_i2c_p = { + .transfer_supports_write1 = true, + .transfer = machine_hw_i2c_transfer, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + machine_i2c_type, + MP_QSTR_I2C, + MP_TYPE_FLAG_NONE, + make_new, machine_hw_i2c_make_new, + print, machine_hw_i2c_print, + protocol, &machine_hw_i2c_p, + locals_dict, &mp_machine_i2c_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/machine_i2s.c b/microros_ws/extra_packages/micropython/src/machine_i2s.c new file mode 100644 index 0000000..ce1cb59 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_i2s.c @@ -0,0 +1,846 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Mike Teachman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/misc.h" +#include "py/stream.h" +#include "py/objstr.h" +#include "modmachine.h" +#include "mphalport.h" + +#if MICROPY_PY_MACHINE_I2S + +#include "driver/i2s.h" +#include "soc/i2s_reg.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_task.h" + +// The I2S module has 3 modes of operation: +// +// Mode1: Blocking +// - readinto() and write() methods block until the supplied buffer is filled (read) or emptied (write) +// - this is the default mode of operation +// +// Mode2: Non-Blocking +// - readinto() and write() methods return immediately. +// - buffer filling and emptying happens asynchronously to the main MicroPython task +// - a callback function is called when the supplied buffer has been filled (read) or emptied (write) +// - non-blocking mode is enabled when a callback is set with the irq() method +// - a FreeRTOS task is created to implement the asynchronous background operations +// - a FreeRTOS queue is used to transfer the supplied buffer to the background task +// +// Mode3: Uasyncio +// - implements the stream protocol +// - uasyncio mode is enabled when the ioctl() function is called +// - the I2S event queue is used to detect that I2S samples can be read or written from/to DMA memory +// +// The samples contained in the app buffer supplied for the readinto() and write() methods have the following convention: +// Mono: little endian format +// Stereo: little endian format, left channel first +// +// I2S terms: +// "frame": consists of two audio samples (Left audio sample + Right audio sample) +// +// Misc: +// - for Mono configuration: +// - readinto method: samples are gathered from the L channel only +// - write method: every sample is output to both the L and R channels +// - for readinto method the I2S hardware is read using 8-byte frames +// (this is standard for almost all I2S hardware, such as MEMS microphones) +// - all sample data transfers use DMA + +#define I2S_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) +#define I2S_TASK_STACK_SIZE (2048) + +#define DMA_BUF_LEN_IN_I2S_FRAMES (256) + +// The transform buffer is used with the readinto() method to bridge the opaque DMA memory on the ESP devices +// with the app buffer. It facilitates audio sample transformations. e.g. 32-bits samples to 16-bit samples. +// The size of 240 bytes is an engineering optimum that balances transfer performance with an acceptable use of heap space +#define SIZEOF_TRANSFORM_BUFFER_IN_BYTES (240) + +#define NUM_I2S_USER_FORMATS (4) +#define I2S_RX_FRAME_SIZE_IN_BYTES (8) + +typedef enum { + MONO, + STEREO +} format_t; + +typedef enum { + BLOCKING, + NON_BLOCKING, + UASYNCIO +} io_mode_t; + +typedef enum { + I2S_TX_TRANSFER, + I2S_RX_TRANSFER, +} direction_t; + +typedef struct _non_blocking_descriptor_t { + mp_buffer_info_t appbuf; + mp_obj_t callback; + direction_t direction; +} non_blocking_descriptor_t; + +typedef struct _machine_i2s_obj_t { + mp_obj_base_t base; + i2s_port_t port; + mp_hal_pin_obj_t sck; + mp_hal_pin_obj_t ws; + mp_hal_pin_obj_t sd; + int8_t mode; + i2s_bits_per_sample_t bits; + format_t format; + int32_t rate; + int32_t ibuf; + mp_obj_t callback_for_non_blocking; + io_mode_t io_mode; + uint8_t transform_buffer[SIZEOF_TRANSFORM_BUFFER_IN_BYTES]; + QueueHandle_t i2s_event_queue; + QueueHandle_t non_blocking_mode_queue; + TaskHandle_t non_blocking_mode_task; +} machine_i2s_obj_t; + +STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in); + +// The frame map is used with the readinto() method to transform the audio sample data coming +// from DMA memory (32-bit stereo, with the L and R channels reversed) to the format specified +// in the I2S constructor. e.g. 16-bit mono +STATIC const int8_t i2s_frame_map[NUM_I2S_USER_FORMATS][I2S_RX_FRAME_SIZE_IN_BYTES] = { + { 6, 7, -1, -1, -1, -1, -1, -1 }, // Mono, 16-bits + { 4, 5, 6, 7, -1, -1, -1, -1 }, // Mono, 32-bits + { 6, 7, 2, 3, -1, -1, -1, -1 }, // Stereo, 16-bits + { 4, 5, 6, 7, 0, 1, 2, 3 }, // Stereo, 32-bits +}; + +void machine_i2s_init0() { + for (i2s_port_t p = 0; p < I2S_NUM_MAX; p++) { + MP_STATE_PORT(machine_i2s_obj)[p] = NULL; + } +} + +// The following function takes a sample buffer and swaps L/R channels +// +// Background: For 32-bit stereo, the ESP-IDF API has a L/R channel orientation that breaks +// convention with other ESP32 channel formats +// +// appbuf[] = [L_0-7, L_8-15, L_16-23, L_24-31, R_0-7, R_8-15, R_16-23, R_24-31] = [Left channel, Right channel] +// dma[] = [R_0-7, R_8-15, R_16-23, R_24-31, L_0-7, L_8-15, L_16-23, L_24-31] = [Right channel, Left channel] +// +// where: +// L_0-7 is the least significant byte of the 32 bit sample in the Left channel +// L_24-31 is the most significant byte of the 32 bit sample in the Left channel +// +// Example: +// +// appbuf[] = [0x99, 0xBB, 0x11, 0x22, 0x44, 0x55, 0xAB, 0x77] = [Left channel, Right channel] +// dma[] = [0x44, 0x55, 0xAB, 0x77, 0x99, 0xBB, 0x11, 0x22] = [Right channel, Left channel] +// where: +// LEFT Channel = 0x99, 0xBB, 0x11, 0x22 +// RIGHT Channel = 0x44, 0x55, 0xAB, 0x77 +// +// samples in appbuf are in little endian format: +// 0x77 is the most significant byte of the 32-bit sample +// 0x44 is the least significant byte of the 32-bit sample +STATIC void swap_32_bit_stereo_channels(mp_buffer_info_t *bufinfo) { + int32_t swap_sample; + int32_t *sample = bufinfo->buf; + uint32_t num_samples = bufinfo->len / 4; + for (uint32_t i = 0; i < num_samples; i += 2) { + swap_sample = sample[i + 1]; + sample[i + 1] = sample[i]; + sample[i] = swap_sample; + } +} + +STATIC int8_t get_frame_mapping_index(i2s_bits_per_sample_t bits, format_t format) { + if (format == MONO) { + if (bits == I2S_BITS_PER_SAMPLE_16BIT) { + return 0; + } else { // 32 bits + return 1; + } + } else { // STEREO + if (bits == I2S_BITS_PER_SAMPLE_16BIT) { + return 2; + } else { // 32 bits + return 3; + } + } +} + +STATIC i2s_bits_per_sample_t get_dma_bits(uint8_t mode, i2s_bits_per_sample_t bits) { + if (mode == (I2S_MODE_MASTER | I2S_MODE_TX)) { + return bits; + } else { // Master Rx + // read 32 bit samples for I2S hardware. e.g. MEMS microphones + return I2S_BITS_PER_SAMPLE_32BIT; + } +} + +STATIC i2s_channel_fmt_t get_dma_format(uint8_t mode, format_t format) { + if (mode == (I2S_MODE_MASTER | I2S_MODE_TX)) { + if (format == MONO) { + return I2S_CHANNEL_FMT_ONLY_LEFT; + } else { // STEREO + return I2S_CHANNEL_FMT_RIGHT_LEFT; + } + } else { // Master Rx + // read stereo frames for all I2S hardware + return I2S_CHANNEL_FMT_RIGHT_LEFT; + } +} + +STATIC uint32_t get_dma_buf_count(uint8_t mode, i2s_bits_per_sample_t bits, format_t format, int32_t ibuf) { + // calculate how many DMA buffers need to be allocated + uint32_t dma_frame_size_in_bytes = + (get_dma_bits(mode, bits) / 8) * (get_dma_format(mode, format) == I2S_CHANNEL_FMT_RIGHT_LEFT ? 2: 1); + + uint32_t dma_buf_count = ibuf / (DMA_BUF_LEN_IN_I2S_FRAMES * dma_frame_size_in_bytes); + + return dma_buf_count; +} + +STATIC uint32_t fill_appbuf_from_dma(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) { + + // copy audio samples from DMA memory to the app buffer + // audio samples are read from DMA memory in chunks + // loop, reading and copying chunks until the app buffer is filled + // For uasyncio mode, the loop will make an early exit if DMA memory becomes empty + // Example: + // a MicroPython I2S object is configured for 16-bit mono (2 bytes per audio sample). + // For every frame coming from DMA (8 bytes), 2 bytes are "cherry picked" and + // copied to the supplied app buffer. + // Thus, for every 1 byte copied to the app buffer, 4 bytes are read from DMA memory. + // If a 8kB app buffer is supplied, 32kB of audio samples is read from DMA memory. + + uint32_t a_index = 0; + uint8_t *app_p = appbuf->buf; + uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1); + uint32_t num_bytes_needed_from_dma = appbuf->len * (I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes); + while (num_bytes_needed_from_dma) { + size_t num_bytes_requested_from_dma = MIN(sizeof(self->transform_buffer), num_bytes_needed_from_dma); + size_t num_bytes_received_from_dma = 0; + + TickType_t delay; + if (self->io_mode == UASYNCIO) { + delay = 0; // stop i2s_read() operation if DMA memory becomes empty + } else { + delay = portMAX_DELAY; // block until supplied buffer is filled + } + + esp_err_t ret = i2s_read( + self->port, + self->transform_buffer, + num_bytes_requested_from_dma, + &num_bytes_received_from_dma, + delay); + + // the following is a workaround for a bug in ESP-IDF v4.4 + // https://github.com/espressif/esp-idf/issues/8121 + #if (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 4) + if ((delay != portMAX_DELAY) && (ret == ESP_ERR_TIMEOUT)) { + ret = ESP_OK; + } + #endif + + check_esp_err(ret); + + // process the transform buffer one frame at a time. + // copy selected bytes from the transform buffer into the user supplied appbuf. + // Example: + // a MicroPython I2S object is configured for 16-bit mono. This configuration associates to + // a frame map index of 0 = { 6, 7, -1, -1, -1, -1, -1, -1 } in the i2s_frame_map array + // This mapping indicates: + // appbuf[x+0] = frame[6] + // appbuf[x+1] = frame[7] + // frame bytes 0-5 are not used + + uint32_t t_index = 0; + uint8_t f_index = get_frame_mapping_index(self->bits, self->format); + while (t_index < num_bytes_received_from_dma) { + uint8_t *transform_p = self->transform_buffer + t_index; + + for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) { + int8_t t_to_a_mapping = i2s_frame_map[f_index][i]; + if (t_to_a_mapping != -1) { + *app_p++ = transform_p[t_to_a_mapping]; + a_index++; + } + t_index++; + } + } + + num_bytes_needed_from_dma -= num_bytes_received_from_dma; + + if ((self->io_mode == UASYNCIO) && (num_bytes_received_from_dma < num_bytes_requested_from_dma)) { + // Unable to fill the entire app buffer from DMA memory. This indicates all DMA RX buffers are empty. + // Clear the I2S event queue so ioctl() indicates that the I2S object cannot currently + // supply more audio samples + xQueueReset(self->i2s_event_queue); + break; + } + } + + return a_index; +} + +STATIC size_t copy_appbuf_to_dma(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) { + if ((self->bits == I2S_BITS_PER_SAMPLE_32BIT) && (self->format == STEREO)) { + swap_32_bit_stereo_channels(appbuf); + } + + size_t num_bytes_written = 0; + + TickType_t delay; + if (self->io_mode == UASYNCIO) { + delay = 0; // stop i2s_write() operation if DMA memory becomes full + } else { + delay = portMAX_DELAY; // block until supplied buffer is emptied + } + + esp_err_t ret = i2s_write(self->port, appbuf->buf, appbuf->len, &num_bytes_written, delay); + + // the following is a workaround for a bug in ESP-IDF v4.4 + // https://github.com/espressif/esp-idf/issues/8121 + #if (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 4) + if ((delay != portMAX_DELAY) && (ret == ESP_ERR_TIMEOUT)) { + ret = ESP_OK; + } + #endif + + check_esp_err(ret); + + if ((self->io_mode == UASYNCIO) && (num_bytes_written < appbuf->len)) { + // Unable to empty the entire app buffer into DMA memory. This indicates all DMA TX buffers are full. + // Clear the I2S event queue so ioctl() indicates that the I2S object cannot currently + // accept more audio samples + xQueueReset(self->i2s_event_queue); + + // Undo the swap transformation as the buffer has not been completely emptied. + // The uasyncio stream writer will use the same buffer in a future write call. + if ((self->bits == I2S_BITS_PER_SAMPLE_32BIT) && (self->format == STEREO)) { + swap_32_bit_stereo_channels(appbuf); + } + } + return num_bytes_written; +} + +// FreeRTOS task used for non-blocking mode +STATIC void task_for_non_blocking_mode(void *self_in) { + machine_i2s_obj_t *self = (machine_i2s_obj_t *)self_in; + + non_blocking_descriptor_t descriptor; + + for (;;) { + if (xQueueReceive(self->non_blocking_mode_queue, &descriptor, portMAX_DELAY)) { + if (descriptor.direction == I2S_TX_TRANSFER) { + copy_appbuf_to_dma(self, &descriptor.appbuf); + } else { // RX + fill_appbuf_from_dma(self, &descriptor.appbuf); + } + mp_sched_schedule(descriptor.callback, MP_OBJ_FROM_PTR(self)); + } + } +} + +STATIC void machine_i2s_init_helper(machine_i2s_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + + enum { + ARG_sck, + ARG_ws, + ARG_sd, + ARG_mode, + ARG_bits, + ARG_format, + ARG_rate, + ARG_ibuf, + }; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_ws, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_sd, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_format, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_ibuf, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // + // ---- Check validity of arguments ---- + // + + // are Pins valid? + int8_t sck = args[ARG_sck].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sck].u_obj); + int8_t ws = args[ARG_ws].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_ws].u_obj); + int8_t sd = args[ARG_sd].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sd].u_obj); + + // is Mode valid? + i2s_mode_t mode = args[ARG_mode].u_int; + if ((mode != (I2S_MODE_MASTER | I2S_MODE_RX)) && + (mode != (I2S_MODE_MASTER | I2S_MODE_TX))) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid mode")); + } + + // is Bits valid? + i2s_bits_per_sample_t bits = args[ARG_bits].u_int; + if ((bits != I2S_BITS_PER_SAMPLE_16BIT) && + (bits != I2S_BITS_PER_SAMPLE_32BIT)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); + } + + // is Format valid? + format_t format = args[ARG_format].u_int; + if ((format != STEREO) && + (format != MONO)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid format")); + } + + // is Rate valid? + // Not checked: ESP-IDF I2S API does not indicate a valid range for sample rate + + // is Ibuf valid? + // Not checked: ESP-IDF I2S API will return error if requested buffer size exceeds available memory + + self->sck = sck; + self->ws = ws; + self->sd = sd; + self->mode = mode; + self->bits = bits; + self->format = format; + self->rate = args[ARG_rate].u_int; + self->ibuf = args[ARG_ibuf].u_int; + self->callback_for_non_blocking = MP_OBJ_NULL; + self->i2s_event_queue = NULL; + self->non_blocking_mode_queue = NULL; + self->non_blocking_mode_task = NULL; + self->io_mode = BLOCKING; + + i2s_config_t i2s_config; + i2s_config.communication_format = I2S_COMM_FORMAT_I2S; + i2s_config.mode = mode; + i2s_config.bits_per_sample = get_dma_bits(mode, bits); + i2s_config.channel_format = get_dma_format(mode, format); + i2s_config.sample_rate = self->rate; + i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LOWMED; + i2s_config.dma_buf_count = get_dma_buf_count(mode, bits, format, self->ibuf); + i2s_config.dma_buf_len = DMA_BUF_LEN_IN_I2S_FRAMES; + i2s_config.use_apll = false; + i2s_config.tx_desc_auto_clear = true; + i2s_config.fixed_mclk = 0; + #if (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 4) + i2s_config.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT; + i2s_config.bits_per_chan = 0; + #endif + + // I2S queue size equals the number of DMA buffers + check_esp_err(i2s_driver_install(self->port, &i2s_config, i2s_config.dma_buf_count, &self->i2s_event_queue)); + + // apply low-level workaround for bug in some ESP-IDF versions that swap + // the left and right channels + // https://github.com/espressif/esp-idf/issues/6625 + #if CONFIG_IDF_TARGET_ESP32S3 + REG_SET_BIT(I2S_TX_CONF_REG(self->port), I2S_TX_MSB_SHIFT); + REG_SET_BIT(I2S_TX_CONF_REG(self->port), I2S_RX_MSB_SHIFT); + #else + REG_SET_BIT(I2S_CONF_REG(self->port), I2S_TX_MSB_RIGHT); + REG_SET_BIT(I2S_CONF_REG(self->port), I2S_RX_MSB_RIGHT); + #endif + + i2s_pin_config_t pin_config; + #if (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 4) + pin_config.mck_io_num = I2S_PIN_NO_CHANGE; + #endif + pin_config.bck_io_num = self->sck; + pin_config.ws_io_num = self->ws; + + if (mode == (I2S_MODE_MASTER | I2S_MODE_RX)) { + pin_config.data_in_num = self->sd; + pin_config.data_out_num = I2S_PIN_NO_CHANGE; + } else { // TX + pin_config.data_in_num = I2S_PIN_NO_CHANGE; + pin_config.data_out_num = self->sd; + } + + check_esp_err(i2s_set_pin(self->port, &pin_config)); +} + +STATIC void machine_i2s_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2S(id=%u,\n" + "sck="MP_HAL_PIN_FMT ",\n" + "ws="MP_HAL_PIN_FMT ",\n" + "sd="MP_HAL_PIN_FMT ",\n" + "mode=%u,\n" + "bits=%u, format=%u,\n" + "rate=%d, ibuf=%d)", + self->port, + mp_hal_pin_name(self->sck), + mp_hal_pin_name(self->ws), + mp_hal_pin_name(self->sd), + self->mode, + self->bits, self->format, + self->rate, self->ibuf + ); +} + +STATIC mp_obj_t machine_i2s_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { + mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); + + i2s_port_t port = mp_obj_get_int(args[0]); + if (port < 0 || port >= I2S_NUM_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid id")); + } + + machine_i2s_obj_t *self; + if (MP_STATE_PORT(machine_i2s_obj)[port] == NULL) { + self = m_new_obj_with_finaliser(machine_i2s_obj_t); + self->base.type = &machine_i2s_type; + MP_STATE_PORT(machine_i2s_obj)[port] = self; + self->port = port; + } else { + self = MP_STATE_PORT(machine_i2s_obj)[port]; + machine_i2s_deinit(self); + } + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); + machine_i2s_init_helper(self, n_pos_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t machine_i2s_obj_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + machine_i2s_obj_t *self = pos_args[0]; + machine_i2s_deinit(self); + machine_i2s_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_init_obj, 1, machine_i2s_obj_init); + +STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + i2s_driver_uninstall(self->port); + + if (self->non_blocking_mode_task != NULL) { + vTaskDelete(self->non_blocking_mode_task); + self->non_blocking_mode_task = NULL; + } + + if (self->non_blocking_mode_queue != NULL) { + vQueueDelete(self->non_blocking_mode_queue); + self->non_blocking_mode_queue = NULL; + } + + self->i2s_event_queue = NULL; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_i2s_deinit_obj, machine_i2s_deinit); + +STATIC mp_obj_t machine_i2s_irq(mp_obj_t self_in, mp_obj_t handler) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (handler != mp_const_none && !mp_obj_is_callable(handler)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid callback")); + } + + if (handler != mp_const_none) { + self->io_mode = NON_BLOCKING; + + // create a queue linking the MicroPython task to a FreeRTOS task + // that manages the non blocking mode of operation + self->non_blocking_mode_queue = xQueueCreate(1, sizeof(non_blocking_descriptor_t)); + + // non-blocking mode requires a background FreeRTOS task + if (xTaskCreatePinnedToCore( + task_for_non_blocking_mode, + "i2s_non_blocking", + I2S_TASK_STACK_SIZE, + self, + I2S_TASK_PRIORITY, + (TaskHandle_t *)&self->non_blocking_mode_task, + MP_TASK_COREID) != pdPASS) { + + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create I2S task")); + } + } else { + if (self->non_blocking_mode_task != NULL) { + vTaskDelete(self->non_blocking_mode_task); + self->non_blocking_mode_task = NULL; + } + + if (self->non_blocking_mode_queue != NULL) { + vQueueDelete(self->non_blocking_mode_queue); + self->non_blocking_mode_queue = NULL; + } + + self->io_mode = BLOCKING; + } + + self->callback_for_non_blocking = handler; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_irq_obj, machine_i2s_irq); + +// Shift() is typically used as a volume control. +// shift=1 increases volume by 6dB, shift=-1 decreases volume by 6dB +STATIC mp_obj_t machine_i2s_shift(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buf, ARG_bits, ARG_shift}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_bits, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_shift, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_RW); + + int16_t *buf_16 = bufinfo.buf; + int32_t *buf_32 = bufinfo.buf; + + uint8_t bits = args[ARG_bits].u_int; + int8_t shift = args[ARG_shift].u_int; + + uint32_t num_audio_samples; + switch (bits) { + case 16: + num_audio_samples = bufinfo.len / 2; + break; + + case 32: + num_audio_samples = bufinfo.len / 4; + break; + + default: + mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); + break; + } + + for (uint32_t i = 0; i < num_audio_samples; i++) { + switch (bits) { + case 16: + if (shift >= 0) { + buf_16[i] = buf_16[i] << shift; + } else { + buf_16[i] = buf_16[i] >> abs(shift); + } + break; + case 32: + if (shift >= 0) { + buf_32[i] = buf_32[i] << shift; + } else { + buf_32[i] = buf_32[i] >> abs(shift); + } + break; + } + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_shift_fun_obj, 0, machine_i2s_shift); +STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(machine_i2s_shift_obj, MP_ROM_PTR(&machine_i2s_shift_fun_obj)); + +STATIC const mp_rom_map_elem_t machine_i2s_locals_dict_table[] = { + // Methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_i2s_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2s_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_i2s_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_i2s_deinit_obj) }, + + // Static method + { MP_ROM_QSTR(MP_QSTR_shift), MP_ROM_PTR(&machine_i2s_shift_obj) }, + + // Constants + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_INT(I2S_MODE_MASTER | I2S_MODE_RX) }, + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_INT(I2S_MODE_MASTER | I2S_MODE_TX) }, + { MP_ROM_QSTR(MP_QSTR_STEREO), MP_ROM_INT(STEREO) }, + { MP_ROM_QSTR(MP_QSTR_MONO), MP_ROM_INT(MONO) }, +}; +MP_DEFINE_CONST_DICT(machine_i2s_locals_dict, machine_i2s_locals_dict_table); + +STATIC mp_uint_t machine_i2s_stream_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->mode != (I2S_MODE_MASTER | I2S_MODE_RX)) { + *errcode = MP_EPERM; + return MP_STREAM_ERROR; + } + + uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1); + if (size % appbuf_sample_size_in_bytes != 0) { + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } + + if (size == 0) { + return 0; + } + + if (self->io_mode == NON_BLOCKING) { + non_blocking_descriptor_t descriptor; + descriptor.appbuf.buf = (void *)buf_in; + descriptor.appbuf.len = size; + descriptor.callback = self->callback_for_non_blocking; + descriptor.direction = I2S_RX_TRANSFER; + // send the descriptor to the task that handles non-blocking mode + xQueueSend(self->non_blocking_mode_queue, &descriptor, 0); + return size; + } else { // blocking or uasyncio mode + mp_buffer_info_t appbuf; + appbuf.buf = (void *)buf_in; + appbuf.len = size; + uint32_t num_bytes_read = fill_appbuf_from_dma(self, &appbuf); + return num_bytes_read; + } +} + +STATIC mp_uint_t machine_i2s_stream_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->mode != (I2S_MODE_MASTER | I2S_MODE_TX)) { + *errcode = MP_EPERM; + return MP_STREAM_ERROR; + } + + if (size == 0) { + return 0; + } + + if (self->io_mode == NON_BLOCKING) { + non_blocking_descriptor_t descriptor; + descriptor.appbuf.buf = (void *)buf_in; + descriptor.appbuf.len = size; + descriptor.callback = self->callback_for_non_blocking; + descriptor.direction = I2S_TX_TRANSFER; + // send the descriptor to the task that handles non-blocking mode + xQueueSend(self->non_blocking_mode_queue, &descriptor, 0); + return size; + } else { // blocking or uasyncio mode + mp_buffer_info_t appbuf; + appbuf.buf = (void *)buf_in; + appbuf.len = size; + size_t num_bytes_written = copy_appbuf_to_dma(self, &appbuf); + return num_bytes_written; + } +} + +STATIC mp_uint_t machine_i2s_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_uint_t ret; + mp_uint_t flags = arg; + self->io_mode = UASYNCIO; // a call to ioctl() is an indication that uasyncio is being used + + if (request == MP_STREAM_POLL) { + ret = 0; + + if (flags & MP_STREAM_POLL_RD) { + if (self->mode != (I2S_MODE_MASTER | I2S_MODE_RX)) { + *errcode = MP_EPERM; + return MP_STREAM_ERROR; + } + + i2s_event_t i2s_event; + + // check event queue to determine if a DMA buffer has been filled + // (which is an indication that at least one DMA buffer is available to be read) + // note: timeout = 0 so the call is non-blocking + if (xQueueReceive(self->i2s_event_queue, &i2s_event, 0)) { + if (i2s_event.type == I2S_EVENT_RX_DONE) { + // getting here means that at least one DMA buffer is now full + // indicating that audio samples can be read from the I2S object + ret |= MP_STREAM_POLL_RD; + } + } + } + + if (flags & MP_STREAM_POLL_WR) { + if (self->mode != (I2S_MODE_MASTER | I2S_MODE_TX)) { + *errcode = MP_EPERM; + return MP_STREAM_ERROR; + } + + i2s_event_t i2s_event; + + // check event queue to determine if a DMA buffer has been emptied + // (which is an indication that at least one DMA buffer is available to be written) + // note: timeout = 0 so the call is non-blocking + if (xQueueReceive(self->i2s_event_queue, &i2s_event, 0)) { + if (i2s_event.type == I2S_EVENT_TX_DONE) { + // getting here means that at least one DMA buffer is now empty + // indicating that audio samples can be written to the I2S object + ret |= MP_STREAM_POLL_WR; + } + } + } + } else { + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + + return ret; +} + +STATIC const mp_stream_p_t i2s_stream_p = { + .read = machine_i2s_stream_read, + .write = machine_i2s_stream_write, + .ioctl = machine_i2s_ioctl, + .is_text = false, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + machine_i2s_type, + MP_QSTR_I2S, + MP_TYPE_FLAG_ITER_IS_STREAM, + make_new, machine_i2s_make_new, + print, machine_i2s_print, + protocol, &i2s_stream_p, + locals_dict, &machine_i2s_locals_dict + ); + +MP_REGISTER_ROOT_POINTER(struct _machine_i2s_obj_t *machine_i2s_obj[I2S_NUM_MAX]); + +#endif // MICROPY_PY_MACHINE_I2S diff --git a/microros_ws/extra_packages/micropython/src/machine_pin.c b/microros_ws/extra_packages/micropython/src/machine_pin.c new file mode 100644 index 0000000..8a892d2 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_pin.c @@ -0,0 +1,735 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "driver/gpio.h" +#include "driver/rtc_io.h" + +#include "py/runtime.h" +#include "py/mphal.h" +#include "mphalport.h" +#include "modmachine.h" +#include "extmod/virtpin.h" +#include "machine_rtc.h" +#include "modesp32.h" + +#if CONFIG_IDF_TARGET_ESP32C3 +#include "soc/usb_serial_jtag_reg.h" +#endif + +// Used to implement a range of pull capabilities +#define GPIO_PULL_DOWN (1) +#define GPIO_PULL_UP (2) + +#if CONFIG_IDF_TARGET_ESP32 +#define GPIO_FIRST_NON_OUTPUT (34) +#elif CONFIG_IDF_TARGET_ESP32S2 +#define GPIO_FIRST_NON_OUTPUT (46) +#endif + +typedef struct _machine_pin_obj_t { + mp_obj_base_t base; + gpio_num_t id; +} machine_pin_obj_t; + +typedef struct _machine_pin_irq_obj_t { + mp_obj_base_t base; + gpio_num_t id; +} machine_pin_irq_obj_t; + +STATIC const machine_pin_obj_t machine_pin_obj[GPIO_NUM_MAX] = { + #if CONFIG_IDF_TARGET_ESP32 + + {{&machine_pin_type}, GPIO_NUM_0}, + {{&machine_pin_type}, GPIO_NUM_1}, + {{&machine_pin_type}, GPIO_NUM_2}, + {{&machine_pin_type}, GPIO_NUM_3}, + {{&machine_pin_type}, GPIO_NUM_4}, + {{&machine_pin_type}, GPIO_NUM_5}, + {{&machine_pin_type}, GPIO_NUM_6}, + {{&machine_pin_type}, GPIO_NUM_7}, + {{&machine_pin_type}, GPIO_NUM_8}, + {{&machine_pin_type}, GPIO_NUM_9}, + {{&machine_pin_type}, GPIO_NUM_10}, + {{&machine_pin_type}, GPIO_NUM_11}, + {{&machine_pin_type}, GPIO_NUM_12}, + {{&machine_pin_type}, GPIO_NUM_13}, + {{&machine_pin_type}, GPIO_NUM_14}, + {{&machine_pin_type}, GPIO_NUM_15}, + #if CONFIG_ESP32_SPIRAM_SUPPORT + {{NULL}, -1}, + {{NULL}, -1}, + #else + {{&machine_pin_type}, GPIO_NUM_16}, + {{&machine_pin_type}, GPIO_NUM_17}, + #endif + {{&machine_pin_type}, GPIO_NUM_18}, + {{&machine_pin_type}, GPIO_NUM_19}, + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 2) + {{&machine_pin_type}, GPIO_NUM_20}, + #else + {{NULL}, -1}, + #endif + {{&machine_pin_type}, GPIO_NUM_21}, + {{&machine_pin_type}, GPIO_NUM_22}, + {{&machine_pin_type}, GPIO_NUM_23}, + {{NULL}, -1}, + {{&machine_pin_type}, GPIO_NUM_25}, + {{&machine_pin_type}, GPIO_NUM_26}, + {{&machine_pin_type}, GPIO_NUM_27}, + {{NULL}, -1}, + {{NULL}, -1}, + {{NULL}, -1}, + {{NULL}, -1}, + {{&machine_pin_type}, GPIO_NUM_32}, + {{&machine_pin_type}, GPIO_NUM_33}, + {{&machine_pin_type}, GPIO_NUM_34}, + {{&machine_pin_type}, GPIO_NUM_35}, + {{&machine_pin_type}, GPIO_NUM_36}, + {{&machine_pin_type}, GPIO_NUM_37}, + {{&machine_pin_type}, GPIO_NUM_38}, + {{&machine_pin_type}, GPIO_NUM_39}, + + #elif CONFIG_IDF_TARGET_ESP32C3 + + {{&machine_pin_type}, GPIO_NUM_0}, + {{&machine_pin_type}, GPIO_NUM_1}, + {{&machine_pin_type}, GPIO_NUM_2}, + {{&machine_pin_type}, GPIO_NUM_3}, + {{&machine_pin_type}, GPIO_NUM_4}, + {{&machine_pin_type}, GPIO_NUM_5}, + {{&machine_pin_type}, GPIO_NUM_6}, + {{&machine_pin_type}, GPIO_NUM_7}, + {{&machine_pin_type}, GPIO_NUM_8}, + {{&machine_pin_type}, GPIO_NUM_9}, + {{&machine_pin_type}, GPIO_NUM_10}, + {{&machine_pin_type}, GPIO_NUM_11}, + {{&machine_pin_type}, GPIO_NUM_12}, + {{&machine_pin_type}, GPIO_NUM_13}, + {{NULL}, -1}, // 14 FLASH + {{NULL}, -1}, // 15 FLASH + {{NULL}, -1}, // 16 FLASH + {{NULL}, -1}, // 17 FLASH + #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG + {{NULL}, -1}, // 18 is for native USB D- + {{NULL}, -1}, // 19 is for native USB D+ + #else + {{&machine_pin_type}, GPIO_NUM_18}, + {{&machine_pin_type}, GPIO_NUM_19}, + #endif + {{&machine_pin_type}, GPIO_NUM_20}, + {{&machine_pin_type}, GPIO_NUM_21}, + + #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + + {{&machine_pin_type}, GPIO_NUM_0}, + {{&machine_pin_type}, GPIO_NUM_1}, + {{&machine_pin_type}, GPIO_NUM_2}, + {{&machine_pin_type}, GPIO_NUM_3}, + {{&machine_pin_type}, GPIO_NUM_4}, + {{&machine_pin_type}, GPIO_NUM_5}, + {{&machine_pin_type}, GPIO_NUM_6}, + {{&machine_pin_type}, GPIO_NUM_7}, + {{&machine_pin_type}, GPIO_NUM_8}, + {{&machine_pin_type}, GPIO_NUM_9}, + {{&machine_pin_type}, GPIO_NUM_10}, + {{&machine_pin_type}, GPIO_NUM_11}, + {{&machine_pin_type}, GPIO_NUM_12}, + {{&machine_pin_type}, GPIO_NUM_13}, + {{&machine_pin_type}, GPIO_NUM_14}, + {{&machine_pin_type}, GPIO_NUM_15}, + {{&machine_pin_type}, GPIO_NUM_16}, + {{&machine_pin_type}, GPIO_NUM_17}, + {{&machine_pin_type}, GPIO_NUM_18}, + #if CONFIG_USB_CDC_ENABLED + {{NULL}, -1}, // 19 is for native USB D- + {{NULL}, -1}, // 20 is for native USB D- + #else + {{&machine_pin_type}, GPIO_NUM_19}, + {{&machine_pin_type}, GPIO_NUM_20}, + #endif + {{&machine_pin_type}, GPIO_NUM_21}, + {{NULL}, -1}, // 22 not a pin + {{NULL}, -1}, // 23 not a pin + {{NULL}, -1}, // 24 not a pin + {{NULL}, -1}, // 25 not a pin + #if CONFIG_SPIRAM + {{NULL}, -1}, // 26 PSRAM + #else + {{&machine_pin_type}, GPIO_NUM_26}, + #endif + {{NULL}, -1}, // 27 FLASH/PSRAM + {{NULL}, -1}, // 28 FLASH/PSRAM + {{NULL}, -1}, // 29 FLASH/PSRAM + {{NULL}, -1}, // 30 FLASH/PSRAM + {{NULL}, -1}, // 31 FLASH/PSRAM + {{NULL}, -1}, // 32 FLASH/PSRAM + #if CONFIG_SPIRAM_MODE_OCT + {{NULL}, -1}, // 33 FLASH/PSRAM + {{NULL}, -1}, // 34 FLASH/PSRAM + {{NULL}, -1}, // 35 FLASH/PSRAM + {{NULL}, -1}, // 36 FLASH/PSRAM + {{NULL}, -1}, // 37 FLASH/PSRAM + #else + {{&machine_pin_type}, GPIO_NUM_33}, + {{&machine_pin_type}, GPIO_NUM_34}, + {{&machine_pin_type}, GPIO_NUM_35}, + {{&machine_pin_type}, GPIO_NUM_36}, + {{&machine_pin_type}, GPIO_NUM_37}, + #endif + {{&machine_pin_type}, GPIO_NUM_38}, + {{&machine_pin_type}, GPIO_NUM_39}, // MTCLK + {{&machine_pin_type}, GPIO_NUM_40}, // MTDO + {{&machine_pin_type}, GPIO_NUM_41}, // MTDI + {{&machine_pin_type}, GPIO_NUM_42}, // MTMS + {{&machine_pin_type}, GPIO_NUM_43}, // U0TXD + {{&machine_pin_type}, GPIO_NUM_44}, // U0RXD + {{&machine_pin_type}, GPIO_NUM_45}, + {{&machine_pin_type}, GPIO_NUM_46}, + + #endif + + #if CONFIG_IDF_TARGET_ESP32S3 && MICROPY_HW_ESP32S3_EXTENDED_IO + + {{&machine_pin_type}, GPIO_NUM_47}, + {{&machine_pin_type}, GPIO_NUM_48}, + + #endif +}; + +// forward declaration +STATIC const machine_pin_irq_obj_t machine_pin_irq_object[GPIO_NUM_MAX]; + +void machine_pins_init(void) { + static bool did_install = false; + if (!did_install) { + gpio_install_isr_service(0); + did_install = true; + } + memset(&MP_STATE_PORT(machine_pin_irq_handler[0]), 0, sizeof(MP_STATE_PORT(machine_pin_irq_handler))); +} + +void machine_pins_deinit(void) { + for (int i = 0; i < MP_ARRAY_SIZE(machine_pin_obj); ++i) { + if (machine_pin_obj[i].id != (gpio_num_t)-1) { + gpio_isr_handler_remove(machine_pin_obj[i].id); + } + } +} + +STATIC void machine_pin_isr_handler(void *arg) { + machine_pin_obj_t *self = arg; + mp_obj_t handler = MP_STATE_PORT(machine_pin_irq_handler)[self->id]; + mp_sched_schedule(handler, MP_OBJ_FROM_PTR(self)); + mp_hal_wake_main_task_from_isr(); +} + +gpio_num_t machine_pin_get_id(mp_obj_t pin_in) { + if (mp_obj_get_type(pin_in) != &machine_pin_type) { + mp_raise_ValueError(MP_ERROR_TEXT("expecting a pin")); + } + machine_pin_obj_t *self = pin_in; + return self->id; +} + +STATIC void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_pin_obj_t *self = self_in; + mp_printf(print, "Pin(%u)", self->id); +} + +// pin.init(mode=None, pull=-1, *, value, drive, hold) +STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode, ARG_pull, ARG_value, ARG_drive, ARG_hold }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = mp_const_none}}, + { MP_QSTR_pull, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(-1)}}, + { MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + { MP_QSTR_drive, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + { MP_QSTR_hold, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // reset the pin to digital if this is a mode-setting init (grab it back from ADC) + if (args[ARG_mode].u_obj != mp_const_none) { + if (rtc_gpio_is_valid_gpio(self->id)) { + #if !CONFIG_IDF_TARGET_ESP32C3 + rtc_gpio_deinit(self->id); + #endif + } + } + + #if CONFIG_IDF_TARGET_ESP32C3 + if (self->id == 18 || self->id == 19) { + CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE); + } + #endif + + // configure the pin for gpio + gpio_pad_select_gpio(self->id); + + // set initial value (do this before configuring mode/pull) + if (args[ARG_value].u_obj != MP_OBJ_NULL) { + gpio_set_level(self->id, mp_obj_is_true(args[ARG_value].u_obj)); + } + + // set drive capability (do this before configuring mode) + if (args[ARG_drive].u_obj != MP_OBJ_NULL && GPIO_IS_VALID_OUTPUT_GPIO(self->id)) { + mp_int_t strength = mp_obj_get_int(args[ARG_drive].u_obj); + if (0 <= strength && strength < GPIO_DRIVE_CAP_MAX) { + gpio_set_drive_capability(self->id, strength); + } + } + + // configure mode + if (args[ARG_mode].u_obj != mp_const_none) { + mp_int_t pin_io_mode = mp_obj_get_int(args[ARG_mode].u_obj); + #ifdef GPIO_FIRST_NON_OUTPUT + if (self->id >= GPIO_FIRST_NON_OUTPUT && (pin_io_mode & GPIO_MODE_DEF_OUTPUT)) { + mp_raise_ValueError(MP_ERROR_TEXT("pin can only be input")); + } + #endif + gpio_set_direction(self->id, pin_io_mode); + } + + // configure pull + if (args[ARG_pull].u_obj != MP_OBJ_NEW_SMALL_INT(-1)) { + int mode = 0; + if (args[ARG_pull].u_obj != mp_const_none) { + mode = mp_obj_get_int(args[ARG_pull].u_obj); + } + if (mode & GPIO_PULL_DOWN) { + gpio_pulldown_en(self->id); + } else { + gpio_pulldown_dis(self->id); + } + if (mode & GPIO_PULL_UP) { + gpio_pullup_en(self->id); + } else { + gpio_pullup_dis(self->id); + } + } + + // configure pad hold + if (args[ARG_hold].u_obj != MP_OBJ_NULL && GPIO_IS_VALID_OUTPUT_GPIO(self->id)) { + // always disable pad hold to apply outstanding config changes + gpio_hold_dis(self->id); + // (re-)enable pad hold if requested + if (mp_obj_is_true(args[ARG_hold].u_obj)) { + gpio_hold_en(self->id); + } + } + + return mp_const_none; +} + +// constructor(id, ...) +mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // get the wanted pin object + int wanted_pin = mp_obj_get_int(args[0]); + const machine_pin_obj_t *self = NULL; + if (0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(machine_pin_obj)) { + self = (machine_pin_obj_t *)&machine_pin_obj[wanted_pin]; + } + if (self == NULL || self->base.type == NULL) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); + } + + if (n_args > 1 || n_kw > 0) { + // pin mode given, so configure this GPIO + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_pin_obj_init_helper(self, n_args - 1, args + 1, &kw_args); + } + + return MP_OBJ_FROM_PTR(self); +} + +// fast method for getting/setting pin value +STATIC mp_obj_t machine_pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + machine_pin_obj_t *self = self_in; + if (n_args == 0) { + // get pin + return MP_OBJ_NEW_SMALL_INT(gpio_get_level(self->id)); + } else { + // set pin + gpio_set_level(self->id, mp_obj_is_true(args[0])); + return mp_const_none; + } +} + +// pin.init(mode, pull) +STATIC mp_obj_t machine_pin_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return machine_pin_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_init_obj, 1, machine_pin_obj_init); + +// pin.value([value]) +STATIC mp_obj_t machine_pin_value(size_t n_args, const mp_obj_t *args) { + return machine_pin_call(args[0], n_args - 1, 0, args + 1); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pin_value_obj, 1, 2, machine_pin_value); + +// pin.off() +STATIC mp_obj_t machine_pin_off(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + gpio_set_level(self->id, 0); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_off_obj, machine_pin_off); + +// pin.on() +STATIC mp_obj_t machine_pin_on(mp_obj_t self_in) { + machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); + gpio_set_level(self->id, 1); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_on_obj, machine_pin_on); + +// pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING) +STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_handler, ARG_trigger, ARG_wake }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_handler, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_trigger, MP_ARG_INT, {.u_int = GPIO_PIN_INTR_POSEDGE | GPIO_PIN_INTR_NEGEDGE} }, + { MP_QSTR_wake, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + machine_pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (n_args > 1 || kw_args->used != 0) { + // configure irq + mp_obj_t handler = args[ARG_handler].u_obj; + uint32_t trigger = args[ARG_trigger].u_int; + mp_obj_t wake_obj = args[ARG_wake].u_obj; + + if ((trigger == GPIO_PIN_INTR_LOLEVEL || trigger == GPIO_PIN_INTR_HILEVEL) && wake_obj != mp_const_none) { + mp_int_t wake; + if (mp_obj_get_int_maybe(wake_obj, &wake)) { + if (wake < 2 || wake > 7) { + mp_raise_ValueError(MP_ERROR_TEXT("bad wake value")); + } + } else { + mp_raise_ValueError(MP_ERROR_TEXT("bad wake value")); + } + + if (machine_rtc_config.wake_on_touch) { // not compatible + mp_raise_ValueError(MP_ERROR_TEXT("no resources")); + } + + if (!RTC_IS_VALID_EXT_PIN(self->id)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for wake")); + } + + if (machine_rtc_config.ext0_pin == -1) { + machine_rtc_config.ext0_pin = self->id; + } else if (machine_rtc_config.ext0_pin != self->id) { + mp_raise_ValueError(MP_ERROR_TEXT("no resources")); + } + + machine_rtc_config.ext0_level = trigger == GPIO_PIN_INTR_LOLEVEL ? 0 : 1; + machine_rtc_config.ext0_wake_types = wake; + } else { + if (machine_rtc_config.ext0_pin == self->id) { + machine_rtc_config.ext0_pin = -1; + } + + if (handler == mp_const_none) { + handler = MP_OBJ_NULL; + trigger = 0; + } + gpio_isr_handler_remove(self->id); + MP_STATE_PORT(machine_pin_irq_handler)[self->id] = handler; + gpio_set_intr_type(self->id, trigger); + gpio_isr_handler_add(self->id, machine_pin_isr_handler, (void *)self); + } + } + + // return the irq object + return MP_OBJ_FROM_PTR(&machine_pin_irq_object[self->id]); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_irq_obj, 1, machine_pin_irq); + +STATIC const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { + // instance methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pin_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_off_obj) }, + { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_on_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) }, + + // class constants + { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_MODE_INPUT) }, + { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_MODE_INPUT_OUTPUT) }, + { MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(GPIO_MODE_INPUT_OUTPUT_OD) }, + { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_PULL_UP) }, + { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_PULL_DOWN) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_PIN_INTR_POSEDGE) }, + { MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(GPIO_PIN_INTR_NEGEDGE) }, + { MP_ROM_QSTR(MP_QSTR_WAKE_LOW), MP_ROM_INT(GPIO_PIN_INTR_LOLEVEL) }, + { MP_ROM_QSTR(MP_QSTR_WAKE_HIGH), MP_ROM_INT(GPIO_PIN_INTR_HILEVEL) }, + { MP_ROM_QSTR(MP_QSTR_DRIVE_0), MP_ROM_INT(GPIO_DRIVE_CAP_0) }, + { MP_ROM_QSTR(MP_QSTR_DRIVE_1), MP_ROM_INT(GPIO_DRIVE_CAP_1) }, + { MP_ROM_QSTR(MP_QSTR_DRIVE_2), MP_ROM_INT(GPIO_DRIVE_CAP_2) }, + { MP_ROM_QSTR(MP_QSTR_DRIVE_3), MP_ROM_INT(GPIO_DRIVE_CAP_3) }, +}; + +STATIC mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + (void)errcode; + machine_pin_obj_t *self = self_in; + + switch (request) { + case MP_PIN_READ: { + return gpio_get_level(self->id); + } + case MP_PIN_WRITE: { + gpio_set_level(self->id, arg); + return 0; + } + } + return -1; +} + +STATIC MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table); + +STATIC const mp_pin_p_t pin_pin_p = { + .ioctl = pin_ioctl, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + machine_pin_type, + MP_QSTR_Pin, + MP_TYPE_FLAG_NONE, + make_new, mp_pin_make_new, + print, machine_pin_print, + call, machine_pin_call, + protocol, &pin_pin_p, + locals_dict, &machine_pin_locals_dict + ); + +/******************************************************************************/ +// Pin IRQ object + +STATIC const mp_obj_type_t machine_pin_irq_type; + +STATIC const machine_pin_irq_obj_t machine_pin_irq_object[GPIO_NUM_MAX] = { + #if CONFIG_IDF_TARGET_ESP32 + + {{&machine_pin_irq_type}, GPIO_NUM_0}, + {{&machine_pin_irq_type}, GPIO_NUM_1}, + {{&machine_pin_irq_type}, GPIO_NUM_2}, + {{&machine_pin_irq_type}, GPIO_NUM_3}, + {{&machine_pin_irq_type}, GPIO_NUM_4}, + {{&machine_pin_irq_type}, GPIO_NUM_5}, + {{&machine_pin_irq_type}, GPIO_NUM_6}, + {{&machine_pin_irq_type}, GPIO_NUM_7}, + {{&machine_pin_irq_type}, GPIO_NUM_8}, + {{&machine_pin_irq_type}, GPIO_NUM_9}, + {{&machine_pin_irq_type}, GPIO_NUM_10}, + {{&machine_pin_irq_type}, GPIO_NUM_11}, + {{&machine_pin_irq_type}, GPIO_NUM_12}, + {{&machine_pin_irq_type}, GPIO_NUM_13}, + {{&machine_pin_irq_type}, GPIO_NUM_14}, + {{&machine_pin_irq_type}, GPIO_NUM_15}, + #if CONFIG_ESP32_SPIRAM_SUPPORT + {{NULL}, -1}, + {{NULL}, -1}, + #else + {{&machine_pin_irq_type}, GPIO_NUM_16}, + {{&machine_pin_irq_type}, GPIO_NUM_17}, + #endif + {{&machine_pin_irq_type}, GPIO_NUM_18}, + {{&machine_pin_irq_type}, GPIO_NUM_19}, + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 2) + {{&machine_pin_irq_type}, GPIO_NUM_20}, + #else + {{NULL}, -1}, + #endif + {{&machine_pin_irq_type}, GPIO_NUM_21}, + {{&machine_pin_irq_type}, GPIO_NUM_22}, + {{&machine_pin_irq_type}, GPIO_NUM_23}, + {{NULL}, -1}, + {{&machine_pin_irq_type}, GPIO_NUM_25}, + {{&machine_pin_irq_type}, GPIO_NUM_26}, + {{&machine_pin_irq_type}, GPIO_NUM_27}, + {{NULL}, -1}, + {{NULL}, -1}, + {{NULL}, -1}, + {{NULL}, -1}, + {{&machine_pin_irq_type}, GPIO_NUM_32}, + {{&machine_pin_irq_type}, GPIO_NUM_33}, + {{&machine_pin_irq_type}, GPIO_NUM_34}, + {{&machine_pin_irq_type}, GPIO_NUM_35}, + {{&machine_pin_irq_type}, GPIO_NUM_36}, + {{&machine_pin_irq_type}, GPIO_NUM_37}, + {{&machine_pin_irq_type}, GPIO_NUM_38}, + {{&machine_pin_irq_type}, GPIO_NUM_39}, + + #elif CONFIG_IDF_TARGET_ESP32C3 + + {{&machine_pin_irq_type}, GPIO_NUM_0}, + {{&machine_pin_irq_type}, GPIO_NUM_1}, + {{&machine_pin_irq_type}, GPIO_NUM_2}, + {{&machine_pin_irq_type}, GPIO_NUM_3}, + {{&machine_pin_irq_type}, GPIO_NUM_4}, + {{&machine_pin_irq_type}, GPIO_NUM_5}, + {{&machine_pin_irq_type}, GPIO_NUM_6}, + {{&machine_pin_irq_type}, GPIO_NUM_7}, + {{&machine_pin_irq_type}, GPIO_NUM_8}, + {{&machine_pin_irq_type}, GPIO_NUM_9}, + {{&machine_pin_irq_type}, GPIO_NUM_10}, + {{&machine_pin_irq_type}, GPIO_NUM_11}, + {{&machine_pin_irq_type}, GPIO_NUM_12}, + {{&machine_pin_irq_type}, GPIO_NUM_13}, + {{&machine_pin_irq_type}, GPIO_NUM_14}, + {{&machine_pin_irq_type}, GPIO_NUM_15}, + {{&machine_pin_irq_type}, GPIO_NUM_16}, + {{&machine_pin_irq_type}, GPIO_NUM_17}, + {{&machine_pin_irq_type}, GPIO_NUM_18}, + {{&machine_pin_irq_type}, GPIO_NUM_19}, + {{&machine_pin_irq_type}, GPIO_NUM_20}, + {{&machine_pin_irq_type}, GPIO_NUM_21}, + + #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + + {{&machine_pin_irq_type}, GPIO_NUM_0}, + {{&machine_pin_irq_type}, GPIO_NUM_1}, + {{&machine_pin_irq_type}, GPIO_NUM_2}, + {{&machine_pin_irq_type}, GPIO_NUM_3}, + {{&machine_pin_irq_type}, GPIO_NUM_4}, + {{&machine_pin_irq_type}, GPIO_NUM_5}, + {{&machine_pin_irq_type}, GPIO_NUM_6}, + {{&machine_pin_irq_type}, GPIO_NUM_7}, + {{&machine_pin_irq_type}, GPIO_NUM_8}, + {{&machine_pin_irq_type}, GPIO_NUM_9}, + {{&machine_pin_irq_type}, GPIO_NUM_10}, + {{&machine_pin_irq_type}, GPIO_NUM_11}, + {{&machine_pin_irq_type}, GPIO_NUM_12}, + {{&machine_pin_irq_type}, GPIO_NUM_13}, + {{&machine_pin_irq_type}, GPIO_NUM_14}, + {{&machine_pin_irq_type}, GPIO_NUM_15}, + {{&machine_pin_irq_type}, GPIO_NUM_16}, + {{&machine_pin_irq_type}, GPIO_NUM_17}, + {{&machine_pin_irq_type}, GPIO_NUM_18}, + #if CONFIG_USB_CDC_ENABLED + {{NULL}, -1}, // 19 is for native USB D- + {{NULL}, -1}, // 20 is for native USB D- + #else + {{&machine_pin_irq_type}, GPIO_NUM_19}, + {{&machine_pin_irq_type}, GPIO_NUM_20}, + #endif + {{&machine_pin_irq_type}, GPIO_NUM_21}, + {{NULL}, -1}, // 22 not a pin + {{NULL}, -1}, // 23 not a pin + {{NULL}, -1}, // 24 not a pin + {{NULL}, -1}, // 25 not a pin + #if CONFIG_SPIRAM + {{NULL}, -1}, // 26 PSRAM + #else + {{&machine_pin_irq_type}, GPIO_NUM_26}, + #endif + {{NULL}, -1}, // 27 FLASH/PSRAM + {{NULL}, -1}, // 28 FLASH/PSRAM + {{NULL}, -1}, // 29 FLASH/PSRAM + {{NULL}, -1}, // 30 FLASH/PSRAM + {{NULL}, -1}, // 31 FLASH/PSRAM + {{NULL}, -1}, // 32 FLASH/PSRAM + #if CONFIG_SPIRAM_MODE_OCT + {{NULL}, -1}, // 33 FLASH/PSRAM + {{NULL}, -1}, // 34 FLASH/PSRAM + {{NULL}, -1}, // 35 FLASH/PSRAM + {{NULL}, -1}, // 36 FLASH/PSRAM + {{NULL}, -1}, // 37 FLASH/PSRAM + #else + {{&machine_pin_irq_type}, GPIO_NUM_33}, + {{&machine_pin_irq_type}, GPIO_NUM_34}, + {{&machine_pin_irq_type}, GPIO_NUM_35}, + {{&machine_pin_irq_type}, GPIO_NUM_36}, + {{&machine_pin_irq_type}, GPIO_NUM_37}, + #endif + {{&machine_pin_irq_type}, GPIO_NUM_38}, + {{&machine_pin_irq_type}, GPIO_NUM_39}, + {{&machine_pin_irq_type}, GPIO_NUM_40}, + {{&machine_pin_irq_type}, GPIO_NUM_41}, + {{&machine_pin_irq_type}, GPIO_NUM_42}, + {{&machine_pin_irq_type}, GPIO_NUM_43}, + {{&machine_pin_irq_type}, GPIO_NUM_44}, + {{&machine_pin_irq_type}, GPIO_NUM_45}, + {{&machine_pin_irq_type}, GPIO_NUM_46}, + + #endif + + #if CONFIG_IDF_TARGET_ESP32S3 && MICROPY_HW_ESP32S3_EXTENDED_IO + + {{&machine_pin_irq_type}, GPIO_NUM_47}, + {{&machine_pin_irq_type}, GPIO_NUM_48}, + + #endif +}; + +STATIC mp_obj_t machine_pin_irq_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + machine_pin_irq_obj_t *self = self_in; + mp_arg_check_num(n_args, n_kw, 0, 0, false); + machine_pin_isr_handler((void *)&machine_pin_obj[self->id]); + return mp_const_none; +} + +STATIC mp_obj_t machine_pin_irq_trigger(size_t n_args, const mp_obj_t *args) { + machine_pin_irq_obj_t *self = args[0]; + uint32_t orig_trig = GPIO.pin[self->id].int_type; + if (n_args == 2) { + // set trigger + gpio_set_intr_type(self->id, mp_obj_get_int(args[1])); + } + // return original trigger value + return MP_OBJ_NEW_SMALL_INT(orig_trig); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pin_irq_trigger_obj, 1, 2, machine_pin_irq_trigger); + +STATIC const mp_rom_map_elem_t machine_pin_irq_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_trigger), MP_ROM_PTR(&machine_pin_irq_trigger_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_pin_irq_locals_dict, machine_pin_irq_locals_dict_table); + +STATIC MP_DEFINE_CONST_OBJ_TYPE( + machine_pin_irq_type, + MP_QSTR_IRQ, + MP_TYPE_FLAG_NONE, + call, machine_pin_irq_call, + locals_dict, &machine_pin_irq_locals_dict + ); + +MP_REGISTER_ROOT_POINTER(mp_obj_t machine_pin_irq_handler[49]); // 49 is the biggest of GPIO_NUM_MAX's diff --git a/microros_ws/extra_packages/micropython/src/machine_pwm.c b/microros_ws/extra_packages/micropython/src/machine_pwm.c new file mode 100644 index 0000000..99bef88 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_pwm.c @@ -0,0 +1,688 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016-2021 Damien P. George + * Copyright (c) 2018 Alan Dragomirecky + * Copyright (c) 2020 Antoine Aubert + * Copyright (c) 2021 Ihor Nehrutsa + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "py/mphal.h" + +#include "driver/ledc.h" +#include "esp_err.h" + +#define PWM_DBG(...) +// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); + +// Total number of channels +#define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX) + +typedef struct _chan_t { + // Which channel has which GPIO pin assigned? + // (-1 if not assigned) + gpio_num_t pin; + // Which channel has which timer assigned? + // (-1 if not assigned) + int timer_idx; +} chan_t; + +// List of PWM channels +STATIC chan_t chans[PWM_CHANNEL_MAX]; + +// channel_idx is an index (end-to-end sequential numbering) for all channels +// available on the chip and described in chans[] +#define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel) +#define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX) +#define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX) + +// Total number of timers +#define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX) + +// List of timer configs +STATIC ledc_timer_config_t timers[PWM_TIMER_MAX]; + +// timer_idx is an index (end-to-end sequential numbering) for all timers +// available on the chip and configured in timers[] +#define TIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer) +#define TIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX) +#define TIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX) + +// Params for PWM operation +// 5khz is default frequency +#define PWM_FREQ (5000) + +// 10-bit resolution (compatible with esp8266 PWM) +#define PWM_RES_10_BIT (LEDC_TIMER_10_BIT) + +// Maximum duty value on 10-bit resolution +#define MAX_DUTY_U10 ((1 << PWM_RES_10_BIT) - 1) +// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html#supported-range-of-frequency-and-duty-resolutions +// duty() uses 10-bit resolution or less +// duty_u16() and duty_ns() use 16-bit resolution or less + +// Possible highest resolution in device +#if (LEDC_TIMER_BIT_MAX - 1) < LEDC_TIMER_16_BIT +#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) +#else +#define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit for ESP32, but 16 bit is used +#endif +// Duty resolution of user interface in `duty_u16()` and `duty_u16` parameter in constructor/initializer +#define UI_RES_16_BIT (16) +// Maximum duty value on highest user interface resolution +#define UI_MAX_DUTY ((1 << UI_RES_16_BIT) - 1) +// How much to shift from the HIGHEST_PWM_RES duty resolution to the user interface duty resolution UI_RES_16_BIT +#define UI_RES_SHIFT (UI_RES_16_BIT - HIGHEST_PWM_RES) // 0 for ESP32, 2 for S2, S3, C3 + +#if SOC_LEDC_SUPPORT_REF_TICK +// If the PWM frequency is less than EMPIRIC_FREQ, then LEDC_REF_CLK_HZ(1 MHz) source is used, else LEDC_APB_CLK_HZ(80 MHz) source is used +#define EMPIRIC_FREQ (10) // Hz +#endif + +// Config of timer upon which we run all PWM'ed GPIO pins +STATIC bool pwm_inited = false; + +// MicroPython PWM object struct +typedef struct _machine_pwm_obj_t { + mp_obj_base_t base; + gpio_num_t pin; + bool active; + int mode; + int channel; + int timer; + int duty_x; // PWM_RES_10_BIT if duty(), HIGHEST_PWM_RES if duty_u16(), -HIGHEST_PWM_RES if duty_ns() + int duty_u10; // stored values from previous duty setters + int duty_u16; // - / - + int duty_ns; // - / - +} machine_pwm_obj_t; + +STATIC bool is_timer_in_use(int current_channel_idx, int timer_idx); +STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty); +STATIC void set_duty_u10(machine_pwm_obj_t *self, int duty); +STATIC void set_duty_ns(machine_pwm_obj_t *self, int ns); + +STATIC void pwm_init(void) { + // Initial condition: no channels assigned + for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { + chans[i].pin = -1; + chans[i].timer_idx = -1; + } + + // Prepare all timers config + // Initial condition: no timers assigned + for (int i = 0; i < PWM_TIMER_MAX; ++i) { + timers[i].duty_resolution = HIGHEST_PWM_RES; + // unset timer is -1 + timers[i].freq_hz = -1; + timers[i].speed_mode = TIMER_IDX_TO_MODE(i); + timers[i].timer_num = TIMER_IDX_TO_TIMER(i); + timers[i].clk_cfg = LEDC_AUTO_CLK; // will reinstall later according to the EMPIRIC_FREQ + } +} + +// Deinit channel and timer if the timer is unused +STATIC void pwm_deinit(int channel_idx) { + // Valid channel? + if ((channel_idx >= 0) && (channel_idx < PWM_CHANNEL_MAX)) { + // Clean up timer if necessary + int timer_idx = chans[channel_idx].timer_idx; + if (timer_idx != -1) { + if (!is_timer_in_use(channel_idx, timer_idx)) { + check_esp_err(ledc_timer_rst(TIMER_IDX_TO_MODE(timer_idx), TIMER_IDX_TO_TIMER(timer_idx))); + // Flag it unused + timers[chans[channel_idx].timer_idx].freq_hz = -1; + } + } + + int pin = chans[channel_idx].pin; + if (pin != -1) { + int mode = CHANNEL_IDX_TO_MODE(channel_idx); + int channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); + // Mark it unused, and tell the hardware to stop routing + check_esp_err(ledc_stop(mode, channel, 0)); + // Disable ledc signal for the pin + // gpio_matrix_out(pin, SIG_GPIO_OUT_IDX, false, false); + if (mode == LEDC_LOW_SPEED_MODE) { + gpio_matrix_out(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, true); + } else { + #if LEDC_SPEED_MODE_MAX > 1 + #if CONFIG_IDF_TARGET_ESP32 + gpio_matrix_out(pin, LEDC_HS_SIG_OUT0_IDX + channel, false, true); + #else + #error Add supported CONFIG_IDF_TARGET_ESP32_xxx + #endif + #endif + } + } + chans[channel_idx].pin = -1; + chans[channel_idx].timer_idx = -1; + } +} + +// This called from Ctrl-D soft reboot +void machine_pwm_deinit_all(void) { + if (pwm_inited) { + for (int channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { + pwm_deinit(channel_idx); + } + pwm_inited = false; + } +} + +STATIC void configure_channel(machine_pwm_obj_t *self) { + ledc_channel_config_t cfg = { + .channel = self->channel, + .duty = (1 << (timers[TIMER_IDX(self->mode, self->timer)].duty_resolution)) / 2, + .gpio_num = self->pin, + .intr_type = LEDC_INTR_DISABLE, + .speed_mode = self->mode, + .timer_sel = self->timer, + }; + if (ledc_channel_config(&cfg) != ESP_OK) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on Pin(%d)"), self->pin); + } +} + +STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { + if (freq != timer->freq_hz) { + // Find the highest bit resolution for the requested frequency + unsigned int i = LEDC_APB_CLK_HZ; // 80 MHz + #if SOC_LEDC_SUPPORT_REF_TICK + if (freq < EMPIRIC_FREQ) { + i = LEDC_REF_CLK_HZ; // 1 MHz + } + #endif + + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + // original code + i /= freq; + #else + // See https://github.com/espressif/esp-idf/issues/7722 + int divider = (i + freq / 2) / freq; // rounded + if (divider == 0) { + divider = 1; + } + float f = (float)i / divider; // actual frequency + if (f <= 1.0) { + f = 1.0; + } + i = (unsigned int)roundf((float)i / f); + #endif + + unsigned int res = 0; + for (; i > 1; i >>= 1) { + ++res; + } + if (res == 0) { + res = 1; + } else if (res > HIGHEST_PWM_RES) { + // Limit resolution to HIGHEST_PWM_RES to match units of our duty + res = HIGHEST_PWM_RES; + } + + // Configure the new resolution and frequency + timer->duty_resolution = res; + timer->freq_hz = freq; + timer->clk_cfg = LEDC_USE_APB_CLK; + #if SOC_LEDC_SUPPORT_REF_TICK + if (freq < EMPIRIC_FREQ) { + timer->clk_cfg = LEDC_USE_REF_TICK; + } + #endif + + // Set frequency + esp_err_t err = ledc_timer_config(timer); + if (err != ESP_OK) { + if (err == ESP_FAIL) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unreachable frequency %d"), freq); + } else { + check_esp_err(err); + } + } + // Reset the timer if low speed + if (self->mode == LEDC_LOW_SPEED_MODE) { + check_esp_err(ledc_timer_rst(self->mode, self->timer)); + } + } + + // Save the same duty cycle when frequency is changed + if (self->duty_x == HIGHEST_PWM_RES) { + set_duty_u16(self, self->duty_u16); + } else if (self->duty_x == PWM_RES_10_BIT) { + set_duty_u10(self, self->duty_u10); + } else if (self->duty_x == -HIGHEST_PWM_RES) { + set_duty_ns(self, self->duty_ns); + } +} + +// Calculate the duty parameters based on an ns value +STATIC int ns_to_duty(machine_pwm_obj_t *self, int ns) { + ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; + int64_t duty = ((int64_t)ns * UI_MAX_DUTY * timer.freq_hz + 500000000LL) / 1000000000LL; + if ((ns > 0) && (duty == 0)) { + duty = 1; + } else if (duty > UI_MAX_DUTY) { + duty = UI_MAX_DUTY; + } + return duty; +} + +STATIC int duty_to_ns(machine_pwm_obj_t *self, int duty) { + ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; + int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer.freq_hz * UI_MAX_DUTY / 2) / ((int64_t)timer.freq_hz * UI_MAX_DUTY); + return ns; +} + +#define get_duty_raw(self) ledc_get_duty(self->mode, self->channel) + +STATIC void pwm_is_active(machine_pwm_obj_t *self) { + if (self->active == false) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM inactive")); + } +} + +STATIC uint32_t get_duty_u16(machine_pwm_obj_t *self) { + pwm_is_active(self); + int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; + int duty = ledc_get_duty(self->mode, self->channel); + if (resolution <= UI_RES_16_BIT) { + duty <<= (UI_RES_16_BIT - resolution); + } else { + duty >>= (resolution - UI_RES_16_BIT); + } + return duty; +} + +STATIC uint32_t get_duty_u10(machine_pwm_obj_t *self) { + pwm_is_active(self); + return get_duty_u16(self) >> 6; // Scale down from 16 bit to 10 bit resolution +} + +STATIC uint32_t get_duty_ns(machine_pwm_obj_t *self) { + pwm_is_active(self); + return duty_to_ns(self, get_duty_u16(self)); +} + +STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty) { + pwm_is_active(self); + if ((duty < 0) || (duty > UI_MAX_DUTY)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY); + } + ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; + int channel_duty; + if (timer.duty_resolution <= UI_RES_16_BIT) { + channel_duty = duty >> (UI_RES_16_BIT - timer.duty_resolution); + } else { + channel_duty = duty << (timer.duty_resolution - UI_RES_16_BIT); + } + int max_duty = (1 << timer.duty_resolution) - 1; + if (channel_duty < 0) { + channel_duty = 0; + } else if (channel_duty > max_duty) { + channel_duty = max_duty; + } + check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); + check_esp_err(ledc_update_duty(self->mode, self->channel)); + + /* + // Bug: Sometimes duty is not set right now. + // Not a bug. It's a feature. The duty is applied at the beginning of the next signal period. + // Bug: It has been experimentally established that the duty is setted during 2 signal periods, but 1 period is expected. + // See https://github.com/espressif/esp-idf/issues/7288 + if (duty != get_duty_u16(self)) { + PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); + ets_delay_us(2 * 1000000 / timer.freq_hz); + if (duty != get_duty_u16(self)) { + PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); + } + } + */ + + self->duty_x = HIGHEST_PWM_RES; + self->duty_u16 = duty; +} + +STATIC void set_duty_u10(machine_pwm_obj_t *self, int duty) { + pwm_is_active(self); + if ((duty < 0) || (duty > MAX_DUTY_U10)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_U10); + } + set_duty_u16(self, duty << (UI_RES_16_BIT - PWM_RES_10_BIT)); + self->duty_x = PWM_RES_10_BIT; + self->duty_u10 = duty; +} + +STATIC void set_duty_ns(machine_pwm_obj_t *self, int ns) { + pwm_is_active(self); + if ((ns < 0) || (ns > duty_to_ns(self, UI_MAX_DUTY))) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, UI_MAX_DUTY)); + } + set_duty_u16(self, ns_to_duty(self, ns)); + self->duty_x = -HIGHEST_PWM_RES; + self->duty_ns = ns; +} + +/******************************************************************************/ + +#define SAME_FREQ_ONLY (true) +#define SAME_FREQ_OR_FREE (false) +#define ANY_MODE (-1) + +// Return timer_idx. Use TIMER_IDX_TO_MODE(timer_idx) and TIMER_IDX_TO_TIMER(timer_idx) to get mode and timer +STATIC int find_timer(unsigned int freq, bool same_freq_only, int mode) { + int free_timer_idx_found = -1; + // Find a free PWM Timer using the same freq + for (int timer_idx = 0; timer_idx < PWM_TIMER_MAX; ++timer_idx) { + if ((mode == ANY_MODE) || (mode == TIMER_IDX_TO_MODE(timer_idx))) { + if (timers[timer_idx].freq_hz == freq) { + // A timer already uses the same freq. Use it now. + return timer_idx; + } + if (!same_freq_only && (free_timer_idx_found == -1) && (timers[timer_idx].freq_hz == -1)) { + free_timer_idx_found = timer_idx; + // Continue to check if a channel with the same freq is in use. + } + } + } + + return free_timer_idx_found; +} + +// Return true if the timer is in use in addition to current channel +STATIC bool is_timer_in_use(int current_channel_idx, int timer_idx) { + for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { + if ((i != current_channel_idx) && (chans[i].timer_idx == timer_idx)) { + return true; + } + } + + return false; +} + +// Find a free PWM channel, also spot if our pin is already mentioned. +// Return channel_idx. Use CHANNEL_IDX_TO_MODE(channel_idx) and CHANNEL_IDX_TO_CHANNEL(channel_idx) to get mode and channel +STATIC int find_channel(int pin, int mode) { + int avail_idx = -1; + int channel_idx; + for (channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { + if ((mode == ANY_MODE) || (mode == CHANNEL_IDX_TO_MODE(channel_idx))) { + if (chans[channel_idx].pin == pin) { + break; + } + if ((avail_idx == -1) && (chans[channel_idx].pin == -1)) { + avail_idx = channel_idx; + } + } + } + if (channel_idx >= PWM_CHANNEL_MAX) { + channel_idx = avail_idx; + } + return channel_idx; +} + +/******************************************************************************/ +// MicroPython bindings for PWM + +STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "PWM(Pin(%u)", self->pin); + if (self->active) { + mp_printf(print, ", freq=%u", ledc_get_freq(self->mode, self->timer)); + + if (self->duty_x == PWM_RES_10_BIT) { + mp_printf(print, ", duty=%d", get_duty_u10(self)); + } else if (self->duty_x == -HIGHEST_PWM_RES) { + mp_printf(print, ", duty_ns=%d", get_duty_ns(self)); + } else { + mp_printf(print, ", duty_u16=%d", get_duty_u16(self)); + } + int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; + mp_printf(print, ", resolution=%d", resolution); + + mp_printf(print, ", (duty=%.2f%%, resolution=%.3f%%)", 100.0 * get_duty_raw(self) / (1 << resolution), 100.0 * 1 / (1 << resolution)); // percents + + mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer); + } + mp_printf(print, ")"); +} + +// This called from pwm.init() method +STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, + size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty_u16, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, + MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int channel_idx = find_channel(self->pin, ANY_MODE); + if (channel_idx == -1) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in all modes + } + + int duty = args[ARG_duty].u_int; + int duty_u16 = args[ARG_duty_u16].u_int; + int duty_ns = args[ARG_duty_ns].u_int; + if (((duty != -1) && (duty_u16 != -1)) || ((duty != -1) && (duty_ns != -1)) || ((duty_u16 != -1) && (duty_ns != -1))) { + mp_raise_ValueError(MP_ERROR_TEXT("only one of parameters 'duty', 'duty_u16' or 'duty_ns' is allowed")); + } + + int freq = args[ARG_freq].u_int; + // Check if freq wasn't passed as an argument + if (freq == -1) { + // Check if already set, otherwise use the default freq. + // It is possible in case: + // pwm = PWM(pin, freq=1000, duty=256) + // pwm = PWM(pin, duty=128) + if (chans[channel_idx].timer_idx != -1) { + freq = timers[chans[channel_idx].timer_idx].freq_hz; + } + if (freq <= 0) { + freq = PWM_FREQ; + } + } + if ((freq <= 0) || (freq > 40000000)) { + mp_raise_ValueError(MP_ERROR_TEXT("freqency must be from 1Hz to 40MHz")); + } + + int timer_idx; + int current_timer_idx = chans[channel_idx].timer_idx; + bool current_in_use = is_timer_in_use(channel_idx, current_timer_idx); + if (current_in_use) { + timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, CHANNEL_IDX_TO_MODE(channel_idx)); + } else { + timer_idx = chans[channel_idx].timer_idx; + } + + if (timer_idx == -1) { + timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, ANY_MODE); + } + if (timer_idx == -1) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in all modes + } + + int mode = TIMER_IDX_TO_MODE(timer_idx); + if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { + // unregister old channel + chans[channel_idx].pin = -1; + chans[channel_idx].timer_idx = -1; + // find new channel + channel_idx = find_channel(self->pin, mode); + if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in current mode + } + } + self->mode = mode; + self->timer = TIMER_IDX_TO_TIMER(timer_idx); + self->channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); + + // New PWM assignment + if ((chans[channel_idx].pin == -1) || (chans[channel_idx].timer_idx != timer_idx)) { + configure_channel(self); + chans[channel_idx].pin = self->pin; + } + chans[channel_idx].timer_idx = timer_idx; + self->active = true; + + // Set timer frequency + set_freq(self, freq, &timers[timer_idx]); + + // Set duty cycle? + if (duty_u16 != -1) { + set_duty_u16(self, duty_u16); + } else if (duty_ns != -1) { + set_duty_ns(self, duty_ns); + } else if (duty != -1) { + set_duty_u10(self, duty); + } else if (self->duty_x == 0) { + set_duty_u10(self, (1 << PWM_RES_10_BIT) / 2); // 50% + } +} + +// This called from PWM() constructor +STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, + size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 2, true); + gpio_num_t pin_id = machine_pin_get_id(args[0]); + + // create PWM object from the given pin + machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); + self->pin = pin_id; + self->active = false; + self->mode = -1; + self->channel = -1; + self->timer = -1; + self->duty_x = 0; + + // start the PWM subsystem if it's not already running + if (!pwm_inited) { + pwm_init(); + pwm_inited = true; + } + + // start the PWM running for this channel + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +// This called from pwm.deinit() method +STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { + int channel_idx = CHANNEL_IDX(self->mode, self->channel); + pwm_deinit(channel_idx); + self->active = false; + self->mode = -1; + self->channel = -1; + self->timer = -1; + self->duty_x = 0; +} + +// Set's and get's methods of PWM class + +STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { + pwm_is_active(self); + return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer)); +} + +STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { + pwm_is_active(self); + if ((freq <= 0) || (freq > 40000000)) { + mp_raise_ValueError(MP_ERROR_TEXT("freqency must be from 1Hz to 40MHz")); + } + if (freq == timers[TIMER_IDX(self->mode, self->timer)].freq_hz) { + return; + } + + int current_timer_idx = chans[CHANNEL_IDX(self->mode, self->channel)].timer_idx; + bool current_in_use = is_timer_in_use(CHANNEL_IDX(self->mode, self->channel), current_timer_idx); + + // Check if an already running timer with the same freq is running + int new_timer_idx = find_timer(freq, SAME_FREQ_ONLY, self->mode); + + // If no existing timer was found, and the current one is in use, then find a new one + if ((new_timer_idx == -1) && current_in_use) { + // Have to find a new timer + new_timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, self->mode); + + if (new_timer_idx == -1) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in current mode + } + } + + if ((new_timer_idx != -1) && (new_timer_idx != current_timer_idx)) { + // Bind the channel to the new timer + chans[self->channel].timer_idx = new_timer_idx; + + if (ledc_bind_channel_timer(self->mode, self->channel, TIMER_IDX_TO_TIMER(new_timer_idx)) != ESP_OK) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("failed to bind timer to channel")); + } + + if (!current_in_use) { + // Free the old timer + check_esp_err(ledc_timer_rst(self->mode, self->timer)); + // Flag it unused + timers[current_timer_idx].freq_hz = -1; + } + + current_timer_idx = new_timer_idx; + } + self->mode = TIMER_IDX_TO_MODE(current_timer_idx); + self->timer = TIMER_IDX_TO_TIMER(current_timer_idx); + + // Set the frequency + set_freq(self, freq, &timers[current_timer_idx]); +} + +STATIC mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(get_duty_u10(self)); +} + +STATIC void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) { + set_duty_u10(self, duty); +} + +STATIC mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(get_duty_u16(self)); +} + +STATIC void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16) { + set_duty_u16(self, duty_u16); +} + +STATIC mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) { + return MP_OBJ_NEW_SMALL_INT(get_duty_ns(self)); +} + +STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns) { + set_duty_ns(self, duty_ns); +} diff --git a/microros_ws/extra_packages/micropython/src/machine_rtc.c b/microros_ws/extra_packages/micropython/src/machine_rtc.c new file mode 100644 index 0000000..3d62033 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_rtc.c @@ -0,0 +1,182 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 "Eric Poulsen" + * Copyright (c) 2017 "Tom Manning" + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include +#include +#include "driver/gpio.h" + +#include "py/nlr.h" +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "shared/timeutils/timeutils.h" +#include "modmachine.h" +#include "machine_rtc.h" + +typedef struct _machine_rtc_obj_t { + mp_obj_base_t base; +} machine_rtc_obj_t; + +/* There is 8K of rtc_slow_memory, but some is used by the system software + If the MICROPY_HW_RTC_USER_MEM_MAX is set too high, the following compile error will happen: + region `rtc_slow_seg' overflowed by N bytes + The current system software allows almost 4096 to be used. + To avoid running into issues if the system software uses more, 2048 was picked as a max length + + You can also change this max length at compile time by defining MICROPY_HW_RTC_USER_MEM_MAX + either on your make line, or in your board config. + + If MICROPY_HW_RTC_USER_MEM_MAX is set to 0, the RTC.memory() functionality will be not + be compiled which frees some extra flash and RTC memory. +*/ +#ifndef MICROPY_HW_RTC_USER_MEM_MAX +#define MICROPY_HW_RTC_USER_MEM_MAX 2048 +#endif + +// Optionally compile user memory functionality if the size of memory is greater than 0 +#if MICROPY_HW_RTC_USER_MEM_MAX > 0 +#define MEM_MAGIC 0x75507921 +RTC_DATA_ATTR uint32_t rtc_user_mem_magic; +RTC_DATA_ATTR uint16_t rtc_user_mem_len; +RTC_DATA_ATTR uint8_t rtc_user_mem_data[MICROPY_HW_RTC_USER_MEM_MAX]; +#endif + +// singleton RTC object +STATIC const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; + +machine_rtc_config_t machine_rtc_config = { + .ext1_pins = 0, + .ext0_pin = -1 +}; + +STATIC mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // return constant object + return (mp_obj_t)&machine_rtc_obj; +} + +STATIC mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + // Get time + + struct timeval tv; + + gettimeofday(&tv, NULL); + timeutils_struct_time_t tm; + + timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); + + mp_obj_t tuple[8] = { + mp_obj_new_int(tm.tm_year), + mp_obj_new_int(tm.tm_mon), + mp_obj_new_int(tm.tm_mday), + mp_obj_new_int(tm.tm_wday), + mp_obj_new_int(tm.tm_hour), + mp_obj_new_int(tm.tm_min), + mp_obj_new_int(tm.tm_sec), + mp_obj_new_int(tv.tv_usec) + }; + + return mp_obj_new_tuple(8, tuple); + } else { + // Set time + + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[1], 8, &items); + + struct timeval tv = {0}; + tv.tv_sec = timeutils_seconds_since_epoch(mp_obj_get_int(items[0]), mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), mp_obj_get_int(items[4]), mp_obj_get_int(items[5]), mp_obj_get_int(items[6])); + tv.tv_usec = mp_obj_get_int(items[7]); + settimeofday(&tv, NULL); + + return mp_const_none; + } +} +STATIC mp_obj_t machine_rtc_datetime(size_t n_args, const mp_obj_t *args) { + return machine_rtc_datetime_helper(n_args, args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); + +STATIC mp_obj_t machine_rtc_init(mp_obj_t self_in, mp_obj_t date) { + mp_obj_t args[2] = {self_in, date}; + machine_rtc_datetime_helper(2, args); + + #if MICROPY_HW_RTC_USER_MEM_MAX > 0 + if (rtc_user_mem_magic != MEM_MAGIC) { + rtc_user_mem_magic = MEM_MAGIC; + rtc_user_mem_len = 0; + } + #endif + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_rtc_init_obj, machine_rtc_init); + +#if MICROPY_HW_RTC_USER_MEM_MAX > 0 +STATIC mp_obj_t machine_rtc_memory(size_t n_args, const mp_obj_t *args) { + if (n_args == 1) { + // read RTC memory + uint8_t rtcram[MICROPY_HW_RTC_USER_MEM_MAX]; + memcpy((char *)rtcram, (char *)rtc_user_mem_data, rtc_user_mem_len); + return mp_obj_new_bytes(rtcram, rtc_user_mem_len); + } else { + // write RTC memory + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); + + if (bufinfo.len > MICROPY_HW_RTC_USER_MEM_MAX) { + mp_raise_ValueError(MP_ERROR_TEXT("buffer too long")); + } + memcpy((char *)rtc_user_mem_data, (char *)bufinfo.buf, bufinfo.len); + rtc_user_mem_len = bufinfo.len; + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_memory_obj, 1, 2, machine_rtc_memory); +#endif + +STATIC const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_rtc_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_datetime), MP_ROM_PTR(&machine_rtc_datetime_obj) }, + #if MICROPY_HW_RTC_USER_MEM_MAX > 0 + { MP_ROM_QSTR(MP_QSTR_memory), MP_ROM_PTR(&machine_rtc_memory_obj) }, + #endif +}; +STATIC MP_DEFINE_CONST_DICT(machine_rtc_locals_dict, machine_rtc_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_rtc_type, + MP_QSTR_RTC, + MP_TYPE_FLAG_NONE, + make_new, machine_rtc_make_new, + locals_dict, &machine_rtc_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/machine_rtc.h b/microros_ws/extra_packages/micropython/src/machine_rtc.h new file mode 100644 index 0000000..ce2a548 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_rtc.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 "Eric Poulsen" + * Copyright (c) 2017 "Tom Manning" + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ESP32_MACHINE_RTC_H +#define MICROPY_INCLUDED_ESP32_MACHINE_RTC_H + +#include "modmachine.h" + +typedef struct { + uint64_t ext1_pins; // set bit == pin# + int8_t ext0_pin; // just the pin#, -1 == None + bool wake_on_touch : 1; + bool wake_on_ulp : 1; + bool ext0_level : 1; + wake_type_t ext0_wake_types; + bool ext1_level : 1; +} machine_rtc_config_t; + +extern machine_rtc_config_t machine_rtc_config; + +#endif diff --git a/microros_ws/extra_packages/micropython/src/machine_sdcard.c b/microros_ws/extra_packages/micropython/src/machine_sdcard.c new file mode 100644 index 0000000..801a26f --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_sdcard.c @@ -0,0 +1,410 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Nicko van Someren + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/mperrno.h" +#include "extmod/vfs_fat.h" + +#if MICROPY_HW_ENABLE_SDCARD + +#include "driver/sdmmc_host.h" +#include "driver/sdspi_host.h" +#include "sdmmc_cmd.h" +#include "esp_log.h" + +#define DEBUG 0 +#if DEBUG +#define DEBUG_printf(...) ESP_LOGI("modsdcard", __VA_ARGS__) +#else +#define DEBUG_printf(...) (void)0 +#endif + +// +// There are three layers of abstraction: host, slot and card. +// Creating an SD Card object will initialise the host and slot. +// Cards gets initialised by ioctl op==1 and de-inited by ioctl 2 +// Hosts are de-inited in __del__. Slots do not need de-initing. +// + +// Forward declaration +const mp_obj_type_t machine_sdcard_type; + +typedef struct _sdcard_obj_t { + mp_obj_base_t base; + mp_int_t flags; + sdmmc_host_t host; + // The card structure duplicates the host. It's not clear if we + // can avoid this given the way that it is copied. + sdmmc_card_t card; +} sdcard_card_obj_t; + +#define SDCARD_CARD_FLAGS_HOST_INIT_DONE 0x01 +#define SDCARD_CARD_FLAGS_CARD_INIT_DONE 0x02 + +#define _SECTOR_SIZE(self) (self->card.csd.sector_size) + +STATIC gpio_num_t pin_or_int(const mp_obj_t arg) { + if (mp_obj_is_small_int(arg)) { + return MP_OBJ_SMALL_INT_VALUE(arg); + } else { + // This raises a value error if the argument is not a Pin. + return machine_pin_get_id(arg); + } +} + +#define SET_CONFIG_PIN(config, pin_var, arg_id) \ + if (arg_vals[arg_id].u_obj != mp_const_none) \ + config.pin_var = pin_or_int(arg_vals[arg_id].u_obj) + +STATIC esp_err_t sdcard_ensure_card_init(sdcard_card_obj_t *self, bool force) { + if (force || !(self->flags & SDCARD_CARD_FLAGS_CARD_INIT_DONE)) { + DEBUG_printf(" Calling card init"); + + esp_err_t err = sdmmc_card_init(&(self->host), &(self->card)); + if (err == ESP_OK) { + self->flags |= SDCARD_CARD_FLAGS_CARD_INIT_DONE; + } else { + self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE; + } + DEBUG_printf(" Card init returned: %i", err); + + return err; + } else { + return ESP_OK; + } +} + +/******************************************************************************/ +// MicroPython bindings +// +// Expose the SD card or MMC as an object with the block protocol. + +// Create a new SDCard object +// The driver supports either the host SD/MMC controller (default) or SPI mode +// In both cases there are two "slots". Slot 0 on the SD/MMC controller is +// typically tied up with the flash interface in most ESP32 modules but in +// theory supports 1, 4 or 8-bit transfers. Slot 1 supports only 1 and 4-bit +// transfers. Only 1-bit is supported on the SPI interfaces. +// card = SDCard(slot=1, width=None, present_pin=None, wp_pin=None) + +STATIC mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + enum { + ARG_slot, + ARG_width, + ARG_cd, + ARG_wp, + ARG_miso, + ARG_mosi, + ARG_sck, + ARG_cs, + ARG_freq, + }; + STATIC const mp_arg_t allowed_args[] = { + { MP_QSTR_slot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_cd, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_wp, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + // These are only needed if using SPI mode + { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + // freq is valid for both SPI and SDMMC interfaces + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 20000000} }, + }; + mp_arg_val_t arg_vals[MP_ARRAY_SIZE(allowed_args)]; + mp_map_t kw_args; + + DEBUG_printf("Making new SDCard:n"); + DEBUG_printf(" Unpacking arguments"); + + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + + mp_arg_parse_all(n_args, args, &kw_args, + MP_ARRAY_SIZE(allowed_args), allowed_args, arg_vals); + + DEBUG_printf(" slot=%d, width=%d, cd=%p, wp=%p", + arg_vals[ARG_slot].u_int, arg_vals[ARG_width].u_int, + arg_vals[ARG_cd].u_obj, arg_vals[ARG_wp].u_obj); + + DEBUG_printf(" miso=%p, mosi=%p, sck=%p, cs=%p", + arg_vals[ARG_miso].u_obj, arg_vals[ARG_mosi].u_obj, + arg_vals[ARG_sck].u_obj, arg_vals[ARG_cs].u_obj); + + int slot_num = arg_vals[ARG_slot].u_int; + if (slot_num < 0 || slot_num > 3) { + mp_raise_ValueError(MP_ERROR_TEXT("slot number must be between 0 and 3 inclusive")); + } + + // Slots 0 and 1 are native SD/MMC, slots 2 and 3 are SPI + bool is_spi = (slot_num >= 2); + if (is_spi) { + slot_num -= 2; + } + + DEBUG_printf(" Setting up host configuration"); + + sdcard_card_obj_t *self = m_new_obj_with_finaliser(sdcard_card_obj_t); + self->base.type = &machine_sdcard_type; + self->flags = 0; + // Note that these defaults are macros that expand to structure + // constants so we can't directly assign them to fields. + int freq = arg_vals[ARG_freq].u_int; + if (is_spi) { + sdmmc_host_t _temp_host = SDSPI_HOST_DEFAULT(); + _temp_host.max_freq_khz = freq / 1000; + self->host = _temp_host; + } else { + sdmmc_host_t _temp_host = SDMMC_HOST_DEFAULT(); + _temp_host.max_freq_khz = freq / 1000; + self->host = _temp_host; + } + + if (is_spi) { + #if CONFIG_IDF_TARGET_ESP32S3 + self->host.slot = slot_num ? SPI3_HOST : SPI2_HOST; + #else + self->host.slot = slot_num ? HSPI_HOST : VSPI_HOST; + #endif + } + + DEBUG_printf(" Calling host.init()"); + + check_esp_err(self->host.init()); + self->flags |= SDCARD_CARD_FLAGS_HOST_INIT_DONE; + + if (is_spi) { + // SPI interface + #if CONFIG_IDF_TARGET_ESP32S3 + STATIC const sdspi_slot_config_t slot_defaults[2] = { + { + .gpio_miso = GPIO_NUM_36, + .gpio_mosi = GPIO_NUM_35, + .gpio_sck = GPIO_NUM_37, + .gpio_cs = GPIO_NUM_34, + .gpio_cd = SDSPI_SLOT_NO_CD, + .gpio_wp = SDSPI_SLOT_NO_WP, + .dma_channel = 2 + }, + SDSPI_SLOT_CONFIG_DEFAULT() + }; + #else + STATIC const sdspi_slot_config_t slot_defaults[2] = { + { + .gpio_miso = GPIO_NUM_19, + .gpio_mosi = GPIO_NUM_23, + .gpio_sck = GPIO_NUM_18, + .gpio_cs = GPIO_NUM_5, + .gpio_cd = SDSPI_SLOT_NO_CD, + .gpio_wp = SDSPI_SLOT_NO_WP, + .dma_channel = 2 + }, + SDSPI_SLOT_CONFIG_DEFAULT() + }; + #endif + + DEBUG_printf(" Setting up SPI slot configuration"); + sdspi_slot_config_t slot_config = slot_defaults[slot_num]; + + SET_CONFIG_PIN(slot_config, gpio_cd, ARG_cd); + SET_CONFIG_PIN(slot_config, gpio_wp, ARG_wp); + SET_CONFIG_PIN(slot_config, gpio_miso, ARG_miso); + SET_CONFIG_PIN(slot_config, gpio_mosi, ARG_mosi); + SET_CONFIG_PIN(slot_config, gpio_sck, ARG_sck); + SET_CONFIG_PIN(slot_config, gpio_cs, ARG_cs); + + DEBUG_printf(" Calling init_slot()"); + check_esp_err(sdspi_host_init_slot(self->host.slot, &slot_config)); + } else { + // SD/MMC interface + DEBUG_printf(" Setting up SDMMC slot configuration"); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + + // Stronger external pull-ups are still needed but apparently + // it is a good idea to set the internal pull-ups anyway. + // slot_config.flags = SDMMC_SLOT_FLAG_INTERNAL_PULLUP; + + SET_CONFIG_PIN(slot_config, gpio_cd, ARG_cd); + SET_CONFIG_PIN(slot_config, gpio_wp, ARG_wp); + + int width = arg_vals[ARG_width].u_int; + if (width == 1 || width == 4 || (width == 8 && slot_num == 0)) { + slot_config.width = width; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("width must be 1 or 4 (or 8 on slot 0)")); + } + + DEBUG_printf(" Calling init_slot()"); + check_esp_err(sdmmc_host_init_slot(self->host.slot, &slot_config)); + } + + DEBUG_printf(" Returning new card object: %p", self); + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t sd_deinit(mp_obj_t self_in) { + sdcard_card_obj_t *self = self_in; + + DEBUG_printf("De-init host\n"); + + if (self->flags & SDCARD_CARD_FLAGS_HOST_INIT_DONE) { + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) + if (self->host.flags & SDMMC_HOST_FLAG_DEINIT_ARG) { + self->host.deinit_p(self->host.slot); + } else + #endif + { + self->host.deinit(); + } + if (self->host.flags & SDMMC_HOST_FLAG_SPI) { + // SD card used a (dedicated) SPI bus, so free that SPI bus. + spi_bus_free(self->host.slot); + } + self->flags &= ~SDCARD_CARD_FLAGS_HOST_INIT_DONE; + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(sd_deinit_obj, sd_deinit); + +STATIC mp_obj_t sd_info(mp_obj_t self_in) { + sdcard_card_obj_t *self = self_in; + // We could potential return a great deal more SD card data but it + // is not clear that it is worth the extra code space to do + // so. For the most part people only care about the card size and + // block size. + + check_esp_err(sdcard_ensure_card_init((sdcard_card_obj_t *)self, false)); + + uint32_t log_block_nbr = self->card.csd.capacity; + uint32_t log_block_size = _SECTOR_SIZE(self); + + mp_obj_t tuple[2] = { + mp_obj_new_int_from_ull((uint64_t)log_block_nbr * (uint64_t)log_block_size), + mp_obj_new_int_from_uint(log_block_size), + }; + return mp_obj_new_tuple(2, tuple); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(sd_info_obj, sd_info); + +STATIC mp_obj_t machine_sdcard_readblocks(mp_obj_t self_in, mp_obj_t block_num, mp_obj_t buf) { + sdcard_card_obj_t *self = self_in; + mp_buffer_info_t bufinfo; + esp_err_t err; + + err = sdcard_ensure_card_init((sdcard_card_obj_t *)self, false); + if (err != ESP_OK) { + return false; + } + + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); + err = sdmmc_read_sectors(&(self->card), bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / _SECTOR_SIZE(self)); + + return mp_obj_new_bool(err == ESP_OK); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_readblocks_obj, machine_sdcard_readblocks); + +STATIC mp_obj_t machine_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t block_num, mp_obj_t buf) { + sdcard_card_obj_t *self = self_in; + mp_buffer_info_t bufinfo; + esp_err_t err; + + err = sdcard_ensure_card_init((sdcard_card_obj_t *)self, false); + if (err != ESP_OK) { + return false; + } + + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); + err = sdmmc_write_sectors(&(self->card), bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / _SECTOR_SIZE(self)); + + return mp_obj_new_bool(err == ESP_OK); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_writeblocks_obj, machine_sdcard_writeblocks); + +STATIC mp_obj_t machine_sdcard_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { + sdcard_card_obj_t *self = self_in; + esp_err_t err = ESP_OK; + mp_int_t cmd = mp_obj_get_int(cmd_in); + + switch (cmd) { + case MP_BLOCKDEV_IOCTL_INIT: + err = sdcard_ensure_card_init(self, false); + return MP_OBJ_NEW_SMALL_INT((err == ESP_OK) ? 0 : -1); + + case MP_BLOCKDEV_IOCTL_DEINIT: + // Ensure that future attempts to look at info re-read the card + self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE; + return MP_OBJ_NEW_SMALL_INT(0); // success + + case MP_BLOCKDEV_IOCTL_SYNC: + // nothing to do + return MP_OBJ_NEW_SMALL_INT(0); // success + + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: + err = sdcard_ensure_card_init(self, false); + if (err != ESP_OK) { + return MP_OBJ_NEW_SMALL_INT(-1); + } + return MP_OBJ_NEW_SMALL_INT(self->card.csd.capacity); + + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + err = sdcard_ensure_card_init(self, false); + if (err != ESP_OK) { + return MP_OBJ_NEW_SMALL_INT(-1); + } + return MP_OBJ_NEW_SMALL_INT(_SECTOR_SIZE(self)); + + default: // unknown command + return MP_OBJ_NEW_SMALL_INT(-1); // error + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_ioctl_obj, machine_sdcard_ioctl); + +STATIC const mp_rom_map_elem_t machine_sdcard_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&sd_info_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&sd_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&sd_deinit_obj) }, + // block device protocol + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&machine_sdcard_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&machine_sdcard_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&machine_sdcard_ioctl_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(machine_sdcard_locals_dict, machine_sdcard_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_sdcard_type, + MP_QSTR_SDCard, + MP_TYPE_FLAG_NONE, + make_new, machine_sdcard_make_new, + locals_dict, &machine_sdcard_locals_dict + ); + +#endif // MICROPY_HW_ENABLE_SDCARD diff --git a/microros_ws/extra_packages/micropython/src/machine_timer.c b/microros_ws/extra_packages/micropython/src/machine_timer.c new file mode 100644 index 0000000..c66cb2a --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_timer.c @@ -0,0 +1,292 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2015 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "py/obj.h" +#include "py/runtime.h" +#include "modmachine.h" +#include "mphalport.h" + +#include "driver/timer.h" +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 1) +#include "hal/timer_ll.h" +#define HAVE_TIMER_LL (1) +#endif + +#define TIMER_INTR_SEL TIMER_INTR_LEVEL +#define TIMER_DIVIDER 8 + +// TIMER_BASE_CLK is normally 80MHz. TIMER_DIVIDER ought to divide this exactly +#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) + +#define TIMER_FLAGS 0 + +typedef struct _machine_timer_obj_t { + mp_obj_base_t base; + mp_uint_t group; + mp_uint_t index; + + mp_uint_t repeat; + // ESP32 timers are 64-bit + uint64_t period; + + mp_obj_t callback; + + intr_handle_t handle; + + struct _machine_timer_obj_t *next; +} machine_timer_obj_t; + +const mp_obj_type_t machine_timer_type; + +STATIC void machine_timer_disable(machine_timer_obj_t *self); +STATIC mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); + +void machine_timer_deinit_all(void) { + // Disable, deallocate and remove all timers from list + machine_timer_obj_t **t = &MP_STATE_PORT(machine_timer_obj_head); + while (*t != NULL) { + machine_timer_disable(*t); + machine_timer_obj_t *next = (*t)->next; + m_del_obj(machine_timer_obj_t, *t); + *t = next; + } +} + +STATIC void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_timer_obj_t *self = self_in; + + timer_config_t config; + mp_printf(print, "Timer(%p; ", self); + + timer_get_config(self->group, self->index, &config); + + mp_printf(print, "alarm_en=%d, ", config.alarm_en); + mp_printf(print, "auto_reload=%d, ", config.auto_reload); + mp_printf(print, "counter_en=%d)", config.counter_en); +} + +STATIC mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + mp_uint_t group = (mp_obj_get_int(args[0]) >> 1) & 1; + mp_uint_t index = mp_obj_get_int(args[0]) & 1; + + machine_timer_obj_t *self = NULL; + + // Check whether the timer is already initialized, if so use it + for (machine_timer_obj_t *t = MP_STATE_PORT(machine_timer_obj_head); t; t = t->next) { + if (t->group == group && t->index == index) { + self = t; + break; + } + } + // The timer does not exist, create it. + if (self == NULL) { + self = mp_obj_malloc(machine_timer_obj_t, &machine_timer_type); + self->group = group; + self->index = index; + + // Add the timer to the linked-list of timers + self->next = MP_STATE_PORT(machine_timer_obj_head); + MP_STATE_PORT(machine_timer_obj_head) = self; + } + + if (n_args > 1 || n_kw > 0) { + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_timer_init_helper(self, n_args - 1, args + 1, &kw_args); + } + + return self; +} + +STATIC void machine_timer_disable(machine_timer_obj_t *self) { + if (self->handle) { + timer_pause(self->group, self->index); + esp_intr_free(self->handle); + self->handle = NULL; + } + + // We let the disabled timer stay in the list, as it might be + // referenced elsewhere +} + +STATIC void machine_timer_isr(void *self_in) { + machine_timer_obj_t *self = self_in; + timg_dev_t *device = self->group ? &(TIMERG1) : &(TIMERG0); + + #if HAVE_TIMER_LL + + #if CONFIG_IDF_TARGET_ESP32 && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + device->hw_timer[self->index].update = 1; + #else + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) + #if CONFIG_IDF_TARGET_ESP32S3 + device->hw_timer[self->index].update.tn_update = 1; + #else + device->hw_timer[self->index].update.tx_update = 1; + #endif + #else + device->hw_timer[self->index].update.update = 1; + #endif + #endif + timer_ll_clear_intr_status(device, self->index); + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + timer_ll_set_alarm_enable(device, self->index, self->repeat); + #else + timer_ll_set_alarm_value(device, self->index, self->repeat); + #endif + + #else + + device->hw_timer[self->index].update = 1; + if (self->index) { + device->int_clr_timers.t1 = 1; + } else { + device->int_clr_timers.t0 = 1; + } + device->hw_timer[self->index].config.alarm_en = self->repeat; + + #endif + + mp_sched_schedule(self->callback, self); + mp_hal_wake_main_task_from_isr(); +} + +STATIC void machine_timer_enable(machine_timer_obj_t *self) { + timer_config_t config; + config.alarm_en = TIMER_ALARM_EN; + config.auto_reload = self->repeat; + config.counter_dir = TIMER_COUNT_UP; + config.divider = TIMER_DIVIDER; + config.intr_type = TIMER_INTR_LEVEL; + config.counter_en = TIMER_PAUSE; + #if SOC_TIMER_GROUP_SUPPORT_XTAL + config.clk_src = TIMER_SRC_CLK_APB; + #endif + + check_esp_err(timer_init(self->group, self->index, &config)); + check_esp_err(timer_set_counter_value(self->group, self->index, 0x00000000)); + check_esp_err(timer_set_alarm_value(self->group, self->index, self->period)); + check_esp_err(timer_enable_intr(self->group, self->index)); + check_esp_err(timer_isr_register(self->group, self->index, machine_timer_isr, (void *)self, TIMER_FLAGS, &self->handle)); + check_esp_err(timer_start(self->group, self->index)); +} + +STATIC mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_mode, + ARG_callback, + ARG_period, + ARG_tick_hz, + ARG_freq, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_period, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, + { MP_QSTR_tick_hz, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000} }, + #if MICROPY_PY_BUILTINS_FLOAT + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #else + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, + #endif + }; + + machine_timer_disable(self); + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + #if MICROPY_PY_BUILTINS_FLOAT + if (args[ARG_freq].u_obj != mp_const_none) { + self->period = (uint64_t)(TIMER_SCALE / mp_obj_get_float(args[ARG_freq].u_obj)); + } + #else + if (args[ARG_freq].u_int != 0xffffffff) { + self->period = TIMER_SCALE / ((uint64_t)args[ARG_freq].u_int); + } + #endif + else { + self->period = (((uint64_t)args[ARG_period].u_int) * TIMER_SCALE) / args[ARG_tick_hz].u_int; + } + + self->repeat = args[ARG_mode].u_int; + self->callback = args[ARG_callback].u_obj; + self->handle = NULL; + + machine_timer_enable(self); + + return mp_const_none; +} + +STATIC mp_obj_t machine_timer_deinit(mp_obj_t self_in) { + machine_timer_disable(self_in); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_deinit_obj, machine_timer_deinit); + +STATIC mp_obj_t machine_timer_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return machine_timer_init_helper(args[0], n_args - 1, args + 1, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_timer_init_obj, 1, machine_timer_init); + +STATIC mp_obj_t machine_timer_value(mp_obj_t self_in) { + machine_timer_obj_t *self = self_in; + double result; + + timer_get_counter_time_sec(self->group, self->index, &result); + + return MP_OBJ_NEW_SMALL_INT((mp_uint_t)(result * 1000)); // value in ms +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_value_obj, machine_timer_value); + +STATIC const mp_rom_map_elem_t machine_timer_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_timer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_timer_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_timer_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_timer_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_ONE_SHOT), MP_ROM_INT(false) }, + { MP_ROM_QSTR(MP_QSTR_PERIODIC), MP_ROM_INT(true) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_timer_locals_dict, machine_timer_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_timer_type, + MP_QSTR_Timer, + MP_TYPE_FLAG_NONE, + make_new, machine_timer_make_new, + print, machine_timer_print, + locals_dict, &machine_timer_locals_dict + ); + +MP_REGISTER_ROOT_POINTER(struct _machine_timer_obj_t *machine_timer_obj_head); diff --git a/microros_ws/extra_packages/micropython/src/machine_touchpad.c b/microros_ws/extra_packages/micropython/src/machine_touchpad.c new file mode 100644 index 0000000..d9f4ef3 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_touchpad.c @@ -0,0 +1,158 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Nick Moore + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" +#include "modmachine.h" +#include "driver/gpio.h" + +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + +#if CONFIG_IDF_TARGET_ESP32 +#include "driver/touch_pad.h" +#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +#include "driver/touch_sensor.h" +#endif + +typedef struct _mtp_obj_t { + mp_obj_base_t base; + gpio_num_t gpio_id; + touch_pad_t touchpad_id; +} mtp_obj_t; + +STATIC const mtp_obj_t touchpad_obj[] = { + #if CONFIG_IDF_TARGET_ESP32 + {{&machine_touchpad_type}, GPIO_NUM_4, TOUCH_PAD_NUM0}, + {{&machine_touchpad_type}, GPIO_NUM_0, TOUCH_PAD_NUM1}, + {{&machine_touchpad_type}, GPIO_NUM_2, TOUCH_PAD_NUM2}, + {{&machine_touchpad_type}, GPIO_NUM_15, TOUCH_PAD_NUM3}, + {{&machine_touchpad_type}, GPIO_NUM_13, TOUCH_PAD_NUM4}, + {{&machine_touchpad_type}, GPIO_NUM_12, TOUCH_PAD_NUM5}, + {{&machine_touchpad_type}, GPIO_NUM_14, TOUCH_PAD_NUM6}, + {{&machine_touchpad_type}, GPIO_NUM_27, TOUCH_PAD_NUM7}, + {{&machine_touchpad_type}, GPIO_NUM_33, TOUCH_PAD_NUM8}, + {{&machine_touchpad_type}, GPIO_NUM_32, TOUCH_PAD_NUM9}, + #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + {{&machine_touchpad_type}, GPIO_NUM_1, TOUCH_PAD_NUM1}, + {{&machine_touchpad_type}, GPIO_NUM_2, TOUCH_PAD_NUM2}, + {{&machine_touchpad_type}, GPIO_NUM_3, TOUCH_PAD_NUM3}, + {{&machine_touchpad_type}, GPIO_NUM_4, TOUCH_PAD_NUM4}, + {{&machine_touchpad_type}, GPIO_NUM_5, TOUCH_PAD_NUM5}, + {{&machine_touchpad_type}, GPIO_NUM_6, TOUCH_PAD_NUM6}, + {{&machine_touchpad_type}, GPIO_NUM_7, TOUCH_PAD_NUM7}, + {{&machine_touchpad_type}, GPIO_NUM_8, TOUCH_PAD_NUM8}, + {{&machine_touchpad_type}, GPIO_NUM_9, TOUCH_PAD_NUM9}, + {{&machine_touchpad_type}, GPIO_NUM_10, TOUCH_PAD_NUM10}, + {{&machine_touchpad_type}, GPIO_NUM_11, TOUCH_PAD_NUM11}, + {{&machine_touchpad_type}, GPIO_NUM_12, TOUCH_PAD_NUM12}, + {{&machine_touchpad_type}, GPIO_NUM_13, TOUCH_PAD_NUM13}, + {{&machine_touchpad_type}, GPIO_NUM_14, TOUCH_PAD_NUM14}, + #endif +}; + +STATIC mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, + const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, true); + gpio_num_t pin_id = machine_pin_get_id(args[0]); + const mtp_obj_t *self = NULL; + for (int i = 0; i < MP_ARRAY_SIZE(touchpad_obj); i++) { + if (pin_id == touchpad_obj[i].gpio_id) { + self = &touchpad_obj[i]; + break; + } + } + if (!self) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for touchpad")); + } + + static int initialized = 0; + if (!initialized) { + touch_pad_init(); + touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + touch_pad_fsm_start(); + #endif + initialized = 1; + } + #if CONFIG_IDF_TARGET_ESP32 + esp_err_t err = touch_pad_config(self->touchpad_id, 0); + #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + esp_err_t err = touch_pad_config(self->touchpad_id); + #endif + if (err == ESP_OK) { + return MP_OBJ_FROM_PTR(self); + } + mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); +} + +STATIC mp_obj_t mtp_config(mp_obj_t self_in, mp_obj_t value_in) { + mtp_obj_t *self = self_in; + #if CONFIG_IDF_TARGET_ESP32 + uint16_t value = mp_obj_get_int(value_in); + esp_err_t err = touch_pad_config(self->touchpad_id, value); + #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + esp_err_t err = touch_pad_config(self->touchpad_id); + #endif + if (err == ESP_OK) { + return mp_const_none; + } + mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); +} +MP_DEFINE_CONST_FUN_OBJ_2(mtp_config_obj, mtp_config); + +STATIC mp_obj_t mtp_read(mp_obj_t self_in) { + mtp_obj_t *self = self_in; + #if CONFIG_IDF_TARGET_ESP32 + uint16_t value; + esp_err_t err = touch_pad_read(self->touchpad_id, &value); + #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + uint32_t value; + esp_err_t err = touch_pad_read_raw_data(self->touchpad_id, &value); + #endif + if (err == ESP_OK) { + return MP_OBJ_NEW_SMALL_INT(value); + } + mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); +} +MP_DEFINE_CONST_FUN_OBJ_1(mtp_read_obj, mtp_read); + +STATIC const mp_rom_map_elem_t mtp_locals_dict_table[] = { + // instance methods + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&mtp_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mtp_read_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mtp_locals_dict, mtp_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_touchpad_type, + MP_QSTR_TouchPad, + MP_TYPE_FLAG_NONE, + make_new, mtp_make_new, + locals_dict, &mtp_locals_dict + ); + +#endif diff --git a/microros_ws/extra_packages/micropython/src/machine_uart.c b/microros_ws/extra_packages/micropython/src/machine_uart.c new file mode 100644 index 0000000..4cfa31b --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_uart.c @@ -0,0 +1,541 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "driver/uart.h" +#include "freertos/FreeRTOS.h" + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mperrno.h" +#include "modmachine.h" +#include "uart.h" + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) +#define UART_INV_TX UART_INVERSE_TXD +#define UART_INV_RX UART_INVERSE_RXD +#define UART_INV_RTS UART_INVERSE_RTS +#define UART_INV_CTS UART_INVERSE_CTS +#else +#define UART_INV_TX UART_SIGNAL_TXD_INV +#define UART_INV_RX UART_SIGNAL_RXD_INV +#define UART_INV_RTS UART_SIGNAL_RTS_INV +#define UART_INV_CTS UART_SIGNAL_CTS_INV +#endif + +#define UART_INV_MASK (UART_INV_TX | UART_INV_RX | UART_INV_RTS | UART_INV_CTS) + +typedef struct _machine_uart_obj_t { + mp_obj_base_t base; + uart_port_t uart_num; + uart_hw_flowcontrol_t flowcontrol; + uint8_t bits; + uint8_t parity; + uint8_t stop; + int8_t tx; + int8_t rx; + int8_t rts; + int8_t cts; + uint16_t txbuf; + uint16_t rxbuf; + uint16_t timeout; // timeout waiting for first char (in ms) + uint16_t timeout_char; // timeout waiting between chars (in ms) + uint32_t invert; // lines to invert +} machine_uart_obj_t; + +STATIC const char *_parity_name[] = {"None", "1", "0"}; + +/******************************************************************************/ +// MicroPython bindings for UART + +STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + uint32_t baudrate; + uart_get_baudrate(self->uart_num, &baudrate); + mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u", + self->uart_num, baudrate, self->bits, _parity_name[self->parity], + self->stop, self->tx, self->rx, self->rts, self->cts, self->txbuf, self->rxbuf, self->timeout, self->timeout_char); + if (self->invert) { + mp_printf(print, ", invert="); + uint32_t invert_mask = self->invert; + if (invert_mask & UART_INV_TX) { + mp_printf(print, "INV_TX"); + invert_mask &= ~UART_INV_TX; + if (invert_mask) { + mp_printf(print, "|"); + } + } + if (invert_mask & UART_INV_RX) { + mp_printf(print, "INV_RX"); + invert_mask &= ~UART_INV_RX; + if (invert_mask) { + mp_printf(print, "|"); + } + } + if (invert_mask & UART_INV_RTS) { + mp_printf(print, "INV_RTS"); + invert_mask &= ~UART_INV_RTS; + if (invert_mask) { + mp_printf(print, "|"); + } + } + if (invert_mask & UART_INV_CTS) { + mp_printf(print, "INV_CTS"); + } + } + if (self->flowcontrol) { + mp_printf(print, ", flow="); + uint32_t flow_mask = self->flowcontrol; + if (flow_mask & UART_HW_FLOWCTRL_RTS) { + mp_printf(print, "RTS"); + flow_mask &= ~UART_HW_FLOWCTRL_RTS; + if (flow_mask) { + mp_printf(print, "|"); + } + } + if (flow_mask & UART_HW_FLOWCTRL_CTS) { + mp_printf(print, "CTS"); + } + } + mp_printf(print, ")"); +} + +STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char, ARG_invert, ARG_flow }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_bits, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_parity, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_stop, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_tx, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_PIN_NO_CHANGE} }, + { MP_QSTR_rx, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_PIN_NO_CHANGE} }, + { MP_QSTR_rts, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_PIN_NO_CHANGE} }, + { MP_QSTR_cts, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_PIN_NO_CHANGE} }, + { MP_QSTR_txbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_flow, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // wait for all data to be transmitted before changing settings + uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); + + if (args[ARG_txbuf].u_int >= 0 || args[ARG_rxbuf].u_int >= 0) { + // must reinitialise driver to change the tx/rx buffer size + if (self->uart_num == MICROPY_HW_UART_REPL) { + mp_raise_ValueError(MP_ERROR_TEXT("UART buffer size is fixed")); + } + + if (args[ARG_txbuf].u_int >= 0) { + self->txbuf = args[ARG_txbuf].u_int; + } + if (args[ARG_rxbuf].u_int >= 0) { + self->rxbuf = args[ARG_rxbuf].u_int; + } + uart_config_t uartcfg = { + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 0 + }; + uint32_t baudrate; + uart_get_baudrate(self->uart_num, &baudrate); + uartcfg.baud_rate = baudrate; + uart_get_word_length(self->uart_num, &uartcfg.data_bits); + uart_get_parity(self->uart_num, &uartcfg.parity); + uart_get_stop_bits(self->uart_num, &uartcfg.stop_bits); + uart_driver_delete(self->uart_num); + uart_param_config(self->uart_num, &uartcfg); + uart_driver_install(self->uart_num, self->rxbuf, self->txbuf, 0, NULL, 0); + } + + // set baudrate + uint32_t baudrate = 115200; + if (args[ARG_baudrate].u_int > 0) { + uart_set_baudrate(self->uart_num, args[ARG_baudrate].u_int); + } + uart_get_baudrate(self->uart_num, &baudrate); + + uart_set_pin(self->uart_num, args[ARG_tx].u_int, args[ARG_rx].u_int, args[ARG_rts].u_int, args[ARG_cts].u_int); + if (args[ARG_tx].u_int != UART_PIN_NO_CHANGE) { + self->tx = args[ARG_tx].u_int; + } + + if (args[ARG_rx].u_int != UART_PIN_NO_CHANGE) { + self->rx = args[ARG_rx].u_int; + } + + if (args[ARG_rts].u_int != UART_PIN_NO_CHANGE) { + self->rts = args[ARG_rts].u_int; + } + + if (args[ARG_cts].u_int != UART_PIN_NO_CHANGE) { + self->cts = args[ARG_cts].u_int; + } + + // set data bits + switch (args[ARG_bits].u_int) { + case 0: + break; + case 5: + uart_set_word_length(self->uart_num, UART_DATA_5_BITS); + self->bits = 5; + break; + case 6: + uart_set_word_length(self->uart_num, UART_DATA_6_BITS); + self->bits = 6; + break; + case 7: + uart_set_word_length(self->uart_num, UART_DATA_7_BITS); + self->bits = 7; + break; + case 8: + uart_set_word_length(self->uart_num, UART_DATA_8_BITS); + self->bits = 8; + break; + default: + mp_raise_ValueError(MP_ERROR_TEXT("invalid data bits")); + break; + } + + // set parity + if (args[ARG_parity].u_obj != MP_OBJ_NULL) { + if (args[ARG_parity].u_obj == mp_const_none) { + uart_set_parity(self->uart_num, UART_PARITY_DISABLE); + self->parity = 0; + } else { + mp_int_t parity = mp_obj_get_int(args[ARG_parity].u_obj); + if (parity & 1) { + uart_set_parity(self->uart_num, UART_PARITY_ODD); + self->parity = 1; + } else { + uart_set_parity(self->uart_num, UART_PARITY_EVEN); + self->parity = 2; + } + } + } + + // set stop bits + switch (args[ARG_stop].u_int) { + // FIXME: ESP32 also supports 1.5 stop bits + case 0: + break; + case 1: + uart_set_stop_bits(self->uart_num, UART_STOP_BITS_1); + self->stop = 1; + break; + case 2: + uart_set_stop_bits(self->uart_num, UART_STOP_BITS_2); + self->stop = 2; + break; + default: + mp_raise_ValueError(MP_ERROR_TEXT("invalid stop bits")); + break; + } + + // set timeout + if (args[ARG_timeout].u_int != -1) { + self->timeout = args[ARG_timeout].u_int; + } + + // set timeout_char + // make sure it is at least as long as a whole character (12 bits here) + if (args[ARG_timeout_char].u_int != -1) { + self->timeout_char = args[ARG_timeout_char].u_int; + uint32_t char_time_ms = 12000 / baudrate + 1; + uint32_t rx_timeout = self->timeout_char / char_time_ms; + if (rx_timeout < 1) { + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0) + uart_set_rx_full_threshold(self->uart_num, 1); + #endif + uart_set_rx_timeout(self->uart_num, 1); + } else { + uart_set_rx_timeout(self->uart_num, rx_timeout); + } + } + + // set line inversion + if (args[ARG_invert].u_int != -1) { + if (args[ARG_invert].u_int & ~UART_INV_MASK) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid inversion mask")); + } + self->invert = args[ARG_invert].u_int; + } + uart_set_line_inverse(self->uart_num, self->invert); + + // set hardware flow control + if (args[ARG_flow].u_int != -1) { + if (args[ARG_flow].u_int & ~UART_HW_FLOWCTRL_CTS_RTS) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid flow control mask")); + } + self->flowcontrol = args[ARG_flow].u_int; + } + uart_set_hw_flow_ctrl(self->uart_num, self->flowcontrol, UART_FIFO_LEN - UART_FIFO_LEN / 4); +} + +STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + + // get uart id + mp_int_t uart_num = mp_obj_get_int(args[0]); + if (uart_num < 0 || uart_num >= UART_NUM_MAX) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) does not exist"), uart_num); + } + + // Defaults + uart_config_t uartcfg = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 0 + }; + + // create instance + machine_uart_obj_t *self = mp_obj_malloc(machine_uart_obj_t, &machine_uart_type); + self->uart_num = uart_num; + self->bits = 8; + self->parity = 0; + self->stop = 1; + self->rts = UART_PIN_NO_CHANGE; + self->cts = UART_PIN_NO_CHANGE; + self->txbuf = 256; + self->rxbuf = 256; // IDF minimum + self->timeout = 0; + self->timeout_char = 0; + self->invert = 0; + self->flowcontrol = 0; + + switch (uart_num) { + case UART_NUM_0: + self->rx = UART_PIN_NO_CHANGE; // GPIO 3 + self->tx = UART_PIN_NO_CHANGE; // GPIO 1 + break; + case UART_NUM_1: + self->rx = 9; + self->tx = 10; + break; + #if SOC_UART_NUM > 2 + case UART_NUM_2: + self->rx = 16; + self->tx = 17; + break; + #endif + } + + // Only reset the driver if it's not the REPL UART. + if (uart_num != MICROPY_HW_UART_REPL) { + // Remove any existing configuration + uart_driver_delete(self->uart_num); + + // init the peripheral + // Setup + uart_param_config(self->uart_num, &uartcfg); + + uart_driver_install(uart_num, self->rxbuf, self->txbuf, 0, NULL, 0); + } + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_uart_init_helper(self, n_args - 1, args + 1, &kw_args); + + // Make sure pins are connected. + uart_set_pin(self->uart_num, self->tx, self->rx, self->rts, self->cts); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t machine_uart_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + machine_uart_init_helper(args[0], n_args - 1, args + 1, kw_args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_uart_init_obj, 1, machine_uart_init); + +STATIC mp_obj_t machine_uart_deinit(mp_obj_t self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + uart_driver_delete(self->uart_num); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_deinit_obj, machine_uart_deinit); + +STATIC mp_obj_t machine_uart_any(mp_obj_t self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + size_t rxbufsize; + uart_get_buffered_data_len(self->uart_num, &rxbufsize); + return MP_OBJ_NEW_SMALL_INT(rxbufsize); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_any_obj, machine_uart_any); + +STATIC mp_obj_t machine_uart_sendbreak(mp_obj_t self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Save settings + uint32_t baudrate; + uart_get_baudrate(self->uart_num, &baudrate); + + // Synthesise the break condition by reducing the baud rate, + // and cater for the worst case of 5 data bits, no parity. + uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); + uart_set_baudrate(self->uart_num, baudrate * 6 / 15); + char buf[1] = {0}; + uart_write_bytes(self->uart_num, buf, 1); + uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); + + // Restore original setting + uart_set_baudrate(self->uart_num, baudrate); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_sendbreak_obj, machine_uart_sendbreak); + +STATIC mp_obj_t machine_uart_txdone(mp_obj_t self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (uart_wait_tx_done(self->uart_num, 0) == ESP_OK) { + return mp_const_true; + } else { + return mp_const_false; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_txdone_obj, machine_uart_txdone); + +STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_uart_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_uart_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&machine_uart_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_sendbreak), MP_ROM_PTR(&machine_uart_sendbreak_obj) }, + { MP_ROM_QSTR(MP_QSTR_txdone), MP_ROM_PTR(&machine_uart_txdone_obj) }, + + { MP_ROM_QSTR(MP_QSTR_INV_TX), MP_ROM_INT(UART_INV_TX) }, + { MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INV_RX) }, + { MP_ROM_QSTR(MP_QSTR_INV_RTS), MP_ROM_INT(UART_INV_RTS) }, + { MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_INV_CTS) }, + + { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HW_FLOWCTRL_RTS) }, + { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HW_FLOWCTRL_CTS) }, +}; + +STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table); + +STATIC mp_uint_t machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // make sure we want at least 1 char + if (size == 0) { + return 0; + } + + TickType_t time_to_wait; + if (self->timeout == 0) { + time_to_wait = 0; + } else { + time_to_wait = pdMS_TO_TICKS(self->timeout); + } + + int bytes_read = uart_read_bytes(self->uart_num, buf_in, size, time_to_wait); + + if (bytes_read <= 0) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } + + return bytes_read; +} + +STATIC mp_uint_t machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + + int bytes_written = uart_write_bytes(self->uart_num, buf_in, size); + + if (bytes_written < 0) { + *errcode = MP_EAGAIN; + return MP_STREAM_ERROR; + } + + // return number of bytes written + return bytes_written; +} + +STATIC mp_uint_t machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + machine_uart_obj_t *self = self_in; + mp_uint_t ret; + if (request == MP_STREAM_POLL) { + mp_uint_t flags = arg; + ret = 0; + size_t rxbufsize; + uart_get_buffered_data_len(self->uart_num, &rxbufsize); + if ((flags & MP_STREAM_POLL_RD) && rxbufsize > 0) { + ret |= MP_STREAM_POLL_RD; + } + if ((flags & MP_STREAM_POLL_WR) && 1) { // FIXME: uart_tx_any_room(self->uart_num) + ret |= MP_STREAM_POLL_WR; + } + } else if (request == MP_STREAM_FLUSH) { + // The timeout is estimated using the buffer size and the baudrate. + // Take the worst case assumptions at 13 bit symbol size times 2. + uint32_t baudrate; + uart_get_baudrate(self->uart_num, &baudrate); + uint32_t timeout = (3 + self->txbuf) * 13000 * 2 / baudrate; + if (uart_wait_tx_done(self->uart_num, timeout) == ESP_OK) { + ret = 0; + } else { + *errcode = MP_ETIMEDOUT; + ret = MP_STREAM_ERROR; + } + } else { + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + return ret; +} + +STATIC const mp_stream_p_t uart_stream_p = { + .read = machine_uart_read, + .write = machine_uart_write, + .ioctl = machine_uart_ioctl, + .is_text = false, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + machine_uart_type, + MP_QSTR_UART, + MP_TYPE_FLAG_ITER_IS_STREAM, + make_new, machine_uart_make_new, + print, machine_uart_print, + protocol, &uart_stream_p, + locals_dict, &machine_uart_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/machine_wdt.c b/microros_ws/extra_packages/micropython/src/machine_wdt.c new file mode 100644 index 0000000..4ccf417 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/machine_wdt.c @@ -0,0 +1,92 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Paul Sokolovsky + * Copyright (c) 2017 Eric Poulsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/nlr.h" +#include "py/obj.h" +#include "py/runtime.h" + +#include "esp_task_wdt.h" + +const mp_obj_type_t machine_wdt_type; + +typedef struct _machine_wdt_obj_t { + mp_obj_base_t base; +} machine_wdt_obj_t; + +STATIC machine_wdt_obj_t wdt_default = {{&machine_wdt_type}}; + +STATIC mp_obj_t machine_wdt_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout, MP_ARG_INT, {.u_int = 5000} } + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (args[ARG_id].u_int != 0) { + mp_raise_ValueError(NULL); + } + + // Convert milliseconds to seconds (esp_task_wdt_init needs seconds) + args[ARG_timeout].u_int /= 1000; + + if (args[ARG_timeout].u_int <= 0) { + mp_raise_ValueError(MP_ERROR_TEXT("WDT timeout too short")); + } + + mp_int_t rs_code = esp_task_wdt_init(args[ARG_timeout].u_int, true); + if (rs_code != ESP_OK) { + mp_raise_OSError(rs_code); + } + + esp_task_wdt_add(NULL); + + return &wdt_default; +} + +STATIC mp_obj_t machine_wdt_feed(mp_obj_t self_in) { + (void)self_in; + esp_task_wdt_reset(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_wdt_feed_obj, machine_wdt_feed); + +STATIC const mp_rom_map_elem_t machine_wdt_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_feed), MP_ROM_PTR(&machine_wdt_feed_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_wdt_locals_dict, machine_wdt_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_wdt_type, + MP_QSTR_WDT, + MP_TYPE_FLAG_NONE, + make_new, machine_wdt_make_new, + locals_dict, &machine_wdt_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/main.c b/microros_ws/extra_packages/micropython/src/main.c new file mode 100644 index 0000000..784aebb --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/main.c @@ -0,0 +1,271 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "esp_task.h" +#include "soc/cpu.h" +#include "esp_log.h" + +#if CONFIG_IDF_TARGET_ESP32 +#include "esp32/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/spiram.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/spiram.h" +#endif + +#include "py/stackctrl.h" +#include "py/nlr.h" +#include "py/compile.h" +#include "py/runtime.h" +#include "py/persistentcode.h" +#include "py/repl.h" +#include "py/gc.h" +#include "py/mphal.h" +#include "shared/readline/readline.h" +#include "shared/runtime/pyexec.h" +#include "uart.h" +#include "usb.h" +#include "usb_serial_jtag.h" +#include "modmachine.h" +#include "modnetwork.h" +#include "mpthreadport.h" + +#if MICROPY_BLUETOOTH_NIMBLE +#include "extmod/modbluetooth.h" +#endif + +// MicroPython runs as a task under FreeRTOS +#define MP_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) +#define MP_TASK_STACK_SIZE (8 * 1024) + +// Set the margin for detecting stack overflow, depending on the CPU architecture. +#if CONFIG_IDF_TARGET_ESP32C3 +#define MP_TASK_STACK_LIMIT_MARGIN (2048) +#else +#define MP_TASK_STACK_LIMIT_MARGIN (1024) +#endif + +int vprintf_null(const char *format, va_list ap) { + // do nothing: this is used as a log target during raw repl mode + return 0; +} + +void mp_task(void *pvParameter) { + volatile uint32_t sp = (uint32_t)get_sp(); + #if MICROPY_PY_THREAD + mp_thread_init(pxTaskGetStackStart(NULL), MP_TASK_STACK_SIZE / sizeof(uintptr_t)); + #endif + #if CONFIG_USB_ENABLED + usb_init(); + #elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG + usb_serial_jtag_init(); + #endif + #if MICROPY_HW_ENABLE_UART_REPL + uart_stdout_init(); + #endif + machine_init(); + + size_t mp_task_heap_size; + void *mp_task_heap = NULL; + + #if CONFIG_SPIRAM_USE_MALLOC + // SPIRAM is issued using MALLOC, fallback to normal allocation rules + mp_task_heap = NULL; + #elif CONFIG_ESP32_SPIRAM_SUPPORT + // Try to use the entire external SPIRAM directly for the heap + mp_task_heap = (void *)SOC_EXTRAM_DATA_LOW; + switch (esp_spiram_get_chip_size()) { + case ESP_SPIRAM_SIZE_16MBITS: + mp_task_heap_size = 2 * 1024 * 1024; + break; + case ESP_SPIRAM_SIZE_32MBITS: + case ESP_SPIRAM_SIZE_64MBITS: + mp_task_heap_size = 4 * 1024 * 1024; + break; + default: + // No SPIRAM, fallback to normal allocation + mp_task_heap = NULL; + break; + } + #elif CONFIG_ESP32S2_SPIRAM_SUPPORT || CONFIG_ESP32S3_SPIRAM_SUPPORT + // Try to use the entire external SPIRAM directly for the heap + size_t esp_spiram_size = esp_spiram_get_size(); + if (esp_spiram_size > 0) { + mp_task_heap = (void *)SOC_EXTRAM_DATA_HIGH - esp_spiram_size; + mp_task_heap_size = esp_spiram_size; + } + #endif + + if (mp_task_heap == NULL) { + // Allocate the uPy heap using malloc and get the largest available region, + // limiting to 1/2 total available memory to leave memory for the OS. + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0) + size_t heap_total = heap_caps_get_total_size(MALLOC_CAP_8BIT); + #else + multi_heap_info_t info; + heap_caps_get_info(&info, MALLOC_CAP_8BIT); + size_t heap_total = info.total_free_bytes + info.total_allocated_bytes; + #endif + mp_task_heap_size = MIN(heap_caps_get_largest_free_block(MALLOC_CAP_8BIT), heap_total / 2); + mp_task_heap = malloc(mp_task_heap_size); + } + +soft_reset: + // initialise the stack pointer for the main thread + mp_stack_set_top((void *)sp); + mp_stack_set_limit(MP_TASK_STACK_SIZE - MP_TASK_STACK_LIMIT_MARGIN); + gc_init(mp_task_heap, mp_task_heap + mp_task_heap_size); + mp_init(); + mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_lib)); + readline_init0(); + + MP_STATE_PORT(native_code_pointers) = MP_OBJ_NULL; + + // initialise peripherals + machine_pins_init(); + #if MICROPY_PY_MACHINE_I2S + machine_i2s_init0(); + #endif + + // run boot-up scripts + pyexec_frozen_module("_boot.py"); + pyexec_file_if_exists("boot.py"); + if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { + int ret = pyexec_file_if_exists("main.py"); + if (ret & PYEXEC_FORCED_EXIT) { + goto soft_reset_exit; + } + } + + for (;;) { + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + vprintf_like_t vprintf_log = esp_log_set_vprintf(vprintf_null); + if (pyexec_raw_repl() != 0) { + break; + } + esp_log_set_vprintf(vprintf_log); + } else { + if (pyexec_friendly_repl() != 0) { + break; + } + } + } + +soft_reset_exit: + + #if MICROPY_BLUETOOTH_NIMBLE + mp_bluetooth_deinit(); + #endif + + machine_timer_deinit_all(); + + #if MICROPY_PY_THREAD + mp_thread_deinit(); + #endif + + // Free any native code pointers that point to iRAM. + if (MP_STATE_PORT(native_code_pointers) != MP_OBJ_NULL) { + size_t len; + mp_obj_t *items; + mp_obj_list_get(MP_STATE_PORT(native_code_pointers), &len, &items); + for (size_t i = 0; i < len; ++i) { + heap_caps_free(MP_OBJ_TO_PTR(items[i])); + } + } + + gc_sweep_all(); + + mp_hal_stdout_tx_str("MPY: soft reboot\r\n"); + + // deinitialise peripherals + machine_pwm_deinit_all(); + // TODO: machine_rmt_deinit_all(); + machine_pins_deinit(); + machine_deinit(); + #if MICROPY_PY_USOCKET_EVENTS + usocket_events_deinit(); + #endif + + mp_deinit(); + fflush(stdout); + goto soft_reset; +} + +void boardctrl_startup(void) { + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + nvs_flash_erase(); + nvs_flash_init(); + } +} + +void mp_app_main(void) { + // Hook for a board to run code at start up. + // This defaults to initialising NVS. +// MICROPY_BOARD_STARTUP(); + + // Create and transfer control to the MicroPython task. + xTaskCreatePinnedToCore(mp_task, "mp_task", MP_TASK_STACK_SIZE / sizeof(StackType_t), NULL, MP_TASK_PRIORITY, &mp_main_task_handle, MP_TASK_COREID); +} + +void nlr_jump_fail(void *val) { + printf("NLR jump failed, val=%p\n", val); + esp_restart(); +} + +// modussl_mbedtls uses this function but it's not enabled in ESP IDF +void mbedtls_debug_set_threshold(int threshold) { + (void)threshold; +} + +void *esp_native_code_commit(void *buf, size_t len, void *reloc) { + len = (len + 3) & ~3; + uint32_t *p = heap_caps_malloc(len, MALLOC_CAP_EXEC); + if (p == NULL) { + m_malloc_fail(len); + } + if (MP_STATE_PORT(native_code_pointers) == MP_OBJ_NULL) { + MP_STATE_PORT(native_code_pointers) = mp_obj_new_list(0, NULL); + } + mp_obj_list_append(MP_STATE_PORT(native_code_pointers), MP_OBJ_TO_PTR(p)); + if (reloc) { + mp_native_relocate(reloc, buf, (uintptr_t)p); + } + memcpy(p, buf, len); + return p; +} + +MP_REGISTER_ROOT_POINTER(mp_obj_t native_code_pointers); diff --git a/microros_ws/extra_packages/micropython/src/makeimg.py b/microros_ws/extra_packages/micropython/src/makeimg.py new file mode 100644 index 0000000..5964e91 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/makeimg.py @@ -0,0 +1,114 @@ +# Combine bootloader, partition table and application into a final binary. + +import os, sys + +sys.path.append(os.getenv("IDF_PATH") + "/components/partition_table") + +import gen_esp32part + +OFFSET_BOOTLOADER_DEFAULT = 0x1000 +OFFSET_PARTITIONS_DEFAULT = 0x8000 + + +def load_sdkconfig_value(filename, value, default): + value = "CONFIG_" + value + "=" + with open(filename, "r") as f: + for line in f: + if line.startswith(value): + return line.split("=", 1)[1] + return default + + +def load_sdkconfig_hex_value(filename, value, default): + value = load_sdkconfig_value(filename, value, None) + if value is None: + return default + return int(value, 16) + + +def load_sdkconfig_str_value(filename, value, default): + value = load_sdkconfig_value(filename, value, None) + if value is None: + return default + return value.strip().strip('"') + + +def load_partition_table(filename): + with open(filename, "rb") as f: + return gen_esp32part.PartitionTable.from_binary(f.read()) + + +# Extract command-line arguments. +arg_sdkconfig = sys.argv[1] +arg_bootloader_bin = sys.argv[2] +arg_partitions_bin = sys.argv[3] +arg_application_bin = sys.argv[4] +arg_output_bin = sys.argv[5] +arg_output_uf2 = sys.argv[6] + +# Load required sdkconfig values. +idf_target = load_sdkconfig_str_value(arg_sdkconfig, "IDF_TARGET", "").upper() +offset_bootloader = load_sdkconfig_hex_value( + arg_sdkconfig, "BOOTLOADER_OFFSET_IN_FLASH", OFFSET_BOOTLOADER_DEFAULT +) +offset_partitions = load_sdkconfig_hex_value( + arg_sdkconfig, "PARTITION_TABLE_OFFSET", OFFSET_PARTITIONS_DEFAULT +) + +# Load the partition table. +partition_table = load_partition_table(arg_partitions_bin) + +max_size_bootloader = offset_partitions - offset_bootloader +max_size_partitions = 0 +offset_application = 0 +max_size_application = 0 + +# Inspect the partition table to find offsets and maximum sizes. +for part in partition_table: + if part.name == "nvs": + max_size_partitions = part.offset - offset_partitions + elif part.type == gen_esp32part.APP_TYPE and offset_application == 0: + offset_application = part.offset + max_size_application = part.size + +# Define the input files, their location and maximum size. +files_in = [ + ("bootloader", offset_bootloader, max_size_bootloader, arg_bootloader_bin), + ("partitions", offset_partitions, max_size_partitions, arg_partitions_bin), + ("application", offset_application, max_size_application, arg_application_bin), +] +file_out = arg_output_bin + +# Write output file with combined firmware. +cur_offset = offset_bootloader +with open(file_out, "wb") as fout: + for name, offset, max_size, file_in in files_in: + assert offset >= cur_offset + fout.write(b"\xff" * (offset - cur_offset)) + cur_offset = offset + with open(file_in, "rb") as fin: + data = fin.read() + fout.write(data) + cur_offset += len(data) + print( + "%-12s@0x%06x % 8d (% 8d remaining)" + % (name, offset, len(data), max_size - len(data)) + ) + if len(data) > max_size: + print( + "ERROR: %s overflows allocated space of %d bytes by %d bytes" + % (name, max_size, len(data) - max_size) + ) + sys.exit(1) + print("%-22s% 8d" % ("total", cur_offset)) + +# Generate .uf2 file if the SoC has native USB. +if idf_target in ("ESP32S2", "ESP32S3"): + sys.path.append(os.path.join(os.path.dirname(__file__), "../../tools")) + import uf2conv + + families = uf2conv.load_families() + uf2conv.appstartaddr = 0 + uf2conv.familyid = families[idf_target] + with open(arg_application_bin, "rb") as fin, open(arg_output_uf2, "wb") as fout: + fout.write(uf2conv.convert_to_uf2(fin.read())) diff --git a/microros_ws/extra_packages/micropython/src/memory.h b/microros_ws/extra_packages/micropython/src/memory.h new file mode 100644 index 0000000..1f07fe4 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/memory.h @@ -0,0 +1,2 @@ +// this is needed for lib/crypto-algorithms/sha256.c +#include diff --git a/microros_ws/extra_packages/micropython/src/modesp.c b/microros_ws/extra_packages/micropython/src/modesp.c new file mode 100644 index 0000000..f125b61 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modesp.c @@ -0,0 +1,143 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Paul Sokolovsky + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "esp_log.h" +#include "esp_spi_flash.h" + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +STATIC mp_obj_t esp_osdebug(size_t n_args, const mp_obj_t *args) { + esp_log_level_t level = LOG_LOCAL_LEVEL; + if (n_args == 2) { + level = mp_obj_get_int(args[1]); + } + if (args[0] == mp_const_none) { + // Disable logging + esp_log_level_set("*", ESP_LOG_ERROR); + } else { + // Enable logging at the given level + // TODO args[0] should set the UART to which debug is sent + esp_log_level_set("*", level); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_osdebug_obj, 1, 2, esp_osdebug); + +STATIC mp_obj_t esp_flash_read(mp_obj_t offset_in, mp_obj_t buf_in) { + mp_int_t offset = mp_obj_get_int(offset_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE); + esp_err_t res = spi_flash_read(offset, bufinfo.buf, bufinfo.len); + if (res != ESP_OK) { + mp_raise_OSError(MP_EIO); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp_flash_read_obj, esp_flash_read); + +STATIC mp_obj_t esp_flash_write(mp_obj_t offset_in, mp_obj_t buf_in) { + mp_int_t offset = mp_obj_get_int(offset_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + esp_err_t res = spi_flash_write(offset, bufinfo.buf, bufinfo.len); + if (res != ESP_OK) { + mp_raise_OSError(MP_EIO); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp_flash_write_obj, esp_flash_write); + +STATIC mp_obj_t esp_flash_erase(mp_obj_t sector_in) { + mp_int_t sector = mp_obj_get_int(sector_in); + esp_err_t res = spi_flash_erase_sector(sector); + if (res != ESP_OK) { + mp_raise_OSError(MP_EIO); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_flash_erase_obj, esp_flash_erase); + +STATIC mp_obj_t esp_flash_size(void) { + return mp_obj_new_int_from_uint(spi_flash_get_chip_size()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_size_obj, esp_flash_size); + +STATIC mp_obj_t esp_flash_user_start(void) { + return MP_OBJ_NEW_SMALL_INT(0x200000); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_user_start_obj, esp_flash_user_start); + +STATIC mp_obj_t esp_gpio_matrix_in(mp_obj_t pin, mp_obj_t sig, mp_obj_t inv) { + gpio_matrix_in(mp_obj_get_int(pin), mp_obj_get_int(sig), mp_obj_get_int(inv)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp_gpio_matrix_in_obj, esp_gpio_matrix_in); + +STATIC mp_obj_t esp_gpio_matrix_out(size_t n_args, const mp_obj_t *args) { + (void)n_args; + gpio_matrix_out(mp_obj_get_int(args[0]), mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), mp_obj_get_int(args[3])); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_gpio_matrix_out_obj, 4, 4, esp_gpio_matrix_out); + +STATIC const mp_rom_map_elem_t esp_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp) }, + + { MP_ROM_QSTR(MP_QSTR_osdebug), MP_ROM_PTR(&esp_osdebug_obj) }, + + { MP_ROM_QSTR(MP_QSTR_flash_read), MP_ROM_PTR(&esp_flash_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_write), MP_ROM_PTR(&esp_flash_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_erase), MP_ROM_PTR(&esp_flash_erase_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_size), MP_ROM_PTR(&esp_flash_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_flash_user_start), MP_ROM_PTR(&esp_flash_user_start_obj) }, + + { MP_ROM_QSTR(MP_QSTR_gpio_matrix_in), MP_ROM_PTR(&esp_gpio_matrix_in_obj) }, + { MP_ROM_QSTR(MP_QSTR_gpio_matrix_out), MP_ROM_PTR(&esp_gpio_matrix_out_obj) }, + + // Constants for second arg of osdebug() + { MP_ROM_QSTR(MP_QSTR_LOG_NONE), MP_ROM_INT((mp_uint_t)ESP_LOG_NONE)}, + { MP_ROM_QSTR(MP_QSTR_LOG_ERROR), MP_ROM_INT((mp_uint_t)ESP_LOG_ERROR)}, + { MP_ROM_QSTR(MP_QSTR_LOG_WARNING), MP_ROM_INT((mp_uint_t)ESP_LOG_WARN)}, + { MP_ROM_QSTR(MP_QSTR_LOG_INFO), MP_ROM_INT((mp_uint_t)ESP_LOG_INFO)}, + { MP_ROM_QSTR(MP_QSTR_LOG_DEBUG), MP_ROM_INT((mp_uint_t)ESP_LOG_DEBUG)}, + { MP_ROM_QSTR(MP_QSTR_LOG_VERBOSE), MP_ROM_INT((mp_uint_t)ESP_LOG_VERBOSE)}, +}; + +STATIC MP_DEFINE_CONST_DICT(esp_module_globals, esp_module_globals_table); + +const mp_obj_module_t esp_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&esp_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_esp, esp_module); diff --git a/microros_ws/extra_packages/micropython/src/modesp32.c b/microros_ws/extra_packages/micropython/src/modesp32.c new file mode 100644 index 0000000..017db36 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modesp32.c @@ -0,0 +1,238 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 "Eric Poulsen" + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include +#include +#include "soc/rtc_cntl_reg.h" +#include "driver/gpio.h" +#include "driver/adc.h" +#include "esp_heap_caps.h" +#include "multi_heap.h" + +#include "py/nlr.h" +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "shared/timeutils/timeutils.h" +#include "modmachine.h" +#include "machine_rtc.h" +#include "modesp32.h" + +// These private includes are needed for idf_heap_info. +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) +#define MULTI_HEAP_FREERTOS +#include "../multi_heap_platform.h" +#endif +#include "../heap_private.h" + +STATIC mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { + + if (machine_rtc_config.ext0_pin != -1) { + mp_raise_ValueError(MP_ERROR_TEXT("no resources")); + } + // mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("touchpad wakeup not available for this version of ESP-IDF")); + + machine_rtc_config.wake_on_touch = mp_obj_is_true(wake); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_touch_obj, esp32_wake_on_touch); + +STATIC mp_obj_t esp32_wake_on_ext0(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + + if (machine_rtc_config.wake_on_touch) { + mp_raise_ValueError(MP_ERROR_TEXT("no resources")); + } + enum {ARG_pin, ARG_level}; + const mp_arg_t allowed_args[] = { + { MP_QSTR_pin, MP_ARG_OBJ, {.u_obj = mp_obj_new_int(machine_rtc_config.ext0_pin)} }, + { MP_QSTR_level, MP_ARG_BOOL, {.u_bool = machine_rtc_config.ext0_level} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (args[ARG_pin].u_obj == mp_const_none) { + machine_rtc_config.ext0_pin = -1; // "None" + } else { + gpio_num_t pin_id = machine_pin_get_id(args[ARG_pin].u_obj); + if (pin_id != machine_rtc_config.ext0_pin) { + if (!RTC_IS_VALID_EXT_PIN(pin_id)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); + } + machine_rtc_config.ext0_pin = pin_id; + } + } + + machine_rtc_config.ext0_level = args[ARG_level].u_bool; + machine_rtc_config.ext0_wake_types = MACHINE_WAKE_SLEEP | MACHINE_WAKE_DEEPSLEEP; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext0_obj, 0, esp32_wake_on_ext0); + +STATIC mp_obj_t esp32_wake_on_ext1(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum {ARG_pins, ARG_level}; + const mp_arg_t allowed_args[] = { + { MP_QSTR_pins, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_level, MP_ARG_BOOL, {.u_bool = machine_rtc_config.ext1_level} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + uint64_t ext1_pins = machine_rtc_config.ext1_pins; + + + // Check that all pins are allowed + if (args[ARG_pins].u_obj != mp_const_none) { + size_t len = 0; + mp_obj_t *elem; + mp_obj_get_array(args[ARG_pins].u_obj, &len, &elem); + ext1_pins = 0; + + for (int i = 0; i < len; i++) { + + gpio_num_t pin_id = machine_pin_get_id(elem[i]); + if (!RTC_IS_VALID_EXT_PIN(pin_id)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); + break; + } + ext1_pins |= (1ll << pin_id); + } + } + + machine_rtc_config.ext1_level = args[ARG_level].u_bool; + machine_rtc_config.ext1_pins = ext1_pins; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext1_obj, 0, esp32_wake_on_ext1); + +STATIC mp_obj_t esp32_wake_on_ulp(const mp_obj_t wake) { + if (machine_rtc_config.ext0_pin != -1) { + mp_raise_ValueError(MP_ERROR_TEXT("no resources")); + } + machine_rtc_config.wake_on_ulp = mp_obj_is_true(wake); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_ulp_obj, esp32_wake_on_ulp); + +STATIC mp_obj_t esp32_gpio_deep_sleep_hold(const mp_obj_t enable) { + if (mp_obj_is_true(enable)) { + gpio_deep_sleep_hold_en(); + } else { + gpio_deep_sleep_hold_dis(); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_gpio_deep_sleep_hold_obj, esp32_gpio_deep_sleep_hold); + +#if CONFIG_IDF_TARGET_ESP32 + +#include "soc/sens_reg.h" + +STATIC mp_obj_t esp32_raw_temperature(void) { + SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 3, SENS_FORCE_XPD_SAR_S); + SET_PERI_REG_BITS(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_CLK_DIV, 10, SENS_TSENS_CLK_DIV_S); + CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP); + CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP_FORCE); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP); + ets_delay_us(100); + SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT); + ets_delay_us(5); + int res = GET_PERI_REG_BITS2(SENS_SAR_SLAVE_ADDR3_REG, SENS_TSENS_OUT, SENS_TSENS_OUT_S); + + return mp_obj_new_int(res); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp32_raw_temperature_obj, esp32_raw_temperature); + +STATIC mp_obj_t esp32_hall_sensor(void) { + adc1_config_width(ADC_WIDTH_12Bit); + return MP_OBJ_NEW_SMALL_INT(hall_sensor_read()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp32_hall_sensor_obj, esp32_hall_sensor); + +#endif + +STATIC mp_obj_t esp32_idf_heap_info(const mp_obj_t cap_in) { + mp_int_t cap = mp_obj_get_int(cap_in); + multi_heap_info_t info; + heap_t *heap; + mp_obj_t heap_list = mp_obj_new_list(0, 0); + SLIST_FOREACH(heap, ®istered_heaps, next) { + if (heap_caps_match(heap, cap)) { + multi_heap_get_info(heap->heap, &info); + mp_obj_t data[] = { + MP_OBJ_NEW_SMALL_INT(heap->end - heap->start), // total heap size + MP_OBJ_NEW_SMALL_INT(info.total_free_bytes), // total free bytes + MP_OBJ_NEW_SMALL_INT(info.largest_free_block), // largest free contiguous + MP_OBJ_NEW_SMALL_INT(info.minimum_free_bytes), // minimum free seen + }; + mp_obj_t this_heap = mp_obj_new_tuple(4, data); + mp_obj_list_append(heap_list, this_heap); + } + } + return heap_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_idf_heap_info_obj, esp32_idf_heap_info); + +STATIC const mp_rom_map_elem_t esp32_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp32) }, + + { MP_ROM_QSTR(MP_QSTR_wake_on_touch), MP_ROM_PTR(&esp32_wake_on_touch_obj) }, + { MP_ROM_QSTR(MP_QSTR_wake_on_ext0), MP_ROM_PTR(&esp32_wake_on_ext0_obj) }, + { MP_ROM_QSTR(MP_QSTR_wake_on_ext1), MP_ROM_PTR(&esp32_wake_on_ext1_obj) }, + { MP_ROM_QSTR(MP_QSTR_wake_on_ulp), MP_ROM_PTR(&esp32_wake_on_ulp_obj) }, + { MP_ROM_QSTR(MP_QSTR_gpio_deep_sleep_hold), MP_ROM_PTR(&esp32_gpio_deep_sleep_hold_obj) }, + #if CONFIG_IDF_TARGET_ESP32 + { MP_ROM_QSTR(MP_QSTR_raw_temperature), MP_ROM_PTR(&esp32_raw_temperature_obj) }, + { MP_ROM_QSTR(MP_QSTR_hall_sensor), MP_ROM_PTR(&esp32_hall_sensor_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_idf_heap_info), MP_ROM_PTR(&esp32_idf_heap_info_obj) }, + + { MP_ROM_QSTR(MP_QSTR_NVS), MP_ROM_PTR(&esp32_nvs_type) }, + { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, + { MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) }, + #if CONFIG_IDF_TARGET_ESP32 + { MP_ROM_QSTR(MP_QSTR_ULP), MP_ROM_PTR(&esp32_ulp_type) }, + #endif + + { MP_ROM_QSTR(MP_QSTR_WAKEUP_ALL_LOW), MP_ROM_FALSE }, + { MP_ROM_QSTR(MP_QSTR_WAKEUP_ANY_HIGH), MP_ROM_TRUE }, + + { MP_ROM_QSTR(MP_QSTR_HEAP_DATA), MP_ROM_INT(MALLOC_CAP_8BIT) }, + { MP_ROM_QSTR(MP_QSTR_HEAP_EXEC), MP_ROM_INT(MALLOC_CAP_EXEC) }, +}; + +STATIC MP_DEFINE_CONST_DICT(esp32_module_globals, esp32_module_globals_table); + +const mp_obj_module_t esp32_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&esp32_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_esp32, esp32_module); diff --git a/microros_ws/extra_packages/micropython/src/modesp32.h b/microros_ws/extra_packages/micropython/src/modesp32.h new file mode 100644 index 0000000..a685b7b --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modesp32.h @@ -0,0 +1,71 @@ +#ifndef MICROPY_INCLUDED_ESP32_MODESP32_H +#define MICROPY_INCLUDED_ESP32_MODESP32_H + +#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + + #define RTC_VALID_EXT_PINS \ + ( \ + (1ll << 0) | \ + (1ll << 1) | \ + (1ll << 2) | \ + (1ll << 3) | \ + (1ll << 4) | \ + (1ll << 5) | \ + (1ll << 6) | \ + (1ll << 7) | \ + (1ll << 8) | \ + (1ll << 9) | \ + (1ll << 10) | \ + (1ll << 11) | \ + (1ll << 12) | \ + (1ll << 13) | \ + (1ll << 14) | \ + (1ll << 15) | \ + (1ll << 16) | \ + (1ll << 17) | \ + (1ll << 18) | \ + (1ll << 19) | \ + (1ll << 20) | \ + (1ll << 21) \ + ) + #define RTC_LAST_EXT_PIN 21 + +#else + + #define RTC_VALID_EXT_PINS \ + ( \ + (1ll << 0) | \ + (1ll << 2) | \ + (1ll << 4) | \ + (1ll << 12) | \ + (1ll << 13) | \ + (1ll << 14) | \ + (1ll << 15) | \ + (1ll << 25) | \ + (1ll << 26) | \ + (1ll << 27) | \ + (1ll << 32) | \ + (1ll << 33) | \ + (1ll << 34) | \ + (1ll << 35) | \ + (1ll << 36) | \ + (1ll << 37) | \ + (1ll << 38) | \ + (1ll << 39) \ + ) + #define RTC_LAST_EXT_PIN 39 + +#endif + +#define RTC_IS_VALID_EXT_PIN(pin_id) ((1ll << (pin_id)) & RTC_VALID_EXT_PINS) + +extern int8_t esp32_rmt_bitstream_channel_id; + +extern const mp_obj_type_t esp32_nvs_type; +extern const mp_obj_type_t esp32_partition_type; +extern const mp_obj_type_t esp32_rmt_type; +extern const mp_obj_type_t esp32_ulp_type; + +esp_err_t rmt_driver_install_core1(uint8_t channel_id); + +#endif // MICROPY_INCLUDED_ESP32_MODESP32_H diff --git a/microros_ws/extra_packages/micropython/src/modmachine.c b/microros_ws/extra_packages/micropython/src/modmachine.c new file mode 100644 index 0000000..37d4f42 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modmachine.c @@ -0,0 +1,358 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2013-2015 Damien P. George + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_sleep.h" +#include "esp_pm.h" + +#if CONFIG_IDF_TARGET_ESP32 +#include "esp32/rom/rtc.h" +#include "esp32/clk.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rom/rtc.h" +#include "esp32s2/clk.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rom/rtc.h" +#include "esp32s3/clk.h" +#endif + +#include "py/obj.h" +#include "py/runtime.h" +#include "shared/runtime/pyexec.h" +#include "drivers/dht/dht.h" +#include "extmod/machine_bitstream.h" +#include "extmod/machine_mem.h" +#include "extmod/machine_signal.h" +#include "extmod/machine_pulse.h" +#include "extmod/machine_pwm.h" +#include "extmod/machine_i2c.h" +#include "extmod/machine_spi.h" +#include "modmachine.h" +#include "machine_rtc.h" + +#if MICROPY_PY_MACHINE + +typedef enum { + MP_PWRON_RESET = 1, + MP_HARD_RESET, + MP_WDT_RESET, + MP_DEEPSLEEP_RESET, + MP_SOFT_RESET +} reset_reason_t; + +STATIC bool is_soft_reset = 0; + +#if CONFIG_IDF_TARGET_ESP32C3 +int esp_clk_cpu_freq(void); +#endif + +STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + // get + return mp_obj_new_int(esp_clk_cpu_freq()); + } else { + // set + mp_int_t freq = mp_obj_get_int(args[0]) / 1000000; + if (freq != 20 && freq != 40 && freq != 80 && freq != 160 + #if !CONFIG_IDF_TARGET_ESP32C3 + && freq != 240 + #endif + ) { + #if CONFIG_IDF_TARGET_ESP32C3 + mp_raise_ValueError(MP_ERROR_TEXT("frequency must be 20MHz, 40MHz, 80Mhz or 160MHz")); + #else + mp_raise_ValueError(MP_ERROR_TEXT("frequency must be 20MHz, 40MHz, 80Mhz, 160MHz or 240MHz")); + #endif + } + #if CONFIG_IDF_TARGET_ESP32 + esp_pm_config_esp32_t pm; + #elif CONFIG_IDF_TARGET_ESP32C3 + esp_pm_config_esp32c3_t pm; + #elif CONFIG_IDF_TARGET_ESP32S2 + esp_pm_config_esp32s2_t pm; + #elif CONFIG_IDF_TARGET_ESP32S3 + esp_pm_config_esp32s3_t pm; + #endif + pm.max_freq_mhz = freq; + pm.min_freq_mhz = freq; + pm.light_sleep_enable = false; + esp_err_t ret = esp_pm_configure(&pm); + if (ret != ESP_OK) { + mp_raise_ValueError(NULL); + } + while (esp_clk_cpu_freq() != freq * 1000000) { + vTaskDelay(1); + } + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_freq_obj, 0, 1, machine_freq); + +STATIC mp_obj_t machine_sleep_helper(wake_type_t wake_type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + + enum {ARG_sleep_ms}; + const mp_arg_t allowed_args[] = { + { MP_QSTR_sleep_ms, MP_ARG_INT, { .u_int = 0 } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + + mp_int_t expiry = args[ARG_sleep_ms].u_int; + + if (expiry != 0) { + esp_sleep_enable_timer_wakeup(((uint64_t)expiry) * 1000); + } + + #if !CONFIG_IDF_TARGET_ESP32C3 + + if (machine_rtc_config.ext0_pin != -1 && (machine_rtc_config.ext0_wake_types & wake_type)) { + esp_sleep_enable_ext0_wakeup(machine_rtc_config.ext0_pin, machine_rtc_config.ext0_level ? 1 : 0); + } + + if (machine_rtc_config.ext1_pins != 0) { + esp_sleep_enable_ext1_wakeup( + machine_rtc_config.ext1_pins, + machine_rtc_config.ext1_level ? ESP_EXT1_WAKEUP_ANY_HIGH : ESP_EXT1_WAKEUP_ALL_LOW); + } + + if (machine_rtc_config.wake_on_touch) { + if (esp_sleep_enable_touchpad_wakeup() != ESP_OK) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("esp_sleep_enable_touchpad_wakeup() failed")); + } + } + + if (machine_rtc_config.wake_on_ulp) { + if (esp_sleep_enable_ulp_wakeup() != ESP_OK) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("esp_sleep_enable_ulp_wakeup() failed")); + } + } + + #endif + + switch (wake_type) { + case MACHINE_WAKE_SLEEP: + esp_light_sleep_start(); + break; + case MACHINE_WAKE_DEEPSLEEP: + esp_deep_sleep_start(); + break; + } + return mp_const_none; +} + +STATIC mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return machine_sleep_helper(MACHINE_WAKE_SLEEP, n_args, pos_args, kw_args); +}; +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_lightsleep_obj, 0, machine_lightsleep); + +STATIC mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return machine_sleep_helper(MACHINE_WAKE_DEEPSLEEP, n_args, pos_args, kw_args); +}; +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_deepsleep_obj, 0, machine_deepsleep); + +STATIC mp_obj_t machine_reset_cause(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + if (is_soft_reset) { + return MP_OBJ_NEW_SMALL_INT(MP_SOFT_RESET); + } + switch (esp_reset_reason()) { + case ESP_RST_POWERON: + case ESP_RST_BROWNOUT: + return MP_OBJ_NEW_SMALL_INT(MP_PWRON_RESET); + break; + + case ESP_RST_INT_WDT: + case ESP_RST_TASK_WDT: + case ESP_RST_WDT: + return MP_OBJ_NEW_SMALL_INT(MP_WDT_RESET); + break; + + case ESP_RST_DEEPSLEEP: + return MP_OBJ_NEW_SMALL_INT(MP_DEEPSLEEP_RESET); + break; + + case ESP_RST_SW: + case ESP_RST_PANIC: + case ESP_RST_EXT: // Comment in ESP-IDF: "For ESP32, ESP_RST_EXT is never returned" + return MP_OBJ_NEW_SMALL_INT(MP_HARD_RESET); + break; + + case ESP_RST_SDIO: + case ESP_RST_UNKNOWN: + default: + return MP_OBJ_NEW_SMALL_INT(0); + break; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_reset_cause_obj, 0, machine_reset_cause); + +void machine_init(void) { + is_soft_reset = 0; +} + +void machine_deinit(void) { + // we are doing a soft-reset so change the reset_cause + is_soft_reset = 1; +} + +STATIC mp_obj_t machine_wake_reason(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return MP_OBJ_NEW_SMALL_INT(esp_sleep_get_wakeup_cause()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_wake_reason_obj, 0, machine_wake_reason); + +STATIC mp_obj_t machine_reset(void) { + esp_restart(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_obj, machine_reset); + +STATIC mp_obj_t machine_soft_reset(void) { + pyexec_system_exit = PYEXEC_FORCED_EXIT; + mp_raise_type(&mp_type_SystemExit); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_soft_reset_obj, machine_soft_reset); + +STATIC mp_obj_t machine_unique_id(void) { + uint8_t chipid[6]; + esp_efuse_mac_get_default(chipid); + return mp_obj_new_bytes(chipid, 6); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id); + +STATIC mp_obj_t machine_idle(void) { + MP_THREAD_GIL_EXIT(); + taskYIELD(); + MP_THREAD_GIL_ENTER(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); + +STATIC mp_obj_t machine_disable_irq(void) { + uint32_t state = MICROPY_BEGIN_ATOMIC_SECTION(); + return mp_obj_new_int(state); +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_disable_irq_obj, machine_disable_irq); + +STATIC mp_obj_t machine_enable_irq(mp_obj_t state_in) { + uint32_t state = mp_obj_get_int(state_in); + MICROPY_END_ATOMIC_SECTION(state); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_enable_irq_obj, machine_enable_irq); + +STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_umachine) }, + + { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, + { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, + + { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&machine_freq_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_soft_reset), MP_ROM_PTR(&machine_soft_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_unique_id), MP_ROM_PTR(&machine_unique_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&machine_lightsleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_lightsleep), MP_ROM_PTR(&machine_lightsleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_deepsleep), MP_ROM_PTR(&machine_deepsleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, + + { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, + + #if MICROPY_PY_MACHINE_BITSTREAM + { MP_ROM_QSTR(MP_QSTR_bitstream), MP_ROM_PTR(&machine_bitstream_obj) }, + #endif + #if MICROPY_PY_MACHINE_PULSE + { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_dht_readinto), MP_ROM_PTR(&dht_readinto_obj) }, + + { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, + { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) }, + #if MICROPY_HW_ENABLE_SDCARD + { MP_ROM_QSTR(MP_QSTR_SDCard), MP_ROM_PTR(&machine_sdcard_type) }, + #endif + + // wake abilities + { MP_ROM_QSTR(MP_QSTR_SLEEP), MP_ROM_INT(MACHINE_WAKE_SLEEP) }, + { MP_ROM_QSTR(MP_QSTR_DEEPSLEEP), MP_ROM_INT(MACHINE_WAKE_DEEPSLEEP) }, + { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, + { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, + #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + { MP_ROM_QSTR(MP_QSTR_TouchPad), MP_ROM_PTR(&machine_touchpad_type) }, + #endif + { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, + { MP_ROM_QSTR(MP_QSTR_ADCBlock), MP_ROM_PTR(&machine_adcblock_type) }, + #if MICROPY_PY_MACHINE_DAC + { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) }, + #endif + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, + #if MICROPY_PY_MACHINE_I2S + { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&machine_i2s_type) }, + #endif + { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, + { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, + + // Reset reasons + { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, + { MP_ROM_QSTR(MP_QSTR_HARD_RESET), MP_ROM_INT(MP_HARD_RESET) }, + { MP_ROM_QSTR(MP_QSTR_PWRON_RESET), MP_ROM_INT(MP_PWRON_RESET) }, + { MP_ROM_QSTR(MP_QSTR_WDT_RESET), MP_ROM_INT(MP_WDT_RESET) }, + { MP_ROM_QSTR(MP_QSTR_DEEPSLEEP_RESET), MP_ROM_INT(MP_DEEPSLEEP_RESET) }, + { MP_ROM_QSTR(MP_QSTR_SOFT_RESET), MP_ROM_INT(MP_SOFT_RESET) }, + + // Wake reasons + { MP_ROM_QSTR(MP_QSTR_wake_reason), MP_ROM_PTR(&machine_wake_reason_obj) }, + { MP_ROM_QSTR(MP_QSTR_PIN_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_EXT0) }, + { MP_ROM_QSTR(MP_QSTR_EXT0_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_EXT0) }, + { MP_ROM_QSTR(MP_QSTR_EXT1_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_EXT1) }, + { MP_ROM_QSTR(MP_QSTR_TIMER_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_TIMER) }, + { MP_ROM_QSTR(MP_QSTR_TOUCHPAD_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_TOUCHPAD) }, + { MP_ROM_QSTR(MP_QSTR_ULP_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_ULP) }, +}; + +STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); + +const mp_obj_module_t mp_module_machine = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&machine_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_umachine, mp_module_machine); + +#endif // MICROPY_PY_MACHINE diff --git a/microros_ws/extra_packages/micropython/src/modmachine.h b/microros_ws/extra_packages/micropython/src/modmachine.h new file mode 100644 index 0000000..138a89e --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modmachine.h @@ -0,0 +1,35 @@ +#ifndef MICROPY_INCLUDED_ESP32_MODMACHINE_H +#define MICROPY_INCLUDED_ESP32_MODMACHINE_H + +#include "py/obj.h" + +typedef enum { + // MACHINE_WAKE_IDLE=0x01, + MACHINE_WAKE_SLEEP=0x02, + MACHINE_WAKE_DEEPSLEEP=0x04 +} wake_type_t; + +extern const mp_obj_type_t machine_timer_type; +extern const mp_obj_type_t machine_wdt_type; +extern const mp_obj_type_t machine_pin_type; +extern const mp_obj_type_t machine_touchpad_type; +extern const mp_obj_type_t machine_adc_type; +extern const mp_obj_type_t machine_adcblock_type; +extern const mp_obj_type_t machine_dac_type; +extern const mp_obj_type_t machine_i2c_type; +extern const mp_obj_type_t machine_spi_type; +extern const mp_obj_type_t machine_i2s_type; +extern const mp_obj_type_t machine_uart_type; +extern const mp_obj_type_t machine_rtc_type; +extern const mp_obj_type_t machine_sdcard_type; + +void machine_init(void); +void machine_deinit(void); +void machine_pins_init(void); +void machine_pins_deinit(void); +void machine_pwm_deinit_all(void); +// TODO: void machine_rmt_deinit_all(void); +void machine_timer_deinit_all(void); +void machine_i2s_init0(); + +#endif // MICROPY_INCLUDED_ESP32_MODMACHINE_H diff --git a/microros_ws/extra_packages/micropython/src/modnetwork.c b/microros_ws/extra_packages/micropython/src/modnetwork.c new file mode 100644 index 0000000..3ebba2a --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modnetwork.c @@ -0,0 +1,313 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * and Mnemote Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 2017 Nick Moore @mnemote + * Copyright (c) 2017 "Eric Poulsen" + * + * Based on esp8266/modnetwork.c which is Copyright (c) 2015 Paul Sokolovsky + * And the ESP IDF example code which is Public Domain / CC0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "shared/netutils/netutils.h" +#include "modnetwork.h" + +#include "esp_wifi.h" +#include "esp_log.h" +#include "lwip/dns.h" + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) +#define DNS_MAIN TCPIP_ADAPTER_DNS_MAIN +#else +#define DNS_MAIN ESP_NETIF_DNS_MAIN +#endif + +#define MODNETWORK_INCLUDE_CONSTANTS (1) + +NORETURN void esp_exceptions_helper(esp_err_t e) { + switch (e) { + case ESP_ERR_WIFI_NOT_INIT: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Not Initialized")); + case ESP_ERR_WIFI_NOT_STARTED: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Not Started")); + case ESP_ERR_WIFI_NOT_STOPPED: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Not Stopped")); + case ESP_ERR_WIFI_IF: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Invalid Interface")); + case ESP_ERR_WIFI_MODE: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Invalid Mode")); + case ESP_ERR_WIFI_STATE: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Internal State Error")); + case ESP_ERR_WIFI_CONN: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Internal Error")); + case ESP_ERR_WIFI_NVS: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Internal NVS Error")); + case ESP_ERR_WIFI_MAC: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Invalid MAC Address")); + case ESP_ERR_WIFI_SSID: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi SSID Invalid")); + case ESP_ERR_WIFI_PASSWORD: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Invalid Password")); + case ESP_ERR_WIFI_TIMEOUT: + mp_raise_OSError(MP_ETIMEDOUT); + case ESP_ERR_WIFI_WAKE_FAIL: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Wakeup Failure")); + case ESP_ERR_WIFI_WOULD_BLOCK: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Would Block")); + case ESP_ERR_WIFI_NOT_CONNECT: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Wifi Not Connected")); + case ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("TCP/IP Invalid Parameters")); + case ESP_ERR_TCPIP_ADAPTER_IF_NOT_READY: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("TCP/IP IF Not Ready")); + case ESP_ERR_TCPIP_ADAPTER_DHCPC_START_FAILED: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("TCP/IP DHCP Client Start Failed")); + case ESP_ERR_TCPIP_ADAPTER_NO_MEM: + mp_raise_OSError(MP_ENOMEM); + default: + mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Wifi Unknown Error 0x%04x"), e); + } +} + +// This function is called by the system-event task and so runs in a different +// thread to the main MicroPython task. It must not raise any Python exceptions. +static esp_err_t event_handler(void *ctx, system_event_t *event) { + switch (event->event_id) { + #if MICROPY_PY_NETWORK_WLAN + case SYSTEM_EVENT_STA_START: + case SYSTEM_EVENT_STA_CONNECTED: + case SYSTEM_EVENT_STA_GOT_IP: + case SYSTEM_EVENT_STA_DISCONNECTED: + network_wlan_event_handler(event); + break; + #endif + case SYSTEM_EVENT_GOT_IP6: + ESP_LOGI("network", "Got IPv6"); + break; + case SYSTEM_EVENT_ETH_START: + ESP_LOGI("ethernet", "start"); + break; + case SYSTEM_EVENT_ETH_STOP: + ESP_LOGI("ethernet", "stop"); + break; + case SYSTEM_EVENT_ETH_CONNECTED: + ESP_LOGI("ethernet", "LAN cable connected"); + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + ESP_LOGI("ethernet", "LAN cable disconnected"); + break; + case SYSTEM_EVENT_ETH_GOT_IP: + ESP_LOGI("ethernet", "Got IP"); + break; + default: + ESP_LOGI("network", "event %d", event->event_id); + break; + } + return ESP_OK; +} + +STATIC mp_obj_t esp_initialize() { + static int initialized = 0; + if (!initialized) { + ESP_LOGD("modnetwork", "Initializing TCP/IP"); + tcpip_adapter_init(); + ESP_LOGD("modnetwork", "Initializing Event Loop"); + esp_exceptions(esp_event_loop_init(event_handler, NULL)); + ESP_LOGD("modnetwork", "esp_event_loop_init done"); + initialized = 1; + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_initialize_obj, esp_initialize); + +STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + tcpip_adapter_ip_info_t info; + tcpip_adapter_dns_info_t dns_info; + tcpip_adapter_get_ip_info(self->if_id, &info); + tcpip_adapter_get_dns_info(self->if_id, DNS_MAIN, &dns_info); + if (n_args == 1) { + // get + mp_obj_t tuple[4] = { + netutils_format_ipv4_addr((uint8_t *)&info.ip, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&info.netmask, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&info.gw, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&dns_info.ip, NETUTILS_BIG), + }; + return mp_obj_new_tuple(4, tuple); + } else { + // set + if (mp_obj_is_type(args[1], &mp_type_tuple) || mp_obj_is_type(args[1], &mp_type_list)) { + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[1], 4, &items); + netutils_parse_ipv4_addr(items[0], (void *)&info.ip, NETUTILS_BIG); + if (mp_obj_is_integer(items[1])) { + // allow numeric netmask, i.e.: + // 24 -> 255.255.255.0 + // 16 -> 255.255.0.0 + // etc... + uint32_t *m = (uint32_t *)&info.netmask; + *m = htonl(0xffffffff << (32 - mp_obj_get_int(items[1]))); + } else { + netutils_parse_ipv4_addr(items[1], (void *)&info.netmask, NETUTILS_BIG); + } + netutils_parse_ipv4_addr(items[2], (void *)&info.gw, NETUTILS_BIG); + netutils_parse_ipv4_addr(items[3], (void *)&dns_info.ip, NETUTILS_BIG); + // To set a static IP we have to disable DHCP first + if (self->if_id == WIFI_IF_STA || self->if_id == ESP_IF_ETH) { + esp_err_t e = tcpip_adapter_dhcpc_stop(self->if_id); + if (e != ESP_OK && e != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { + esp_exceptions_helper(e); + } + esp_exceptions(tcpip_adapter_set_ip_info(self->if_id, &info)); + esp_exceptions(tcpip_adapter_set_dns_info(self->if_id, DNS_MAIN, &dns_info)); + } else if (self->if_id == WIFI_IF_AP) { + esp_err_t e = tcpip_adapter_dhcps_stop(WIFI_IF_AP); + if (e != ESP_OK && e != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { + esp_exceptions_helper(e); + } + esp_exceptions(tcpip_adapter_set_ip_info(WIFI_IF_AP, &info)); + esp_exceptions(tcpip_adapter_set_dns_info(WIFI_IF_AP, DNS_MAIN, &dns_info)); + esp_exceptions(tcpip_adapter_dhcps_start(WIFI_IF_AP)); + } + } else { + // check for the correct string + const char *mode = mp_obj_str_get_str(args[1]); + if ((self->if_id != WIFI_IF_STA && self->if_id != ESP_IF_ETH) || strcmp("dhcp", mode)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments")); + } + esp_exceptions(tcpip_adapter_dhcpc_start(self->if_id)); + } + return mp_const_none; + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_ifconfig_obj, 1, 2, esp_ifconfig); + +STATIC mp_obj_t esp_phy_mode(size_t n_args, const mp_obj_t *args) { + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_phy_mode_obj, 0, 1, esp_phy_mode); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) +#define TEST_WIFI_AUTH_MAX 9 +#else +#define TEST_WIFI_AUTH_MAX 8 +#endif +_Static_assert(WIFI_AUTH_MAX == TEST_WIFI_AUTH_MAX, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); + +STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_network) }, + { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&esp_initialize_obj) }, + + #if MICROPY_PY_NETWORK_WLAN + { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&get_wlan_obj) }, + #endif + + #if MICROPY_PY_NETWORK_LAN + { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&get_lan_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&ppp_make_new_obj) }, + { MP_ROM_QSTR(MP_QSTR_phy_mode), MP_ROM_PTR(&esp_phy_mode_obj) }, + + #if MODNETWORK_INCLUDE_CONSTANTS + + #if MICROPY_PY_NETWORK_WLAN + { MP_ROM_QSTR(MP_QSTR_STA_IF), MP_ROM_INT(WIFI_IF_STA)}, + { MP_ROM_QSTR(MP_QSTR_AP_IF), MP_ROM_INT(WIFI_IF_AP)}, + + { MP_ROM_QSTR(MP_QSTR_MODE_11B), MP_ROM_INT(WIFI_PROTOCOL_11B) }, + { MP_ROM_QSTR(MP_QSTR_MODE_11G), MP_ROM_INT(WIFI_PROTOCOL_11G) }, + { MP_ROM_QSTR(MP_QSTR_MODE_11N), MP_ROM_INT(WIFI_PROTOCOL_11N) }, + { MP_ROM_QSTR(MP_QSTR_MODE_LR), MP_ROM_INT(WIFI_PROTOCOL_LR) }, + + { MP_ROM_QSTR(MP_QSTR_AUTH_OPEN), MP_ROM_INT(WIFI_AUTH_OPEN) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WEP), MP_ROM_INT(WIFI_AUTH_WEP) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA_PSK), MP_ROM_INT(WIFI_AUTH_WPA_PSK) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_PSK), MP_ROM_INT(WIFI_AUTH_WPA2_PSK) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA_WPA2_PSK), MP_ROM_INT(WIFI_AUTH_WPA_WPA2_PSK) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_ENTERPRISE), MP_ROM_INT(WIFI_AUTH_WPA2_ENTERPRISE) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA3_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_PSK) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_WPA3_PSK), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_PSK) }, + #if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(4, 3, 0) + { MP_ROM_QSTR(MP_QSTR_AUTH_WAPI_PSK), MP_ROM_INT(WIFI_AUTH_WAPI_PSK) }, + #endif + { MP_ROM_QSTR(MP_QSTR_AUTH_MAX), MP_ROM_INT(WIFI_AUTH_MAX) }, + #endif + + #if MICROPY_PY_NETWORK_LAN + { MP_ROM_QSTR(MP_QSTR_PHY_LAN8710), MP_ROM_INT(PHY_LAN8710) }, + { MP_ROM_QSTR(MP_QSTR_PHY_LAN8720), MP_ROM_INT(PHY_LAN8720) }, + { MP_ROM_QSTR(MP_QSTR_PHY_IP101), MP_ROM_INT(PHY_IP101) }, + { MP_ROM_QSTR(MP_QSTR_PHY_RTL8201), MP_ROM_INT(PHY_RTL8201) }, + { MP_ROM_QSTR(MP_QSTR_PHY_DP83848), MP_ROM_INT(PHY_DP83848) }, + #if ESP_IDF_VERSION_MINOR >= 3 + // PHY_KSZ8041 is new in ESP-IDF v4.3 + { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8041), MP_ROM_INT(PHY_KSZ8041) }, + #endif + + #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + { MP_ROM_QSTR(MP_QSTR_PHY_KSZ8851SNL), MP_ROM_INT(PHY_KSZ8851SNL) }, + #endif + #if CONFIG_ETH_SPI_ETHERNET_DM9051 + { MP_ROM_QSTR(MP_QSTR_PHY_DM9051), MP_ROM_INT(PHY_DM9051) }, + #endif + #if CONFIG_ETH_SPI_ETHERNET_W5500 + { MP_ROM_QSTR(MP_QSTR_PHY_W5500), MP_ROM_INT(PHY_W5500) }, + #endif + + { MP_ROM_QSTR(MP_QSTR_ETH_INITIALIZED), MP_ROM_INT(ETH_INITIALIZED)}, + { MP_ROM_QSTR(MP_QSTR_ETH_STARTED), MP_ROM_INT(ETH_STARTED)}, + { MP_ROM_QSTR(MP_QSTR_ETH_STOPPED), MP_ROM_INT(ETH_STOPPED)}, + { MP_ROM_QSTR(MP_QSTR_ETH_CONNECTED), MP_ROM_INT(ETH_CONNECTED)}, + { MP_ROM_QSTR(MP_QSTR_ETH_DISCONNECTED), MP_ROM_INT(ETH_DISCONNECTED)}, + { MP_ROM_QSTR(MP_QSTR_ETH_GOT_IP), MP_ROM_INT(ETH_GOT_IP)}, + #endif + + { MP_ROM_QSTR(MP_QSTR_STAT_IDLE), MP_ROM_INT(STAT_IDLE)}, + { MP_ROM_QSTR(MP_QSTR_STAT_CONNECTING), MP_ROM_INT(STAT_CONNECTING)}, + { MP_ROM_QSTR(MP_QSTR_STAT_GOT_IP), MP_ROM_INT(STAT_GOT_IP)}, + // Errors from the ESP-IDF + { MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND), MP_ROM_INT(WIFI_REASON_NO_AP_FOUND)}, + { MP_ROM_QSTR(MP_QSTR_STAT_WRONG_PASSWORD), MP_ROM_INT(WIFI_REASON_AUTH_FAIL)}, + { MP_ROM_QSTR(MP_QSTR_STAT_BEACON_TIMEOUT), MP_ROM_INT(WIFI_REASON_BEACON_TIMEOUT)}, + { MP_ROM_QSTR(MP_QSTR_STAT_ASSOC_FAIL), MP_ROM_INT(WIFI_REASON_ASSOC_FAIL)}, + { MP_ROM_QSTR(MP_QSTR_STAT_HANDSHAKE_TIMEOUT), MP_ROM_INT(WIFI_REASON_HANDSHAKE_TIMEOUT)}, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_network_globals, mp_module_network_globals_table); + +const mp_obj_module_t mp_module_network = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_network_globals, +}; + +// Note: This port doesn't define MICROPY_PY_NETWORK so this will not conflict +// with the common implementation provided by extmod/modnetwork.c. +MP_REGISTER_MODULE(MP_QSTR_network, mp_module_network); diff --git a/microros_ws/extra_packages/micropython/src/modnetwork.h b/microros_ws/extra_packages/micropython/src/modnetwork.h new file mode 100644 index 0000000..49bbd9c --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modnetwork.h @@ -0,0 +1,65 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 "Eric Poulsen" + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP32_MODNETWORK_H +#define MICROPY_INCLUDED_ESP32_MODNETWORK_H + +#include "esp_event.h" + +enum { PHY_LAN8710, PHY_LAN8720, PHY_IP101, PHY_RTL8201, PHY_DP83848, PHY_KSZ8041, PHY_KSZ8851SNL = 100, PHY_DM9051, PHY_W5500 }; +#define IS_SPI_PHY(NUM) (NUM >= 100) +enum { ETH_INITIALIZED, ETH_STARTED, ETH_STOPPED, ETH_CONNECTED, ETH_DISCONNECTED, ETH_GOT_IP }; + +// Cases similar to ESP8266 user_interface.h +// Error cases are referenced from wifi_err_reason_t in ESP-IDF +enum { + STAT_IDLE = 1000, + STAT_CONNECTING = 1001, + STAT_GOT_IP = 1010, +}; + +typedef struct _wlan_if_obj_t { + mp_obj_base_t base; + int if_id; +} wlan_if_obj_t; + +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(get_wlan_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(get_lan_obj); +MP_DECLARE_CONST_FUN_OBJ_1(ppp_make_new_obj); +MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_ifconfig_obj); +MP_DECLARE_CONST_FUN_OBJ_KW(esp_config_obj); + +NORETURN void esp_exceptions_helper(esp_err_t e); + +static inline void esp_exceptions(esp_err_t e) { + if (e != ESP_OK) { + esp_exceptions_helper(e); + } +} + +void usocket_events_deinit(void); +void network_wlan_event_handler(system_event_t *event); + +#endif diff --git a/microros_ws/extra_packages/micropython/src/modsocket.c b/microros_ws/extra_packages/micropython/src/modsocket.c new file mode 100644 index 0000000..9812eb3 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modsocket.c @@ -0,0 +1,875 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * and Mnemote Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 2017 Nick Moore @mnemote + * + * Based on extmod/modlwip.c + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2015 Galen Hazelwood + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "py/runtime0.h" +#include "py/nlr.h" +#include "py/objlist.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/stream.h" +#include "py/mperrno.h" +#include "shared/netutils/netutils.h" +#include "mdns.h" +#include "modnetwork.h" + +#include "lwip/sockets.h" +#include "lwip/netdb.h" +#include "lwip/ip4.h" +#include "lwip/igmp.h" +#include "esp_log.h" + +#define SOCKET_POLL_US (100000) +#define MDNS_QUERY_TIMEOUT_MS (5000) +#define MDNS_LOCAL_SUFFIX ".local" + +enum { + SOCKET_STATE_NEW, + SOCKET_STATE_CONNECTED, + SOCKET_STATE_PEER_CLOSED, +}; + +typedef struct _socket_obj_t { + mp_obj_base_t base; + int fd; + uint8_t domain; + uint8_t type; + uint8_t proto; + uint8_t state; + unsigned int retries; + #if MICROPY_PY_USOCKET_EVENTS + mp_obj_t events_callback; + struct _socket_obj_t *events_next; + #endif +} socket_obj_t; + +void _socket_settimeout(socket_obj_t *sock, uint64_t timeout_ms); + +#if MICROPY_PY_USOCKET_EVENTS +// Support for callbacks on asynchronous socket events (when socket becomes readable) + +// This divisor is used to reduce the load on the system, so it doesn't poll sockets too often +#define USOCKET_EVENTS_DIVISOR (8) + +STATIC uint8_t usocket_events_divisor; +STATIC socket_obj_t *usocket_events_head; + +void usocket_events_deinit(void) { + usocket_events_head = NULL; +} + +// Assumes the socket is not already in the linked list, and adds it +STATIC void usocket_events_add(socket_obj_t *sock) { + sock->events_next = usocket_events_head; + usocket_events_head = sock; +} + +// Assumes the socket is already in the linked list, and removes it +STATIC void usocket_events_remove(socket_obj_t *sock) { + for (socket_obj_t **s = &usocket_events_head;; s = &(*s)->events_next) { + if (*s == sock) { + *s = (*s)->events_next; + return; + } + } +} + +// Polls all registered sockets for readability and calls their callback if they are readable +void usocket_events_handler(void) { + if (usocket_events_head == NULL) { + return; + } + if (--usocket_events_divisor) { + return; + } + usocket_events_divisor = USOCKET_EVENTS_DIVISOR; + + fd_set rfds; + FD_ZERO(&rfds); + int max_fd = 0; + + for (socket_obj_t *s = usocket_events_head; s != NULL; s = s->events_next) { + FD_SET(s->fd, &rfds); + max_fd = MAX(max_fd, s->fd); + } + + // Poll the sockets + struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 }; + int r = select(max_fd + 1, &rfds, NULL, NULL, &timeout); + if (r <= 0) { + return; + } + + // Call the callbacks + for (socket_obj_t *s = usocket_events_head; s != NULL; s = s->events_next) { + if (FD_ISSET(s->fd, &rfds)) { + mp_call_function_1_protected(s->events_callback, s); + } + } +} + +#endif // MICROPY_PY_USOCKET_EVENTS + +static inline void check_for_exceptions(void) { + mp_handle_pending(true); +} + +// This function mimics lwip_getaddrinfo, with added support for mDNS queries +static int _socket_getaddrinfo3(const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) { + + #if MICROPY_HW_ENABLE_MDNS_QUERIES + int nodename_len = strlen(nodename); + const int local_len = sizeof(MDNS_LOCAL_SUFFIX) - 1; + if (nodename_len > local_len + && strcasecmp(nodename + nodename_len - local_len, MDNS_LOCAL_SUFFIX) == 0) { + // mDNS query + char nodename_no_local[nodename_len - local_len + 1]; + memcpy(nodename_no_local, nodename, nodename_len - local_len); + nodename_no_local[nodename_len - local_len] = '\0'; + + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 1, 0) + struct ip4_addr addr = {0}; + #else + esp_ip4_addr_t addr = {0}; + #endif + + esp_err_t err = mdns_query_a(nodename_no_local, MDNS_QUERY_TIMEOUT_MS, &addr); + if (err != ESP_OK) { + if (err == ESP_ERR_NOT_FOUND) { + *res = NULL; + return 0; + } + *res = NULL; + return err; + } + + struct addrinfo *ai = memp_malloc(MEMP_NETDB); + if (ai == NULL) { + *res = NULL; + return EAI_MEMORY; + } + memset(ai, 0, sizeof(struct addrinfo) + sizeof(struct sockaddr_storage)); + + struct sockaddr_in *sa = (struct sockaddr_in *)((uint8_t *)ai + sizeof(struct addrinfo)); + inet_addr_from_ip4addr(&sa->sin_addr, &addr); + sa->sin_family = AF_INET; + sa->sin_len = sizeof(struct sockaddr_in); + sa->sin_port = lwip_htons((u16_t)atoi(servname)); + ai->ai_family = AF_INET; + ai->ai_canonname = ((char *)sa + sizeof(struct sockaddr_storage)); + memcpy(ai->ai_canonname, nodename, nodename_len + 1); + ai->ai_addrlen = sizeof(struct sockaddr_storage); + ai->ai_addr = (struct sockaddr *)sa; + + *res = ai; + return 0; + } + #endif + + // Normal query + return lwip_getaddrinfo(nodename, servname, hints, res); +} + +static int _socket_getaddrinfo2(const mp_obj_t host, const mp_obj_t portx, struct addrinfo **resp) { + const struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }; + + mp_obj_t port = portx; + if (mp_obj_is_integer(port)) { + // This is perverse, because lwip_getaddrinfo promptly converts it back to an int, but + // that's the API we have to work with ... + port = mp_obj_str_binary_op(MP_BINARY_OP_MODULO, mp_obj_new_str_via_qstr("%s", 2), port); + } + + const char *host_str = mp_obj_str_get_str(host); + const char *port_str = mp_obj_str_get_str(port); + + if (host_str[0] == '\0') { + // a host of "" is equivalent to the default/all-local IP address + host_str = "0.0.0.0"; + } + + MP_THREAD_GIL_EXIT(); + int res = _socket_getaddrinfo3(host_str, port_str, &hints, resp); + MP_THREAD_GIL_ENTER(); + + // Per docs: instead of raising gaierror getaddrinfo raises negative error number + if (res != 0) { + mp_raise_OSError(res > 0 ? -res : res); + } + // Somehow LwIP returns a resolution of 0.0.0.0 for failed lookups, traced it as far back + // as netconn_gethostbyname_addrtype returning OK instead of error. + if (*resp == NULL || + (strcmp(resp[0]->ai_canonname, "0.0.0.0") == 0 && strcmp(host_str, "0.0.0.0") != 0)) { + mp_raise_OSError(-2); // name or service not known + } + + return res; +} + +STATIC void _socket_getaddrinfo(const mp_obj_t addrtuple, struct addrinfo **resp) { + mp_obj_t *elem; + mp_obj_get_array_fixed_n(addrtuple, 2, &elem); + _socket_getaddrinfo2(elem[0], elem[1], resp); +} + +STATIC mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 3, false); + + socket_obj_t *sock = m_new_obj_with_finaliser(socket_obj_t); + sock->base.type = type_in; + sock->domain = AF_INET; + sock->type = SOCK_STREAM; + sock->proto = 0; + if (n_args > 0) { + sock->domain = mp_obj_get_int(args[0]); + if (n_args > 1) { + sock->type = mp_obj_get_int(args[1]); + if (n_args > 2) { + sock->proto = mp_obj_get_int(args[2]); + } + } + } + + sock->state = sock->type == SOCK_STREAM ? SOCKET_STATE_NEW : SOCKET_STATE_CONNECTED; + + sock->fd = lwip_socket(sock->domain, sock->type, sock->proto); + if (sock->fd < 0) { + mp_raise_OSError(errno); + } + _socket_settimeout(sock, UINT64_MAX); + + return MP_OBJ_FROM_PTR(sock); +} + +STATIC mp_obj_t socket_bind(const mp_obj_t arg0, const mp_obj_t arg1) { + socket_obj_t *self = MP_OBJ_TO_PTR(arg0); + struct addrinfo *res; + _socket_getaddrinfo(arg1, &res); + self->state = SOCKET_STATE_CONNECTED; + int r = lwip_bind(self->fd, res->ai_addr, res->ai_addrlen); + lwip_freeaddrinfo(res); + if (r < 0) { + mp_raise_OSError(errno); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_bind_obj, socket_bind); + +// method socket.listen([backlog]) +STATIC mp_obj_t socket_listen(size_t n_args, const mp_obj_t *args) { + socket_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + int backlog = MICROPY_PY_USOCKET_LISTEN_BACKLOG_DEFAULT; + if (n_args > 1) { + backlog = mp_obj_get_int(args[1]); + backlog = (backlog < 0) ? 0 : backlog; + } + + self->state = SOCKET_STATE_CONNECTED; + int r = lwip_listen(self->fd, backlog); + if (r < 0) { + mp_raise_OSError(errno); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_listen_obj, 1, 2, socket_listen); + +STATIC mp_obj_t socket_accept(const mp_obj_t arg0) { + socket_obj_t *self = MP_OBJ_TO_PTR(arg0); + + struct sockaddr addr; + socklen_t addr_len = sizeof(addr); + + int new_fd = -1; + for (int i = 0; i <= self->retries; i++) { + MP_THREAD_GIL_EXIT(); + new_fd = lwip_accept(self->fd, &addr, &addr_len); + MP_THREAD_GIL_ENTER(); + if (new_fd >= 0) { + break; + } + if (errno != EAGAIN) { + mp_raise_OSError(errno); + } + check_for_exceptions(); + } + if (new_fd < 0) { + if (self->retries == 0) { + mp_raise_OSError(MP_EAGAIN); + } else { + mp_raise_OSError(MP_ETIMEDOUT); + } + } + + // create new socket object + socket_obj_t *sock = m_new_obj_with_finaliser(socket_obj_t); + sock->base.type = self->base.type; + sock->fd = new_fd; + sock->domain = self->domain; + sock->type = self->type; + sock->proto = self->proto; + sock->state = SOCKET_STATE_CONNECTED; + _socket_settimeout(sock, UINT64_MAX); + + // make the return value + uint8_t *ip = (uint8_t *)&((struct sockaddr_in *)&addr)->sin_addr; + mp_uint_t port = lwip_ntohs(((struct sockaddr_in *)&addr)->sin_port); + mp_obj_tuple_t *client = mp_obj_new_tuple(2, NULL); + client->items[0] = sock; + client->items[1] = netutils_format_inet_addr(ip, port, NETUTILS_BIG); + + return client; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(socket_accept_obj, socket_accept); + +STATIC mp_obj_t socket_connect(const mp_obj_t arg0, const mp_obj_t arg1) { + socket_obj_t *self = MP_OBJ_TO_PTR(arg0); + struct addrinfo *res; + _socket_getaddrinfo(arg1, &res); + MP_THREAD_GIL_EXIT(); + self->state = SOCKET_STATE_CONNECTED; + int r = lwip_connect(self->fd, res->ai_addr, res->ai_addrlen); + MP_THREAD_GIL_ENTER(); + lwip_freeaddrinfo(res); + if (r != 0) { + mp_raise_OSError(errno); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_connect_obj, socket_connect); + +STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { + (void)n_args; // always 4 + socket_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + int opt = mp_obj_get_int(args[2]); + + switch (opt) { + // level: SOL_SOCKET + case SO_REUSEADDR: { + int val = mp_obj_get_int(args[3]); + int ret = lwip_setsockopt(self->fd, SOL_SOCKET, opt, &val, sizeof(int)); + if (ret != 0) { + mp_raise_OSError(errno); + } + break; + } + + #if MICROPY_PY_USOCKET_EVENTS + // level: SOL_SOCKET + // special "register callback" option + case 20: { + if (args[3] == mp_const_none) { + if (self->events_callback != MP_OBJ_NULL) { + usocket_events_remove(self); + self->events_callback = MP_OBJ_NULL; + } + } else { + if (self->events_callback == MP_OBJ_NULL) { + usocket_events_add(self); + } + self->events_callback = args[3]; + } + break; + } + #endif + + // level: IPPROTO_IP + case IP_ADD_MEMBERSHIP: { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ); + if (bufinfo.len != sizeof(ip4_addr_t) * 2) { + mp_raise_ValueError(NULL); + } + + // POSIX setsockopt has order: group addr, if addr, lwIP has it vice-versa + err_t err = igmp_joingroup((const ip4_addr_t *)bufinfo.buf + 1, bufinfo.buf); + if (err != ERR_OK) { + mp_raise_OSError(-err); + } + break; + } + + default: + mp_printf(&mp_plat_print, "Warning: lwip.setsockopt() option not implemented\n"); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_setsockopt_obj, 4, 4, socket_setsockopt); + +void _socket_settimeout(socket_obj_t *sock, uint64_t timeout_ms) { + // Rather than waiting for the entire timeout specified, we wait sock->retries times + // for SOCKET_POLL_US each, checking for a MicroPython interrupt between timeouts. + // with SOCKET_POLL_MS == 100ms, sock->retries allows for timeouts up to 13 years. + // if timeout_ms == UINT64_MAX, wait forever. + sock->retries = (timeout_ms == UINT64_MAX) ? UINT_MAX : timeout_ms * 1000 / SOCKET_POLL_US; + + struct timeval timeout = { + .tv_sec = 0, + .tv_usec = timeout_ms ? SOCKET_POLL_US : 0 + }; + lwip_setsockopt(sock->fd, SOL_SOCKET, SO_SNDTIMEO, (const void *)&timeout, sizeof(timeout)); + lwip_setsockopt(sock->fd, SOL_SOCKET, SO_RCVTIMEO, (const void *)&timeout, sizeof(timeout)); + lwip_fcntl(sock->fd, F_SETFL, timeout_ms ? 0 : O_NONBLOCK); +} + +STATIC mp_obj_t socket_settimeout(const mp_obj_t arg0, const mp_obj_t arg1) { + socket_obj_t *self = MP_OBJ_TO_PTR(arg0); + if (arg1 == mp_const_none) { + _socket_settimeout(self, UINT64_MAX); + } else { + #if MICROPY_PY_BUILTINS_FLOAT + _socket_settimeout(self, (uint64_t)(mp_obj_get_float(arg1) * MICROPY_FLOAT_CONST(1000.0))); + #else + _socket_settimeout(self, mp_obj_get_int(arg1) * 1000); + #endif + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_settimeout_obj, socket_settimeout); + +STATIC mp_obj_t socket_setblocking(const mp_obj_t arg0, const mp_obj_t arg1) { + socket_obj_t *self = MP_OBJ_TO_PTR(arg0); + if (mp_obj_is_true(arg1)) { + _socket_settimeout(self, UINT64_MAX); + } else { + _socket_settimeout(self, 0); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_setblocking_obj, socket_setblocking); + +// XXX this can end up waiting a very long time if the content is dribbled in one character +// at a time, as the timeout resets each time a recvfrom succeeds ... this is probably not +// good behaviour. +STATIC mp_uint_t _socket_read_data(mp_obj_t self_in, void *buf, size_t size, + struct sockaddr *from, socklen_t *from_len, int *errcode) { + socket_obj_t *sock = MP_OBJ_TO_PTR(self_in); + + // A new socket cannot be read from. + if (sock->state == SOCKET_STATE_NEW) { + *errcode = MP_ENOTCONN; + return MP_STREAM_ERROR; + } + + // If the peer closed the connection then the lwIP socket API will only return "0" once + // from lwip_recvfrom and then block on subsequent calls. To emulate POSIX behaviour, + // which continues to return "0" for each call on a closed socket, we set a flag when + // the peer closed the socket. + if (sock->state == SOCKET_STATE_PEER_CLOSED) { + return 0; + } + + // XXX Would be nicer to use RTC to handle timeouts + for (int i = 0; i <= sock->retries; ++i) { + // Poll the socket to see if it has waiting data and only release the GIL if it doesn't. + // This ensures higher performance in the case of many small reads, eg for readline. + bool release_gil; + { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(sock->fd, &rfds); + struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 }; + int r = select(sock->fd + 1, &rfds, NULL, NULL, &timeout); + release_gil = r != 1; + } + if (release_gil) { + MP_THREAD_GIL_EXIT(); + } + int r = lwip_recvfrom(sock->fd, buf, size, 0, from, from_len); + if (release_gil) { + MP_THREAD_GIL_ENTER(); + } + if (r == 0) { + sock->state = SOCKET_STATE_PEER_CLOSED; + } + if (r >= 0) { + return r; + } + if (errno != EWOULDBLOCK) { + *errcode = errno; + return MP_STREAM_ERROR; + } + check_for_exceptions(); + } + + *errcode = sock->retries == 0 ? MP_EWOULDBLOCK : MP_ETIMEDOUT; + return MP_STREAM_ERROR; +} + +mp_obj_t _socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in, + struct sockaddr *from, socklen_t *from_len) { + size_t len = mp_obj_get_int(len_in); + vstr_t vstr; + vstr_init_len(&vstr, len); + + int errcode; + mp_uint_t ret = _socket_read_data(self_in, vstr.buf, len, from, from_len, &errcode); + if (ret == MP_STREAM_ERROR) { + mp_raise_OSError(errcode); + } + + vstr.len = ret; + return mp_obj_new_bytes_from_vstr(&vstr); +} + +STATIC mp_obj_t socket_recv(mp_obj_t self_in, mp_obj_t len_in) { + return _socket_recvfrom(self_in, len_in, NULL, NULL); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_recv_obj, socket_recv); + +STATIC mp_obj_t socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { + struct sockaddr from; + socklen_t fromlen = sizeof(from); + + mp_obj_t tuple[2]; + tuple[0] = _socket_recvfrom(self_in, len_in, &from, &fromlen); + + uint8_t *ip = (uint8_t *)&((struct sockaddr_in *)&from)->sin_addr; + mp_uint_t port = lwip_ntohs(((struct sockaddr_in *)&from)->sin_port); + tuple[1] = netutils_format_inet_addr(ip, port, NETUTILS_BIG); + + return mp_obj_new_tuple(2, tuple); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_recvfrom_obj, socket_recvfrom); + +int _socket_send(socket_obj_t *sock, const char *data, size_t datalen) { + int sentlen = 0; + for (int i = 0; i <= sock->retries && sentlen < datalen; i++) { + MP_THREAD_GIL_EXIT(); + int r = lwip_write(sock->fd, data + sentlen, datalen - sentlen); + MP_THREAD_GIL_ENTER(); + // lwip returns EINPROGRESS when trying to send right after a non-blocking connect + if (r < 0 && errno != EWOULDBLOCK && errno != EINPROGRESS) { + mp_raise_OSError(errno); + } + if (r > 0) { + sentlen += r; + } + check_for_exceptions(); + } + if (sentlen == 0) { + mp_raise_OSError(sock->retries == 0 ? MP_EWOULDBLOCK : MP_ETIMEDOUT); + } + return sentlen; +} + +STATIC mp_obj_t socket_send(const mp_obj_t arg0, const mp_obj_t arg1) { + socket_obj_t *sock = MP_OBJ_TO_PTR(arg0); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(arg1, &bufinfo, MP_BUFFER_READ); + int r = _socket_send(sock, bufinfo.buf, bufinfo.len); + return mp_obj_new_int(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_send_obj, socket_send); + +STATIC mp_obj_t socket_sendall(const mp_obj_t arg0, const mp_obj_t arg1) { + // XXX behaviour when nonblocking (see extmod/modlwip.c) + // XXX also timeout behaviour. + socket_obj_t *sock = MP_OBJ_TO_PTR(arg0); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(arg1, &bufinfo, MP_BUFFER_READ); + int r = _socket_send(sock, bufinfo.buf, bufinfo.len); + if (r < bufinfo.len) { + mp_raise_OSError(MP_ETIMEDOUT); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_sendall_obj, socket_sendall); + +STATIC mp_obj_t socket_sendto(mp_obj_t self_in, mp_obj_t data_in, mp_obj_t addr_in) { + socket_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // get the buffer to send + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_READ); + + // create the destination address + struct sockaddr_in to; + to.sin_len = sizeof(to); + to.sin_family = AF_INET; + to.sin_port = lwip_htons(netutils_parse_inet_addr(addr_in, (uint8_t *)&to.sin_addr, NETUTILS_BIG)); + + // send the data + for (int i = 0; i <= self->retries; i++) { + MP_THREAD_GIL_EXIT(); + int ret = lwip_sendto(self->fd, bufinfo.buf, bufinfo.len, 0, (struct sockaddr *)&to, sizeof(to)); + MP_THREAD_GIL_ENTER(); + if (ret > 0) { + return mp_obj_new_int_from_uint(ret); + } + if (ret == -1 && errno != EWOULDBLOCK) { + mp_raise_OSError(errno); + } + check_for_exceptions(); + } + mp_raise_OSError(MP_ETIMEDOUT); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(socket_sendto_obj, socket_sendto); + +STATIC mp_obj_t socket_fileno(const mp_obj_t arg0) { + socket_obj_t *self = MP_OBJ_TO_PTR(arg0); + return mp_obj_new_int(self->fd); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(socket_fileno_obj, socket_fileno); + +STATIC mp_obj_t socket_makefile(size_t n_args, const mp_obj_t *args) { + (void)n_args; + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_makefile_obj, 1, 3, socket_makefile); + +STATIC mp_uint_t socket_stream_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { + return _socket_read_data(self_in, buf, size, NULL, NULL, errcode); +} + +STATIC mp_uint_t socket_stream_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { + socket_obj_t *sock = self_in; + for (int i = 0; i <= sock->retries; i++) { + MP_THREAD_GIL_EXIT(); + int r = lwip_write(sock->fd, buf, size); + MP_THREAD_GIL_ENTER(); + if (r > 0) { + return r; + } + // lwip returns MP_EINPROGRESS when trying to write right after a non-blocking connect + if (r < 0 && errno != EWOULDBLOCK && errno != EINPROGRESS) { + *errcode = errno; + return MP_STREAM_ERROR; + } + check_for_exceptions(); + } + *errcode = sock->retries == 0 ? MP_EWOULDBLOCK : MP_ETIMEDOUT; + return MP_STREAM_ERROR; +} + +STATIC mp_uint_t socket_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + socket_obj_t *socket = self_in; + if (request == MP_STREAM_POLL) { + if (socket->fd == -1) { + return MP_STREAM_POLL_NVAL; + } + + fd_set rfds; + FD_ZERO(&rfds); + fd_set wfds; + FD_ZERO(&wfds); + fd_set efds; + FD_ZERO(&efds); + struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 }; + if (arg & MP_STREAM_POLL_RD) { + FD_SET(socket->fd, &rfds); + } + if (arg & MP_STREAM_POLL_WR) { + FD_SET(socket->fd, &wfds); + } + if (arg & MP_STREAM_POLL_HUP) { + FD_SET(socket->fd, &efds); + } + + int r = select((socket->fd) + 1, &rfds, &wfds, &efds, &timeout); + if (r < 0) { + *errcode = MP_EIO; + return MP_STREAM_ERROR; + } + + mp_uint_t ret = 0; + if (FD_ISSET(socket->fd, &rfds)) { + ret |= MP_STREAM_POLL_RD; + } + if (FD_ISSET(socket->fd, &wfds)) { + ret |= MP_STREAM_POLL_WR; + } + if (FD_ISSET(socket->fd, &efds)) { + ret |= MP_STREAM_POLL_HUP; + } + + // New (unconnected) sockets are writable and have HUP set. + if (socket->state == SOCKET_STATE_NEW) { + ret |= (arg & MP_STREAM_POLL_WR) | MP_STREAM_POLL_HUP; + } + + return ret; + } else if (request == MP_STREAM_CLOSE) { + if (socket->fd >= 0) { + #if MICROPY_PY_USOCKET_EVENTS + if (socket->events_callback != MP_OBJ_NULL) { + usocket_events_remove(socket); + socket->events_callback = MP_OBJ_NULL; + } + #endif + int ret = lwip_close(socket->fd); + if (ret != 0) { + *errcode = errno; + return MP_STREAM_ERROR; + } + socket->fd = -1; + } + return 0; + } + + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; +} + +STATIC const mp_rom_map_elem_t socket_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, + { MP_ROM_QSTR(MP_QSTR_bind), MP_ROM_PTR(&socket_bind_obj) }, + { MP_ROM_QSTR(MP_QSTR_listen), MP_ROM_PTR(&socket_listen_obj) }, + { MP_ROM_QSTR(MP_QSTR_accept), MP_ROM_PTR(&socket_accept_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&socket_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&socket_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_sendall), MP_ROM_PTR(&socket_sendall_obj) }, + { MP_ROM_QSTR(MP_QSTR_sendto), MP_ROM_PTR(&socket_sendto_obj) }, + { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&socket_recv_obj) }, + { MP_ROM_QSTR(MP_QSTR_recvfrom), MP_ROM_PTR(&socket_recvfrom_obj) }, + { MP_ROM_QSTR(MP_QSTR_setsockopt), MP_ROM_PTR(&socket_setsockopt_obj) }, + { MP_ROM_QSTR(MP_QSTR_settimeout), MP_ROM_PTR(&socket_settimeout_obj) }, + { MP_ROM_QSTR(MP_QSTR_setblocking), MP_ROM_PTR(&socket_setblocking_obj) }, + { MP_ROM_QSTR(MP_QSTR_makefile), MP_ROM_PTR(&socket_makefile_obj) }, + { MP_ROM_QSTR(MP_QSTR_fileno), MP_ROM_PTR(&socket_fileno_obj) }, + + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(socket_locals_dict, socket_locals_dict_table); + +STATIC const mp_stream_p_t socket_stream_p = { + .read = socket_stream_read, + .write = socket_stream_write, + .ioctl = socket_stream_ioctl +}; + +STATIC MP_DEFINE_CONST_OBJ_TYPE( + socket_type, + MP_QSTR_socket, + MP_TYPE_FLAG_NONE, + make_new, socket_make_new, + protocol, &socket_stream_p, + locals_dict, &socket_locals_dict + ); + +STATIC mp_obj_t esp_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { + // TODO support additional args beyond the first two + + struct addrinfo *res = NULL; + _socket_getaddrinfo2(args[0], args[1], &res); + mp_obj_t ret_list = mp_obj_new_list(0, NULL); + + for (struct addrinfo *resi = res; resi; resi = resi->ai_next) { + mp_obj_t addrinfo_objs[5] = { + mp_obj_new_int(resi->ai_family), + mp_obj_new_int(resi->ai_socktype), + mp_obj_new_int(resi->ai_protocol), + mp_obj_new_str(resi->ai_canonname, strlen(resi->ai_canonname)), + mp_const_none + }; + + if (resi->ai_family == AF_INET) { + struct sockaddr_in *addr = (struct sockaddr_in *)resi->ai_addr; + // This looks odd, but it's really just a u32_t + ip4_addr_t ip4_addr = { .addr = addr->sin_addr.s_addr }; + char buf[16]; + ip4addr_ntoa_r(&ip4_addr, buf, sizeof(buf)); + mp_obj_t inaddr_objs[2] = { + mp_obj_new_str(buf, strlen(buf)), + mp_obj_new_int(ntohs(addr->sin_port)) + }; + addrinfo_objs[4] = mp_obj_new_tuple(2, inaddr_objs); + } + mp_obj_list_append(ret_list, mp_obj_new_tuple(5, addrinfo_objs)); + } + + if (res) { + lwip_freeaddrinfo(res); + } + return ret_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_socket_getaddrinfo_obj, 2, 6, esp_socket_getaddrinfo); + +STATIC mp_obj_t esp_socket_initialize() { + static int initialized = 0; + if (!initialized) { + ESP_LOGI("modsocket", "Initializing"); + tcpip_adapter_init(); + initialized = 1; + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_socket_initialize_obj, esp_socket_initialize); + +STATIC const mp_rom_map_elem_t mp_module_socket_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usocket) }, + { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&esp_socket_initialize_obj) }, + { MP_ROM_QSTR(MP_QSTR_socket), MP_ROM_PTR(&socket_type) }, + { MP_ROM_QSTR(MP_QSTR_getaddrinfo), MP_ROM_PTR(&esp_socket_getaddrinfo_obj) }, + + { MP_ROM_QSTR(MP_QSTR_AF_INET), MP_ROM_INT(AF_INET) }, + { MP_ROM_QSTR(MP_QSTR_AF_INET6), MP_ROM_INT(AF_INET6) }, + { MP_ROM_QSTR(MP_QSTR_SOCK_STREAM), MP_ROM_INT(SOCK_STREAM) }, + { MP_ROM_QSTR(MP_QSTR_SOCK_DGRAM), MP_ROM_INT(SOCK_DGRAM) }, + { MP_ROM_QSTR(MP_QSTR_SOCK_RAW), MP_ROM_INT(SOCK_RAW) }, + { MP_ROM_QSTR(MP_QSTR_IPPROTO_TCP), MP_ROM_INT(IPPROTO_TCP) }, + { MP_ROM_QSTR(MP_QSTR_IPPROTO_UDP), MP_ROM_INT(IPPROTO_UDP) }, + { MP_ROM_QSTR(MP_QSTR_IPPROTO_IP), MP_ROM_INT(IPPROTO_IP) }, + { MP_ROM_QSTR(MP_QSTR_SOL_SOCKET), MP_ROM_INT(SOL_SOCKET) }, + { MP_ROM_QSTR(MP_QSTR_SO_REUSEADDR), MP_ROM_INT(SO_REUSEADDR) }, + { MP_ROM_QSTR(MP_QSTR_IP_ADD_MEMBERSHIP), MP_ROM_INT(IP_ADD_MEMBERSHIP) }, +}; + +STATIC MP_DEFINE_CONST_DICT(mp_module_socket_globals, mp_module_socket_globals_table); + +const mp_obj_module_t mp_module_usocket = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_socket_globals, +}; + +// Note: This port doesn't define MICROPY_PY_USOCKET or MICROPY_PY_LWIP so +// this will not conflict with the common implementation provided by +// extmod/mod{lwip,usocket}.c. +MP_REGISTER_MODULE(MP_QSTR_usocket, mp_module_usocket); diff --git a/microros_ws/extra_packages/micropython/src/modules/_boot.py b/microros_ws/extra_packages/micropython/src/modules/_boot.py new file mode 100644 index 0000000..a7d9813 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modules/_boot.py @@ -0,0 +1,13 @@ +import gc +import uos +from flashbdev import bdev + +try: + if bdev: + uos.mount(bdev, "/") +except OSError: + import inisetup + + vfs = inisetup.setup() + +gc.collect() diff --git a/microros_ws/extra_packages/micropython/src/modules/apa106.py b/microros_ws/extra_packages/micropython/src/modules/apa106.py new file mode 100644 index 0000000..ef971d7 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modules/apa106.py @@ -0,0 +1,8 @@ +# APA106driver for MicroPython on ESP32 +# MIT license; Copyright (c) 2016 Damien P. George + +from neopixel import NeoPixel + + +class APA106(NeoPixel): + ORDER = (0, 1, 2, 3) diff --git a/microros_ws/extra_packages/micropython/src/modules/flashbdev.py b/microros_ws/extra_packages/micropython/src/modules/flashbdev.py new file mode 100644 index 0000000..1ee6ff7 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modules/flashbdev.py @@ -0,0 +1,7 @@ +from esp32 import Partition + +# MicroPython's partition table uses "vfs", TinyUF2 uses "ffat". +bdev = Partition.find(Partition.TYPE_DATA, label="vfs") +if not bdev: + bdev = Partition.find(Partition.TYPE_DATA, label="ffat", block_size=512) +bdev = bdev[0] if bdev else None diff --git a/microros_ws/extra_packages/micropython/src/modules/inisetup.py b/microros_ws/extra_packages/micropython/src/modules/inisetup.py new file mode 100644 index 0000000..426a47a --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modules/inisetup.py @@ -0,0 +1,49 @@ +import uos +from flashbdev import bdev + + +def check_bootsec(): + buf = bytearray(bdev.ioctl(5, 0)) # 5 is SEC_SIZE + bdev.readblocks(0, buf) + empty = True + for b in buf: + if b != 0xFF: + empty = False + break + if empty: + return True + fs_corrupted() + + +def fs_corrupted(): + import time + + while 1: + print( + """\ +The filesystem appears to be corrupted. If you had important data there, you +may want to make a flash snapshot to try to recover it. Otherwise, perform +factory reprogramming of MicroPython firmware (completely erase flash, followed +by firmware programming). +""" + ) + time.sleep(3) + + +def setup(): + check_bootsec() + print("Performing initial setup") + uos.VfsLfs2.mkfs(bdev) + vfs = uos.VfsLfs2(bdev) + uos.mount(vfs, "/") + with open("boot.py", "w") as f: + f.write( + """\ +# This file is executed on every boot (including wake-boot from deepsleep) +#import esp +#esp.osdebug(None) +#import webrepl +#webrepl.start() +""" + ) + return vfs diff --git a/microros_ws/extra_packages/micropython/src/moduos.c b/microros_ws/extra_packages/micropython/src/moduos.c new file mode 100644 index 0000000..bdfd19c --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/moduos.c @@ -0,0 +1,65 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Josef Gajdusek + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "esp_system.h" + +#include "py/runtime.h" +#include "py/mphal.h" +#include "extmod/misc.h" + +STATIC mp_obj_t mp_uos_urandom(mp_obj_t num) { + mp_int_t n = mp_obj_get_int(num); + vstr_t vstr; + vstr_init_len(&vstr, n); + uint32_t r = 0; + for (int i = 0; i < n; i++) { + if ((i & 3) == 0) { + r = esp_random(); // returns 32-bit hardware random number + } + vstr.buf[i] = r; + r >>= 8; + } + return mp_obj_new_bytes_from_vstr(&vstr); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_uos_urandom_obj, mp_uos_urandom); + +#if MICROPY_PY_UOS_DUPTERM_NOTIFY +STATIC mp_obj_t mp_uos_dupterm_notify(mp_obj_t obj_in) { + (void)obj_in; + for (;;) { + int c = mp_uos_dupterm_rx_chr(); + if (c < 0) { + break; + } + ringbuf_put(&stdin_ringbuf, c); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_uos_dupterm_notify_obj, mp_uos_dupterm_notify); +#endif diff --git a/microros_ws/extra_packages/micropython/src/modutime.c b/microros_ws/extra_packages/micropython/src/modutime.c new file mode 100644 index 0000000..631d0fe --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/modutime.c @@ -0,0 +1,110 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "py/runtime.h" +#include "shared/timeutils/timeutils.h" +#include "extmod/utime_mphal.h" + +STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { + timeutils_struct_time_t tm; + mp_int_t seconds; + if (n_args == 0 || args[0] == mp_const_none) { + struct timeval tv; + gettimeofday(&tv, NULL); + seconds = tv.tv_sec; + } else { + seconds = mp_obj_get_int(args[0]); + } + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); + mp_obj_t tuple[8] = { + tuple[0] = mp_obj_new_int(tm.tm_year), + tuple[1] = mp_obj_new_int(tm.tm_mon), + tuple[2] = mp_obj_new_int(tm.tm_mday), + tuple[3] = mp_obj_new_int(tm.tm_hour), + tuple[4] = mp_obj_new_int(tm.tm_min), + tuple[5] = mp_obj_new_int(tm.tm_sec), + tuple[6] = mp_obj_new_int(tm.tm_wday), + tuple[7] = mp_obj_new_int(tm.tm_yday), + }; + return mp_obj_new_tuple(8, tuple); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(time_localtime_obj, 0, 1, time_localtime); + +STATIC mp_obj_t time_mktime(mp_obj_t tuple) { + size_t len; + mp_obj_t *elem; + mp_obj_get_array(tuple, &len, &elem); + + // localtime generates a tuple of len 8. CPython uses 9, so we accept both. + if (len < 8 || len > 9) { + mp_raise_msg_varg(&mp_type_TypeError, MP_ERROR_TEXT("mktime needs a tuple of length 8 or 9 (%d given)"), len); + } + + return mp_obj_new_int_from_uint(timeutils_mktime(mp_obj_get_int(elem[0]), + mp_obj_get_int(elem[1]), mp_obj_get_int(elem[2]), mp_obj_get_int(elem[3]), + mp_obj_get_int(elem[4]), mp_obj_get_int(elem[5]))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(time_mktime_obj, time_mktime); + +STATIC mp_obj_t time_time(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return mp_obj_new_int(tv.tv_sec); +} +MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); + +STATIC const mp_rom_map_elem_t time_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) }, + + { MP_ROM_QSTR(MP_QSTR_gmtime), MP_ROM_PTR(&time_localtime_obj) }, + { MP_ROM_QSTR(MP_QSTR_localtime), MP_ROM_PTR(&time_localtime_obj) }, + { MP_ROM_QSTR(MP_QSTR_mktime), MP_ROM_PTR(&time_mktime_obj) }, + { MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_us), MP_ROM_PTR(&mp_utime_ticks_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) }, + { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) }, + { MP_ROM_QSTR(MP_QSTR_time_ns), MP_ROM_PTR(&mp_utime_time_ns_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table); + +const mp_obj_module_t utime_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&time_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_utime, utime_module); diff --git a/microros_ws/extra_packages/micropython/src/mpconfigport.h b/microros_ws/extra_packages/micropython/src/mpconfigport.h new file mode 100644 index 0000000..38468cf --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/mpconfigport.h @@ -0,0 +1,237 @@ +// Options to control how MicroPython is built for this port, +// overriding defaults in py/mpconfig.h. + +// Board-specific definitions +#include "mpconfigboard.h" + +#include +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "driver/i2s.h" + +#ifndef MICROPY_CONFIG_ROM_LEVEL +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) +#endif + +// object representation and NLR handling +#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A) +#define MICROPY_NLR_SETJMP (1) +#if CONFIG_IDF_TARGET_ESP32C3 +#define MICROPY_GCREGS_SETJMP (1) +#endif + +// memory allocation policies +#define MICROPY_ALLOC_PATH_MAX (128) + +// emitters +#define MICROPY_PERSISTENT_CODE_LOAD (1) +#if !CONFIG_IDF_TARGET_ESP32C3 +#define MICROPY_EMIT_XTENSAWIN (1) +#endif + +// workaround for xtensa-esp32-elf-gcc esp-2020r3, which can generate wrong code for loops +// see https://github.com/espressif/esp-idf/issues/9130 +// this was fixed in newer versions of the compiler by: +// "gas: use literals/const16 for xtensa loop relaxation" +// https://github.com/jcmvbkbc/binutils-gdb-xtensa/commit/403b0b61f6d4358aee8493cb1d11814e368942c9 +#define MICROPY_COMP_CONST_FOLDING_COMPILER_WORKAROUND (1) + +// optimisations +#define MICROPY_OPT_COMPUTED_GOTO (1) + +// Python internal features +#define MICROPY_READER_VFS (1) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) +#define MICROPY_WARNINGS (1) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) +#define MICROPY_STREAMS_POSIX_API (1) +#define MICROPY_MODULE_FROZEN_STR (0) +#define MICROPY_MODULE_FROZEN_MPY (1) +#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool +#define MICROPY_USE_INTERNAL_ERRNO (0) // errno.h from xtensa-esp32-elf/sys-include/sys +#define MICROPY_USE_INTERNAL_PRINTF (0) // ESP32 SDK requires its own printf +#define MICROPY_SCHEDULER_DEPTH (8) +#define MICROPY_VFS (1) + +// control over Python builtins +#define MICROPY_PY_STR_BYTES_CMP_WARN (1) +#define MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS (1) +#define MICROPY_PY_BUILTINS_HELP_TEXT esp32_help_text +#define MICROPY_PY_IO_BUFFEREDWRITER (1) +#define MICROPY_PY_UTIME_MP_HAL (1) +#define MICROPY_PY_THREAD (1) +#define MICROPY_PY_THREAD_GIL (1) +#define MICROPY_PY_THREAD_GIL_VM_DIVISOR (32) + +// extended modules +#ifndef MICROPY_PY_BLUETOOTH +#define MICROPY_PY_BLUETOOTH (1) +#define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS (1) +#define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK (1) +#define MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE (CONFIG_BT_NIMBLE_TASK_STACK_SIZE) +#define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (1) +#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (1) +#define MICROPY_BLUETOOTH_NIMBLE (1) +#define MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY (1) +#endif +#define MICROPY_PY_UTIMEQ (1) +#define MICROPY_PY_UHASHLIB_SHA1 (1) +#define MICROPY_PY_UHASHLIB_SHA256 (1) +#define MICROPY_PY_UCRYPTOLIB (1) +#define MICROPY_PY_URANDOM_SEED_INIT_FUNC (esp_random()) +#define MICROPY_PY_UOS_INCLUDEFILE "ports/esp32/moduos.c" +#define MICROPY_PY_OS_DUPTERM (1) +#define MICROPY_PY_UOS_DUPTERM_NOTIFY (1) +#define MICROPY_PY_UOS_UNAME (1) +#define MICROPY_PY_UOS_URANDOM (1) +#define MICROPY_PY_MACHINE (1) +#define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new +#define MICROPY_PY_MACHINE_BITSTREAM (1) +#define MICROPY_PY_MACHINE_PULSE (1) +#define MICROPY_PY_MACHINE_PWM (1) +#define MICROPY_PY_MACHINE_PWM_INIT (1) +#define MICROPY_PY_MACHINE_PWM_DUTY (1) +#define MICROPY_PY_MACHINE_PWM_DUTY_U16_NS (1) +#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/esp32/machine_pwm.c" +#define MICROPY_PY_MACHINE_I2C (1) +#define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) +#define MICROPY_PY_MACHINE_SOFTI2C (1) +#define MICROPY_PY_MACHINE_SPI (1) +#define MICROPY_PY_MACHINE_SPI_MSB (0) +#define MICROPY_PY_MACHINE_SPI_LSB (1) +#define MICROPY_PY_MACHINE_SOFTSPI (1) +#ifndef MICROPY_PY_MACHINE_DAC +#define MICROPY_PY_MACHINE_DAC (1) +#endif +#ifndef MICROPY_PY_MACHINE_I2S +#define MICROPY_PY_MACHINE_I2S (1) +#endif +#ifndef MICROPY_PY_NETWORK_WLAN +#define MICROPY_PY_NETWORK_WLAN (1) +#endif +#ifndef MICROPY_HW_ENABLE_SDCARD +#define MICROPY_HW_ENABLE_SDCARD (1) +#endif +#define MICROPY_HW_SOFTSPI_MIN_DELAY (0) +#define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (ets_get_cpu_frequency() * 1000000 / 200) // roughly +#define MICROPY_PY_USSL (1) +#define MICROPY_SSL_MBEDTLS (1) +#define MICROPY_PY_USSL_FINALISER (1) +#define MICROPY_PY_UWEBSOCKET (1) +#define MICROPY_PY_WEBREPL (1) +#define MICROPY_PY_ONEWIRE (1) +#define MICROPY_PY_UPLATFORM (1) +#define MICROPY_PY_USOCKET_EVENTS (MICROPY_PY_WEBREPL) +#define MICROPY_PY_BLUETOOTH_RANDOM_ADDR (1) +#define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME ("ESP32") + +// fatfs configuration +#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_RPATH (2) +#define MICROPY_FATFS_MAX_SS (4096) +#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ + +#define MP_STATE_PORT MP_STATE_VM + +// type definitions for the specific machine + +#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p))) +void *esp_native_code_commit(void *, size_t, void *); +#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) esp_native_code_commit(buf, len, reloc) +#define MP_SSIZE_MAX (0x7fffffff) + +// Note: these "critical nested" macros do not ensure cross-CPU exclusion, +// the only disable interrupts on the current CPU. To full manage exclusion +// one should use portENTER_CRITICAL/portEXIT_CRITICAL instead. +#include "freertos/FreeRTOS.h" +#define MICROPY_BEGIN_ATOMIC_SECTION() portENTER_CRITICAL_NESTED() +#define MICROPY_END_ATOMIC_SECTION(state) portEXIT_CRITICAL_NESTED(state) + +#if MICROPY_PY_USOCKET_EVENTS +#define MICROPY_PY_USOCKET_EVENTS_HANDLER extern void usocket_events_handler(void); usocket_events_handler(); +#else +#define MICROPY_PY_USOCKET_EVENTS_HANDLER +#endif + +#if MICROPY_PY_THREAD +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ + MICROPY_PY_USOCKET_EVENTS_HANDLER \ + MP_THREAD_GIL_EXIT(); \ + ulTaskNotifyTake(pdFALSE, 1); \ + MP_THREAD_GIL_ENTER(); \ + } while (0); +#else +#define MICROPY_EVENT_POLL_HOOK \ + do { \ + extern void mp_handle_pending(bool); \ + mp_handle_pending(true); \ + MICROPY_PY_USOCKET_EVENTS_HANDLER \ + asm ("waiti 0"); \ + } while (0); +#endif + +// Functions that should go in IRAM +#define MICROPY_WRAP_MP_BINARY_OP(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_EXECUTE_BYTECODE(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_LOAD_GLOBAL(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_LOAD_NAME(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_MAP_LOOKUP(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_OBJ_GET_TYPE(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_SCHED_EXCEPTION(f) IRAM_ATTR f +#define MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(f) IRAM_ATTR f + +#define UINT_FMT "%u" +#define INT_FMT "%d" + +typedef int32_t mp_int_t; // must be pointer size +typedef uint32_t mp_uint_t; // must be pointer size +typedef long mp_off_t; +// ssize_t, off_t as required by POSIX-signatured functions in stream.h +#include + +// board specifics +#define MICROPY_PY_SYS_PLATFORM "esp32" + +// ESP32-S3 extended IO for 47 & 48 +#ifndef MICROPY_HW_ESP32S3_EXTENDED_IO +#define MICROPY_HW_ESP32S3_EXTENDED_IO (1) +#endif + +#ifndef MICROPY_HW_ENABLE_MDNS_QUERIES +#define MICROPY_HW_ENABLE_MDNS_QUERIES (1) +#endif + +#ifndef MICROPY_HW_ENABLE_MDNS_RESPONDER +#define MICROPY_HW_ENABLE_MDNS_RESPONDER (1) +#endif + +#ifndef MICROPY_BOARD_STARTUP +#define MICROPY_BOARD_STARTUP boardctrl_startup +#endif + +void boardctrl_startup(void); + +#ifndef MICROPY_PY_NETWORK_LAN +#if (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 1) && (CONFIG_IDF_TARGET_ESP32 || (CONFIG_ETH_USE_SPI_ETHERNET && (CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL || CONFIG_ETH_SPI_ETHERNET_DM9051 || CONFIG_ETH_SPI_ETHERNET_W5500))) +#define MICROPY_PY_NETWORK_LAN (1) +#else +#define MICROPY_PY_NETWORK_LAN (0) +#endif +#endif + +#if MICROPY_PY_NETWORK_LAN && CONFIG_ETH_USE_SPI_ETHERNET +#ifndef MICROPY_PY_NETWORK_LAN_SPI_CLOCK_SPEED_MZ +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 +#define MICROPY_PY_NETWORK_LAN_SPI_CLOCK_SPEED_MZ (12) +#else +#define MICROPY_PY_NETWORK_LAN_SPI_CLOCK_SPEED_MZ (36) +#endif +#endif +#endif diff --git a/microros_ws/extra_packages/micropython/src/mphalport.c b/microros_ws/extra_packages/micropython/src/mphalport.c new file mode 100644 index 0000000..2aaeb97 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/mphalport.c @@ -0,0 +1,200 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "py/obj.h" +#include "py/objstr.h" +#include "py/stream.h" +#include "py/mpstate.h" +#include "py/mphal.h" +#include "extmod/misc.h" +#include "shared/timeutils/timeutils.h" +#include "shared/runtime/pyexec.h" +#include "mphalport.h" +#include "usb.h" +#include "usb_serial_jtag.h" +#include "uart.h" + +TaskHandle_t mp_main_task_handle; + +STATIC uint8_t stdin_ringbuf_array[260]; +ringbuf_t stdin_ringbuf = {stdin_ringbuf_array, sizeof(stdin_ringbuf_array), 0, 0}; + +// Check the ESP-IDF error code and raise an OSError if it's not ESP_OK. +void check_esp_err(esp_err_t code) { + if (code != ESP_OK) { + // map esp-idf error code to posix error code + uint32_t pcode = -code; + switch (code) { + case ESP_ERR_NO_MEM: + pcode = MP_ENOMEM; + break; + case ESP_ERR_TIMEOUT: + pcode = MP_ETIMEDOUT; + break; + case ESP_ERR_NOT_SUPPORTED: + pcode = MP_EOPNOTSUPP; + break; + } + // construct string object + mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); + if (o_str == NULL) { + mp_raise_OSError(pcode); + return; + } + o_str->base.type = &mp_type_str; + o_str->data = (const byte *)esp_err_to_name(code); // esp_err_to_name ret's ptr to const str + o_str->len = strlen((char *)o_str->data); + o_str->hash = qstr_compute_hash(o_str->data, o_str->len); + // raise + mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(pcode), MP_OBJ_FROM_PTR(o_str)}; + nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args)); + } +} + +uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { + uintptr_t ret = 0; + if ((poll_flags & MP_STREAM_POLL_RD) && stdin_ringbuf.iget != stdin_ringbuf.iput) { + ret |= MP_STREAM_POLL_RD; + } + return ret; +} + +int mp_hal_stdin_rx_chr(void) { + for (;;) { + int c = ringbuf_get(&stdin_ringbuf); + if (c != -1) { + return c; + } + MICROPY_EVENT_POLL_HOOK + } +} + +void mp_hal_stdout_tx_strn(const char *str, size_t len) { + // Only release the GIL if many characters are being sent + bool release_gil = len > 20; + if (release_gil) { + MP_THREAD_GIL_EXIT(); + } + #if CONFIG_USB_ENABLED + usb_tx_strn(str, len); + #elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG + usb_serial_jtag_tx_strn(str, len); + #endif + #if MICROPY_HW_ENABLE_UART_REPL + uart_stdout_tx_strn(str, len); + #endif + if (release_gil) { + MP_THREAD_GIL_ENTER(); + } + mp_uos_dupterm_tx_strn(str, len); +} + +uint32_t mp_hal_ticks_ms(void) { + return esp_timer_get_time() / 1000; +} + +uint32_t mp_hal_ticks_us(void) { + return esp_timer_get_time(); +} + +void mp_hal_delay_ms(uint32_t ms) { + uint64_t us = (uint64_t)ms * 1000ULL; + uint64_t dt; + uint64_t t0 = esp_timer_get_time(); + for (;;) { + mp_handle_pending(true); + MICROPY_PY_USOCKET_EVENTS_HANDLER + MP_THREAD_GIL_EXIT(); + uint64_t t1 = esp_timer_get_time(); + dt = t1 - t0; + if (dt + portTICK_PERIOD_MS * 1000ULL >= us) { + // doing a vTaskDelay would take us beyond requested delay time + taskYIELD(); + MP_THREAD_GIL_ENTER(); + t1 = esp_timer_get_time(); + dt = t1 - t0; + break; + } else { + ulTaskNotifyTake(pdFALSE, 1); + MP_THREAD_GIL_ENTER(); + } + } + if (dt < us) { + // do the remaining delay accurately + mp_hal_delay_us(us - dt); + } +} + +void mp_hal_delay_us(uint32_t us) { + // these constants are tested for a 240MHz clock + const uint32_t this_overhead = 5; + const uint32_t pend_overhead = 150; + + // return if requested delay is less than calling overhead + if (us < this_overhead) { + return; + } + us -= this_overhead; + + uint64_t t0 = esp_timer_get_time(); + for (;;) { + uint64_t dt = esp_timer_get_time() - t0; + if (dt >= us) { + return; + } + if (dt + pend_overhead < us) { + // we have enough time to service pending events + // (don't use MICROPY_EVENT_POLL_HOOK because it also yields) + mp_handle_pending(true); + } + } +} + +uint64_t mp_hal_time_ns(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t ns = tv.tv_sec * 1000000000ULL; + ns += (uint64_t)tv.tv_usec * 1000ULL; + return ns; +} + +// Wake up the main task if it is sleeping +void mp_hal_wake_main_task_from_isr(void) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + vTaskNotifyGiveFromISR(mp_main_task_handle, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} diff --git a/microros_ws/extra_packages/micropython/src/mphalport.h b/microros_ws/extra_packages/micropython/src/mphalport.h new file mode 100644 index 0000000..7f14b6e --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/mphalport.h @@ -0,0 +1,118 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef INCLUDED_MPHALPORT_H +#define INCLUDED_MPHALPORT_H + +#include "py/ringbuf.h" +#include "shared/runtime/interrupt_char.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "driver/spi_master.h" + +#define MICROPY_PLATFORM_VERSION "IDF" IDF_VER + +// The core that the MicroPython task(s) are pinned to. +// Now that we have IDF 4.2.0+, we are once again able to pin to core 1 +// and avoid the Wifi/BLE timing problems on the same core. +// Best effort here to remain backwards compatible in rare version edge cases... +// See https://github.com/micropython/micropython/issues/5489 for history +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) +#define MP_TASK_COREID (1) +#else +#define MP_TASK_COREID (0) +#endif + +extern TaskHandle_t mp_main_task_handle; + +extern ringbuf_t stdin_ringbuf; + +// Check the ESP-IDF error code and raise an OSError if it's not ESP_OK. +void check_esp_err(esp_err_t code); + +uint32_t mp_hal_ticks_us(void); +__attribute__((always_inline)) static inline uint32_t mp_hal_ticks_cpu(void) { + uint32_t ccount; + #if CONFIG_IDF_TARGET_ESP32C3 + __asm__ __volatile__ ("csrr %0, 0x7E2" : "=r" (ccount)); // Machine Performance Counter Value + #else + __asm__ __volatile__ ("rsr %0,ccount" : "=a" (ccount)); + #endif + return ccount; +} + +void mp_hal_delay_us(uint32_t); +#define mp_hal_delay_us_fast(us) ets_delay_us(us) +void mp_hal_set_interrupt_char(int c); +uint32_t mp_hal_get_cpu_freq(void); + +#define mp_hal_quiet_timing_enter() MICROPY_BEGIN_ATOMIC_SECTION() +#define mp_hal_quiet_timing_exit(irq_state) MICROPY_END_ATOMIC_SECTION(irq_state) + +// Wake up the main task if it is sleeping +void mp_hal_wake_main_task_from_isr(void); + +// C-level pin HAL +#include "py/obj.h" +#include "driver/gpio.h" +#define MP_HAL_PIN_FMT "%u" +#define mp_hal_pin_obj_t gpio_num_t +mp_hal_pin_obj_t machine_pin_get_id(mp_obj_t pin_in); +#define mp_hal_get_pin_obj(o) machine_pin_get_id(o) +#define mp_obj_get_pin(o) machine_pin_get_id(o) // legacy name; only to support esp8266/modonewire +#define mp_hal_pin_name(p) (p) +static inline void mp_hal_pin_input(mp_hal_pin_obj_t pin) { + gpio_pad_select_gpio(pin); + gpio_set_direction(pin, GPIO_MODE_INPUT); +} +static inline void mp_hal_pin_output(mp_hal_pin_obj_t pin) { + gpio_pad_select_gpio(pin); + gpio_set_direction(pin, GPIO_MODE_INPUT_OUTPUT); +} +static inline void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin) { + gpio_pad_select_gpio(pin); + gpio_set_direction(pin, GPIO_MODE_INPUT_OUTPUT_OD); +} +static inline void mp_hal_pin_od_low(mp_hal_pin_obj_t pin) { + gpio_set_level(pin, 0); +} +static inline void mp_hal_pin_od_high(mp_hal_pin_obj_t pin) { + gpio_set_level(pin, 1); +} +static inline int mp_hal_pin_read(mp_hal_pin_obj_t pin) { + return gpio_get_level(pin); +} +static inline void mp_hal_pin_write(mp_hal_pin_obj_t pin, int v) { + gpio_set_level(pin, v); +} + +spi_host_device_t machine_hw_spi_get_host(mp_obj_t in); + +#endif // INCLUDED_MPHALPORT_H diff --git a/microros_ws/extra_packages/micropython/src/mpnimbleport.c b/microros_ws/extra_packages/micropython/src/mpnimbleport.c new file mode 100644 index 0000000..a58fcbd --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/mpnimbleport.c @@ -0,0 +1,77 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jim Mussared + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE + +#define DEBUG_printf(...) // printf("nimble (esp32): " __VA_ARGS__) + +#include "esp_nimble_hci.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" + +#include "extmod/nimble/modbluetooth_nimble.h" + +STATIC void ble_host_task(void *param) { + DEBUG_printf("ble_host_task\n"); + nimble_port_run(); // This function will return only when nimble_port_stop() is executed. + nimble_port_freertos_deinit(); +} + +void mp_bluetooth_nimble_port_hci_init(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_init\n"); + esp_nimble_hci_and_controller_init(); +} + +void mp_bluetooth_nimble_port_hci_deinit(void) { + DEBUG_printf("mp_bluetooth_nimble_port_hci_deinit\n"); + + esp_nimble_hci_and_controller_deinit(); +} + +void mp_bluetooth_nimble_port_start(void) { + DEBUG_printf("mp_bluetooth_nimble_port_start\n"); + nimble_port_freertos_init(ble_host_task); +} + +void mp_bluetooth_nimble_port_shutdown(void) { + DEBUG_printf("mp_bluetooth_nimble_port_shutdown\n"); + + // Despite the name, these is an ESP32-specific (no other NimBLE ports have these functions). + // Calls ble_hs_stop() and waits for stack shutdown. + nimble_port_stop(); + + // Shuts down the event queue. + nimble_port_deinit(); + + // Mark stack as shutdown. + mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; +} + +#endif diff --git a/microros_ws/extra_packages/micropython/src/mpthreadport.c b/microros_ws/extra_packages/micropython/src/mpthreadport.c new file mode 100644 index 0000000..e6c7e9b --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/mpthreadport.c @@ -0,0 +1,243 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd + * Copyright (c) 2017 Pycom Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "stdio.h" + +#include "py/runtime.h" +#include "py/gc.h" +#include "py/mpthread.h" +#include "py/mphal.h" +#include "mpthreadport.h" + +#include "esp_task.h" + +#if MICROPY_PY_THREAD + +#define MP_THREAD_MIN_STACK_SIZE (4 * 1024) +#define MP_THREAD_DEFAULT_STACK_SIZE (MP_THREAD_MIN_STACK_SIZE + 1024) +#define MP_THREAD_PRIORITY (ESP_TASK_PRIO_MIN + 1) + +// this structure forms a linked list, one node per active thread +typedef struct _mp_thread_t { + TaskHandle_t id; // system id of thread + int ready; // whether the thread is ready and running + void *arg; // thread Python args, a GC root pointer + void *stack; // pointer to the stack + size_t stack_len; // number of words in the stack + struct _mp_thread_t *next; +} mp_thread_t; + +// the mutex controls access to the linked list +STATIC mp_thread_mutex_t thread_mutex; +STATIC mp_thread_t thread_entry0; +STATIC mp_thread_t *thread = NULL; // root pointer, handled by mp_thread_gc_others + +void mp_thread_init(void *stack, uint32_t stack_len) { + mp_thread_set_state(&mp_state_ctx.thread); + // create the first entry in the linked list of all threads + thread_entry0.id = xTaskGetCurrentTaskHandle(); + thread_entry0.ready = 1; + thread_entry0.arg = NULL; + thread_entry0.stack = stack; + thread_entry0.stack_len = stack_len; + thread_entry0.next = NULL; + mp_thread_mutex_init(&thread_mutex); + + // memory barrier to ensure above data is committed + __sync_synchronize(); + + // vPortCleanUpTCB needs the thread ready after thread_mutex is ready + thread = &thread_entry0; +} + +void mp_thread_gc_others(void) { + mp_thread_mutex_lock(&thread_mutex, 1); + for (mp_thread_t *th = thread; th != NULL; th = th->next) { + gc_collect_root((void **)&th, 1); + gc_collect_root(&th->arg, 1); // probably not needed + if (th->id == xTaskGetCurrentTaskHandle()) { + continue; + } + if (!th->ready) { + continue; + } + gc_collect_root(th->stack, th->stack_len); + } + mp_thread_mutex_unlock(&thread_mutex); +} + +mp_state_thread_t *mp_thread_get_state(void) { + return pvTaskGetThreadLocalStoragePointer(NULL, 1); +} + +void mp_thread_set_state(mp_state_thread_t *state) { + vTaskSetThreadLocalStoragePointer(NULL, 1, state); +} + +void mp_thread_start(void) { + mp_thread_mutex_lock(&thread_mutex, 1); + for (mp_thread_t *th = thread; th != NULL; th = th->next) { + if (th->id == xTaskGetCurrentTaskHandle()) { + th->ready = 1; + break; + } + } + mp_thread_mutex_unlock(&thread_mutex); +} + +STATIC void *(*ext_thread_entry)(void *) = NULL; + +STATIC void freertos_entry(void *arg) { + if (ext_thread_entry) { + ext_thread_entry(arg); + } + vTaskDelete(NULL); + for (;;) {; + } +} + +void mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_size, int priority, char *name) { + // store thread entry function into a global variable so we can access it + ext_thread_entry = entry; + + if (*stack_size == 0) { + *stack_size = MP_THREAD_DEFAULT_STACK_SIZE; // default stack size + } else if (*stack_size < MP_THREAD_MIN_STACK_SIZE) { + *stack_size = MP_THREAD_MIN_STACK_SIZE; // minimum stack size + } + + // Allocate linked-list node (must be outside thread_mutex lock) + mp_thread_t *th = m_new_obj(mp_thread_t); + + mp_thread_mutex_lock(&thread_mutex, 1); + + // create thread + BaseType_t result = xTaskCreatePinnedToCore(freertos_entry, name, *stack_size / sizeof(StackType_t), arg, priority, &th->id, MP_TASK_COREID); + if (result != pdPASS) { + mp_thread_mutex_unlock(&thread_mutex); + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("can't create thread")); + } + + // add thread to linked list of all threads + th->ready = 0; + th->arg = arg; + th->stack = pxTaskGetStackStart(th->id); + th->stack_len = *stack_size / sizeof(uintptr_t); + th->next = thread; + thread = th; + + // adjust the stack_size to provide room to recover from hitting the limit + *stack_size -= 1024; + + mp_thread_mutex_unlock(&thread_mutex); +} + +void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { + mp_thread_create_ex(entry, arg, stack_size, MP_THREAD_PRIORITY, "mp_thread"); +} + +void mp_thread_finish(void) { + mp_thread_mutex_lock(&thread_mutex, 1); + for (mp_thread_t *th = thread; th != NULL; th = th->next) { + if (th->id == xTaskGetCurrentTaskHandle()) { + th->ready = 0; + break; + } + } + mp_thread_mutex_unlock(&thread_mutex); +} + +// This is called from the FreeRTOS idle task and is not within Python context, +// so MP_STATE_THREAD is not valid and it does not have the GIL. +void vPortCleanUpTCB(void *tcb) { + if (thread == NULL) { + // threading not yet initialised + return; + } + mp_thread_t *prev = NULL; + mp_thread_mutex_lock(&thread_mutex, 1); + for (mp_thread_t *th = thread; th != NULL; prev = th, th = th->next) { + // unlink the node from the list + if ((void *)th->id == tcb) { + if (prev != NULL) { + prev->next = th->next; + } else { + // move the start pointer + thread = th->next; + } + // The "th" memory will eventually be reclaimed by the GC. + break; + } + } + mp_thread_mutex_unlock(&thread_mutex); +} + +void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { + // Need a binary semaphore so a lock can be acquired on one Python thread + // and then released on another. + mutex->handle = xSemaphoreCreateBinaryStatic(&mutex->buffer); + xSemaphoreGive(mutex->handle); +} + +int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { + return pdTRUE == xSemaphoreTake(mutex->handle, wait ? portMAX_DELAY : 0); +} + +void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { + xSemaphoreGive(mutex->handle); +} + +void mp_thread_deinit(void) { + for (;;) { + // Find a task to delete + TaskHandle_t id = NULL; + mp_thread_mutex_lock(&thread_mutex, 1); + for (mp_thread_t *th = thread; th != NULL; th = th->next) { + // Don't delete the current task + if (th->id != xTaskGetCurrentTaskHandle()) { + id = th->id; + break; + } + } + mp_thread_mutex_unlock(&thread_mutex); + + if (id == NULL) { + // No tasks left to delete + break; + } else { + // Call FreeRTOS to delete the task (it will call vPortCleanUpTCB) + vTaskDelete(id); + } + } +} + +#else + +void vPortCleanUpTCB(void *tcb) { +} + +#endif // MICROPY_PY_THREAD diff --git a/microros_ws/extra_packages/micropython/src/mpthreadport.h b/microros_ws/extra_packages/micropython/src/mpthreadport.h new file mode 100644 index 0000000..54e35d6 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/mpthreadport.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd + * Copyright (c) 2017 Pycom Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ESP32_MPTHREADPORT_H +#define MICROPY_INCLUDED_ESP32_MPTHREADPORT_H + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" + +typedef struct _mp_thread_mutex_t { + SemaphoreHandle_t handle; + StaticSemaphore_t buffer; +} mp_thread_mutex_t; + +void mp_thread_init(void *stack, uint32_t stack_len); +void mp_thread_gc_others(void); +void mp_thread_deinit(void); + +#endif // MICROPY_INCLUDED_ESP32_MPTHREADPORT_H diff --git a/microros_ws/extra_packages/micropython/src/network_lan.c b/microros_ws/extra_packages/micropython/src/network_lan.c new file mode 100644 index 0000000..d482f95 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/network_lan.c @@ -0,0 +1,430 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 "Eric Poulsen" + * Copyright (c) 2021 "Tobias Eydam" + * + * Based on the ESP IDF example code which is Public Domain / CC0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" + +#include "esp_idf_version.h" + +// LAN only for ESP32 (not ESP32S2) and only for ESP-IDF v4.1 and higher +#if MICROPY_PY_NETWORK_LAN + +#include "esp_eth.h" +#include "esp_eth_mac.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#if CONFIG_ETH_USE_SPI_ETHERNET +#include "driver/spi_master.h" +#endif + +#include "modnetwork.h" + +typedef struct _lan_if_obj_t { + mp_obj_base_t base; + int if_id; // MUST BE FIRST to match wlan_if_obj_t + bool initialized; + bool active; + int8_t mdc_pin; + int8_t mdio_pin; + int8_t phy_power_pin; + int8_t phy_cs_pin; + int8_t phy_int_pin; + uint8_t phy_addr; + uint8_t phy_type; + esp_eth_phy_t *phy; + esp_netif_t *eth_netif; + esp_eth_handle_t eth_handle; +} lan_if_obj_t; + +const mp_obj_type_t lan_if_type; +STATIC lan_if_obj_t lan_obj = {{&lan_if_type}, ESP_IF_ETH, false, false}; +STATIC uint8_t eth_status = 0; + +static void eth_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) { + switch (event_id) { + case ETHERNET_EVENT_CONNECTED: + eth_status = ETH_CONNECTED; + ESP_LOGI("ethernet", "Ethernet Link Up"); + break; + case ETHERNET_EVENT_DISCONNECTED: + eth_status = ETH_DISCONNECTED; + ESP_LOGI("ethernet", "Ethernet Link Down"); + break; + case ETHERNET_EVENT_START: + eth_status = ETH_STARTED; + ESP_LOGI("ethernet", "Ethernet Started"); + break; + case ETHERNET_EVENT_STOP: + eth_status = ETH_STOPPED; + ESP_LOGI("ethernet", "Ethernet Stopped"); + break; + case IP_EVENT_ETH_GOT_IP: + eth_status = ETH_GOT_IP; + ESP_LOGI("ethernet", "Ethernet Got IP"); + break; + default: + break; + } +} + +STATIC mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + lan_if_obj_t *self = &lan_obj; + + if (self->initialized) { + return MP_OBJ_FROM_PTR(&lan_obj); + } + + enum { ARG_id, ARG_mdc, ARG_mdio, ARG_power, ARG_phy_addr, ARG_phy_type, + ARG_spi, ARG_cs, ARG_int, ARG_ref_clk_mode, ARG_ref_clk }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_id, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_mdc, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_mdio, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_power, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_phy_addr, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_phy_type, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_spi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_int, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #if ESP_IDF_VERSION_MINOR >= 4 + // Dynamic ref_clk configuration available at v4.4 + { MP_QSTR_ref_clk_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_ref_clk, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + #endif + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (args[ARG_id].u_obj != mp_const_none) { + if (mp_obj_get_int(args[ARG_id].u_obj) != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid LAN interface identifier")); + } + } + + #define GET_PIN(XXX) args[XXX].u_obj == mp_const_none ? -1 : machine_pin_get_id(args[XXX].u_obj); + + self->mdc_pin = GET_PIN(ARG_mdc); + self->mdio_pin = GET_PIN(ARG_mdio); + self->phy_power_pin = GET_PIN(ARG_power); + self->phy_cs_pin = GET_PIN(ARG_cs); + self->phy_int_pin = GET_PIN(ARG_int); + + if (args[ARG_phy_addr].u_int < 0x00 || args[ARG_phy_addr].u_int > 0x1f) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid phy address")); + } + self->phy_addr = args[ARG_phy_addr].u_int; + + if (args[ARG_phy_type].u_int != PHY_LAN8710 && + args[ARG_phy_type].u_int != PHY_LAN8720 && + args[ARG_phy_type].u_int != PHY_IP101 && + args[ARG_phy_type].u_int != PHY_RTL8201 && + #if ESP_IDF_VERSION_MINOR >= 3 // KSZ8041 is new in ESP-IDF v4.3 + args[ARG_phy_type].u_int != PHY_KSZ8041 && + #endif + #if CONFIG_ETH_USE_SPI_ETHERNET + #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + args[ARG_phy_type].u_int != PHY_KSZ8851SNL && + #endif + #if CONFIG_ETH_SPI_ETHERNET_DM9051 + args[ARG_phy_type].u_int != PHY_DM9051 && + #endif + #if CONFIG_ETH_SPI_ETHERNET_W5500 + args[ARG_phy_type].u_int != PHY_W5500 && + #endif + #endif + args[ARG_phy_type].u_int != PHY_DP83848) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid phy type")); + } + + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + esp_eth_mac_t *mac = NULL; + + // Dynamic ref_clk configuration available at v4.4 + #if ESP_IDF_VERSION_MINOR >= 4 + if (args[ARG_ref_clk_mode].u_int != -1) { + // Map the GPIO_MODE constants to EMAC_CLK constants. + mac_config.clock_config.rmii.clock_mode = + args[ARG_ref_clk_mode].u_int == GPIO_MODE_INPUT ? EMAC_CLK_EXT_IN : EMAC_CLK_OUT; + } + if (args[ARG_ref_clk].u_obj != mp_const_none) { + mac_config.clock_config.rmii.clock_gpio = machine_pin_get_id(args[ARG_ref_clk].u_obj); + } + #endif + + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + phy_config.phy_addr = self->phy_addr; + phy_config.reset_gpio_num = self->phy_power_pin; + self->phy = NULL; + + #if CONFIG_ETH_USE_SPI_ETHERNET + spi_device_handle_t spi_handle = NULL; + if (IS_SPI_PHY(args[ARG_phy_type].u_int)) { + spi_device_interface_config_t devcfg = { + .mode = 0, + .clock_speed_hz = MICROPY_PY_NETWORK_LAN_SPI_CLOCK_SPEED_MZ * 1000 * 1000, + .queue_size = 20, + .spics_io_num = self->phy_cs_pin, + }; + switch (args[ARG_phy_type].u_int) { + #if CONFIG_ETH_SPI_ETHERNET_DM9051 + case PHY_DM9051: { + devcfg.command_bits = 1; + devcfg.address_bits = 7; + break; + } + #endif + #if CONFIG_ETH_SPI_ETHERNET_W5500 + case PHY_W5500: { + devcfg.command_bits = 16; + devcfg.address_bits = 8; + break; + } + #endif + } + spi_host_device_t host = machine_hw_spi_get_host(args[ARG_spi].u_obj); + if (spi_bus_add_device(host, &devcfg, &spi_handle) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("spi_bus_add_device failed")); + } + } + #endif + + switch (args[ARG_phy_type].u_int) { + #if CONFIG_IDF_TARGET_ESP32 + case PHY_LAN8710: + case PHY_LAN8720: + self->phy = esp_eth_phy_new_lan8720(&phy_config); + break; + case PHY_IP101: + self->phy = esp_eth_phy_new_ip101(&phy_config); + break; + case PHY_RTL8201: + self->phy = esp_eth_phy_new_rtl8201(&phy_config); + break; + case PHY_DP83848: + self->phy = esp_eth_phy_new_dp83848(&phy_config); + break; + #if ESP_IDF_VERSION_MINOR >= 3 // KSZ8041 is new in ESP-IDF v4.3 + case PHY_KSZ8041: + self->phy = esp_eth_phy_new_ksz8041(&phy_config); + break; + #endif + #endif + #if CONFIG_ETH_USE_SPI_ETHERNET + #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + case PHY_KSZ8851SNL: { + eth_ksz8851snl_config_t chip_config = ETH_KSZ8851SNL_DEFAULT_CONFIG(spi_handle); + chip_config.int_gpio_num = self->phy_int_pin; + mac = esp_eth_mac_new_ksz8851snl(&chip_config, &mac_config); + self->phy = esp_eth_phy_new_ksz8851snl(&phy_config); + break; + } + #endif + #if CONFIG_ETH_SPI_ETHERNET_DM9051 + case PHY_DM9051: { + eth_dm9051_config_t chip_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle); + chip_config.int_gpio_num = self->phy_int_pin; + mac = esp_eth_mac_new_dm9051(&chip_config, &mac_config); + self->phy = esp_eth_phy_new_dm9051(&phy_config); + break; + } + #endif + #if CONFIG_ETH_SPI_ETHERNET_W5500 + case PHY_W5500: { + eth_w5500_config_t chip_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); + chip_config.int_gpio_num = self->phy_int_pin; + mac = esp_eth_mac_new_w5500(&chip_config, &mac_config); + self->phy = esp_eth_phy_new_w5500(&phy_config); + break; + } + #endif + #endif + } + + #if CONFIG_IDF_TARGET_ESP32 + if (!IS_SPI_PHY(args[ARG_phy_type].u_int)) { + if (self->mdc_pin == -1 || self->mdio_pin == -1) { + mp_raise_ValueError(MP_ERROR_TEXT("mdc and mdio must be specified")); + } + mac_config.smi_mdc_gpio_num = self->mdc_pin; + mac_config.smi_mdio_gpio_num = self->mdio_pin; + mac = esp_eth_mac_new_esp32(&mac_config); + } + #endif + + if (esp_netif_init() != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_netif_init failed")); + } + + esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH(); + self->eth_netif = esp_netif_new(&cfg); + + if (esp_eth_set_default_handlers(self->eth_netif) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_set_default_handlers failed (invalid parameter)")); + } + + if (esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_event_handler_register failed")); + } + + if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_event_handler_register failed")); + } + + esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, self->phy); + + esp_err_t esp_err = esp_eth_driver_install(&config, &self->eth_handle); + if (esp_err == ESP_OK) { + self->active = false; + self->initialized = true; + } else { + if (esp_err == ESP_ERR_INVALID_ARG) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_driver_install failed with invalid argument")); + } else if (esp_err == ESP_ERR_NO_MEM) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_driver_install failed with no memory for driver")); + } else { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_driver_install failed")); + } + } + + if (esp_netif_attach(self->eth_netif, esp_eth_new_netif_glue(self->eth_handle)) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_netif_attach failed")); + } + + eth_status = ETH_INITIALIZED; + + return MP_OBJ_FROM_PTR(&lan_obj); +} +MP_DEFINE_CONST_FUN_OBJ_KW(get_lan_obj, 0, get_lan); + +STATIC mp_obj_t lan_active(size_t n_args, const mp_obj_t *args) { + lan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args > 1) { + if (mp_obj_is_true(args[1])) { + self->active = (esp_eth_start(self->eth_handle) == ESP_OK); + if (!self->active) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ethernet enable failed")); + } + } else { + self->active = !(esp_eth_stop(self->eth_handle) == ESP_OK); + if (self->active) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ethernet disable failed")); + } + } + } + + return mp_obj_new_bool(self->active); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(lan_active_obj, 1, 2, lan_active); + +STATIC mp_obj_t lan_status(mp_obj_t self_in) { + return MP_OBJ_NEW_SMALL_INT(eth_status); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(lan_status_obj, lan_status); + +STATIC mp_obj_t lan_isconnected(mp_obj_t self_in) { + lan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + return self->active ? mp_obj_new_bool(self->phy->get_link(self->phy) == ETH_LINK_UP) : mp_const_false; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(lan_isconnected_obj, lan_isconnected); + +STATIC mp_obj_t lan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args != 1 && kwargs->used != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); + } + lan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (kwargs->used != 0) { + + for (size_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + case MP_QSTR_mac: { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(kwargs->table[i].value, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len != 6) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length")); + } + if ( + (esp_eth_ioctl(self->eth_handle, ETH_CMD_S_MAC_ADDR, bufinfo.buf) != ESP_OK) || + (esp_netif_set_mac(self->eth_netif, bufinfo.buf) != ESP_OK) + ) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("failed setting MAC address")); + } + break; + } + default: + break; + } + } + } + return mp_const_none; + } + + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); + } + + mp_obj_t val = mp_const_none; + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_mac: { + uint8_t mac[6]; + esp_eth_ioctl(self->eth_handle, ETH_CMD_G_MAC_ADDR, mac); + return mp_obj_new_bytes(mac, sizeof(mac)); + } + default: + mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); + } + + return val; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(lan_config_obj, 1, lan_config); + +STATIC const mp_rom_map_elem_t lan_if_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&lan_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&lan_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&lan_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&lan_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_ifconfig_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(lan_if_locals_dict, lan_if_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + lan_if_type, + MP_QSTR_LAN, + MP_TYPE_FLAG_NONE, + locals_dict, &lan_if_locals_dict + ); + +#endif diff --git a/microros_ws/extra_packages/micropython/src/network_ppp.c b/microros_ws/extra_packages/micropython/src/network_ppp.c new file mode 100644 index 0000000..703cdc7 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/network_ppp.c @@ -0,0 +1,286 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 "Eric Poulsen" + * + * Based on the ESP IDF example code which is Public Domain / CC0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/objtype.h" +#include "py/stream.h" +#include "shared/netutils/netutils.h" +#include "modmachine.h" + +#include "netif/ppp/ppp.h" +#include "netif/ppp/pppos.h" +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" +#include "netif/ppp/pppapi.h" + +#define PPP_CLOSE_TIMEOUT_MS (4000) + +typedef struct _ppp_if_obj_t { + mp_obj_base_t base; + bool active; + bool connected; + volatile bool clean_close; + ppp_pcb *pcb; + mp_obj_t stream; + SemaphoreHandle_t inactiveWaitSem; + volatile TaskHandle_t client_task_handle; + struct netif pppif; +} ppp_if_obj_t; + +const mp_obj_type_t ppp_if_type; + +static void ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { + ppp_if_obj_t *self = ctx; + struct netif *pppif = ppp_netif(self->pcb); + + switch (err_code) { + case PPPERR_NONE: + self->connected = (pppif->ip_addr.u_addr.ip4.addr != 0); + break; + case PPPERR_USER: + self->clean_close = true; + break; + case PPPERR_CONNECT: + self->connected = false; + break; + default: + break; + } +} + +STATIC mp_obj_t ppp_make_new(mp_obj_t stream) { + mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + + ppp_if_obj_t *self = m_new_obj_with_finaliser(ppp_if_obj_t); + + self->base.type = &ppp_if_type; + self->stream = stream; + self->active = false; + self->connected = false; + self->clean_close = false; + self->client_task_handle = NULL; + + return MP_OBJ_FROM_PTR(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(ppp_make_new_obj, ppp_make_new); + +static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) { + ppp_if_obj_t *self = ctx; + int err; + return mp_stream_rw(self->stream, data, len, &err, MP_STREAM_RW_WRITE); +} + +static void pppos_client_task(void *self_in) { + ppp_if_obj_t *self = (ppp_if_obj_t *)self_in; + uint8_t buf[256]; + + while (ulTaskNotifyTake(pdTRUE, 0) == 0) { + int err; + int len = mp_stream_rw(self->stream, buf, sizeof(buf), &err, 0); + if (len > 0) { + pppos_input_tcpip(self->pcb, (u8_t *)buf, len); + } + } + + self->client_task_handle = NULL; + vTaskDelete(NULL); +} + +STATIC mp_obj_t ppp_active(size_t n_args, const mp_obj_t *args) { + ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args > 1) { + if (mp_obj_is_true(args[1])) { + if (self->active) { + return mp_const_true; + } + + self->pcb = pppapi_pppos_create(&self->pppif, ppp_output_callback, ppp_status_cb, self); + + if (self->pcb == NULL) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("init failed")); + } + self->active = true; + } else { + if (!self->active) { + return mp_const_false; + } + + if (self->client_task_handle != NULL) { // is connecting or connected? + // Wait for PPPERR_USER, with timeout + pppapi_close(self->pcb, 0); + uint32_t t0 = mp_hal_ticks_ms(); + while (!self->clean_close && mp_hal_ticks_ms() - t0 < PPP_CLOSE_TIMEOUT_MS) { + mp_hal_delay_ms(10); + } + + // Shutdown task + xTaskNotifyGive(self->client_task_handle); + t0 = mp_hal_ticks_ms(); + while (self->client_task_handle != NULL && mp_hal_ticks_ms() - t0 < PPP_CLOSE_TIMEOUT_MS) { + mp_hal_delay_ms(10); + } + } + + // Release PPP + pppapi_free(self->pcb); + self->pcb = NULL; + self->active = false; + self->connected = false; + self->clean_close = false; + } + } + return mp_obj_new_bool(self->active); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ppp_active_obj, 1, 2, ppp_active); + +STATIC mp_obj_t ppp_connect_py(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + enum { ARG_authmode, ARG_username, ARG_password }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_authmode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = PPPAUTHTYPE_NONE} }, + { MP_QSTR_username, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_password, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + mp_arg_val_t parsed_args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, parsed_args); + + ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (!self->active) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("must be active")); + } + + if (self->client_task_handle != NULL) { + mp_raise_OSError(MP_EALREADY); + } + + switch (parsed_args[ARG_authmode].u_int) { + case PPPAUTHTYPE_NONE: + case PPPAUTHTYPE_PAP: + case PPPAUTHTYPE_CHAP: + break; + default: + mp_raise_ValueError(MP_ERROR_TEXT("invalid auth")); + } + + if (parsed_args[ARG_authmode].u_int != PPPAUTHTYPE_NONE) { + const char *username_str = mp_obj_str_get_str(parsed_args[ARG_username].u_obj); + const char *password_str = mp_obj_str_get_str(parsed_args[ARG_password].u_obj); + pppapi_set_auth(self->pcb, parsed_args[ARG_authmode].u_int, username_str, password_str); + } + if (pppapi_set_default(self->pcb) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("set default failed")); + } + + ppp_set_usepeerdns(self->pcb, true); + + if (pppapi_connect(self->pcb, 0) != ESP_OK) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("connect failed")); + } + + if (xTaskCreatePinnedToCore(pppos_client_task, "ppp", 2048, self, 1, (TaskHandle_t *)&self->client_task_handle, MP_TASK_COREID) != pdPASS) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create worker task")); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(ppp_connect_obj, 1, ppp_connect_py); + +STATIC mp_obj_t ppp_delete(mp_obj_t self_in) { + ppp_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t args[] = {self, mp_const_false}; + ppp_active(2, args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(ppp_delete_obj, ppp_delete); + +STATIC mp_obj_t ppp_ifconfig(size_t n_args, const mp_obj_t *args) { + ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args == 1) { + // get + const ip_addr_t *dns; + if (self->pcb != NULL) { + dns = dns_getserver(0); + struct netif *pppif = ppp_netif(self->pcb); + mp_obj_t tuple[4] = { + netutils_format_ipv4_addr((uint8_t *)&pppif->ip_addr, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&pppif->gw, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&pppif->netmask, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)dns, NETUTILS_BIG), + }; + return mp_obj_new_tuple(4, tuple); + } else { + mp_obj_t tuple[4] = { mp_const_none, mp_const_none, mp_const_none, mp_const_none }; + return mp_obj_new_tuple(4, tuple); + } + } else { + ip_addr_t dns; + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[1], 4, &items); + netutils_parse_ipv4_addr(items[3], (uint8_t *)&dns.u_addr.ip4, NETUTILS_BIG); + dns_setserver(0, &dns); + return mp_const_none; + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ppp_ifconfig_obj, 1, 2, ppp_ifconfig); + +STATIC mp_obj_t ppp_status(mp_obj_t self_in) { + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(ppp_status_obj, ppp_status); + +STATIC mp_obj_t ppp_isconnected(mp_obj_t self_in) { + ppp_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(self->connected); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(ppp_isconnected_obj, ppp_isconnected); + +STATIC const mp_rom_map_elem_t ppp_if_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&ppp_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&ppp_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&ppp_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&ppp_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&ppp_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ppp_delete_obj) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_NONE), MP_ROM_INT(PPPAUTHTYPE_NONE) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_PAP), MP_ROM_INT(PPPAUTHTYPE_PAP) }, + { MP_ROM_QSTR(MP_QSTR_AUTH_CHAP), MP_ROM_INT(PPPAUTHTYPE_CHAP) }, +}; +STATIC MP_DEFINE_CONST_DICT(ppp_if_locals_dict, ppp_if_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + ppp_if_type, + MP_QSTR_PPP, + MP_TYPE_FLAG_NONE, + locals_dict, &ppp_if_locals_dict + ); diff --git a/microros_ws/extra_packages/micropython/src/network_wlan.c b/microros_ws/extra_packages/micropython/src/network_wlan.c new file mode 100644 index 0000000..f0b458e --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/network_wlan.c @@ -0,0 +1,628 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * and Mnemote Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016, 2017 Nick Moore @mnemote + * Copyright (c) 2017 "Eric Poulsen" + * + * Based on esp8266/modnetwork.c which is Copyright (c) 2015 Paul Sokolovsky + * And the ESP IDF example code which is Public Domain / CC0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/objlist.h" +#include "py/runtime.h" +#include "modnetwork.h" + +#include "esp_wifi.h" +#include "esp_log.h" +#include "mdns.h" + +#if MICROPY_PY_NETWORK_WLAN + +#if (WIFI_MODE_STA & WIFI_MODE_AP != WIFI_MODE_NULL || WIFI_MODE_STA | WIFI_MODE_AP != WIFI_MODE_APSTA) +#error WIFI_MODE_STA and WIFI_MODE_AP are supposed to be bitfields! +#endif + +STATIC const wlan_if_obj_t wlan_sta_obj; +STATIC const wlan_if_obj_t wlan_ap_obj; + +// Set to "true" if esp_wifi_start() was called +static bool wifi_started = false; + +// Set to "true" if the STA interface is requested to be connected by the +// user, used for automatic reassociation. +static bool wifi_sta_connect_requested = false; + +// Set to "true" if the STA interface is connected to wifi and has IP address. +static bool wifi_sta_connected = false; + +// Store the current status. 0 means None here, safe to do so as first enum value is WIFI_REASON_UNSPECIFIED=1. +static uint8_t wifi_sta_disconn_reason = 0; + +#if MICROPY_HW_ENABLE_MDNS_QUERIES || MICROPY_HW_ENABLE_MDNS_RESPONDER +// Whether mDNS has been initialised or not +static bool mdns_initialised = false; +#endif + +static uint8_t conf_wifi_sta_reconnects = 0; +static uint8_t wifi_sta_reconnects; + +// This function is called by the system-event task and so runs in a different +// thread to the main MicroPython task. It must not raise any Python exceptions. +void network_wlan_event_handler(system_event_t *event) { + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + ESP_LOGI("wifi", "STA_START"); + wifi_sta_reconnects = 0; + break; + case SYSTEM_EVENT_STA_CONNECTED: + ESP_LOGI("network", "CONNECTED"); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI("network", "GOT_IP"); + wifi_sta_connected = true; + wifi_sta_disconn_reason = 0; // Success so clear error. (in case of new error will be replaced anyway) + #if MICROPY_HW_ENABLE_MDNS_QUERIES || MICROPY_HW_ENABLE_MDNS_RESPONDER + if (!mdns_initialised) { + mdns_init(); + #if MICROPY_HW_ENABLE_MDNS_RESPONDER + const char *hostname = NULL; + if (tcpip_adapter_get_hostname(WIFI_IF_STA, &hostname) != ESP_OK || hostname == NULL) { + hostname = "esp32"; + } + mdns_hostname_set(hostname); + mdns_instance_name_set(hostname); + #endif + mdns_initialised = true; + } + #endif + break; + case SYSTEM_EVENT_STA_DISCONNECTED: { + // This is a workaround as ESP32 WiFi libs don't currently + // auto-reassociate. + system_event_sta_disconnected_t *disconn = &event->event_info.disconnected; + char *message = ""; + wifi_sta_disconn_reason = disconn->reason; + switch (disconn->reason) { + case WIFI_REASON_BEACON_TIMEOUT: + // AP has dropped out; try to reconnect. + message = "\nbeacon timeout"; + break; + case WIFI_REASON_NO_AP_FOUND: + // AP may not exist, or it may have momentarily dropped out; try to reconnect. + message = "\nno AP found"; + break; + case WIFI_REASON_AUTH_FAIL: + // Password may be wrong, or it just failed to connect; try to reconnect. + message = "\nauthentication failed"; + break; + default: + // Let other errors through and try to reconnect. + break; + } + ESP_LOGI("wifi", "STA_DISCONNECTED, reason:%d%s", disconn->reason, message); + + wifi_sta_connected = false; + if (wifi_sta_connect_requested) { + wifi_mode_t mode; + if (esp_wifi_get_mode(&mode) != ESP_OK) { + break; + } + if (!(mode & WIFI_MODE_STA)) { + break; + } + if (conf_wifi_sta_reconnects) { + ESP_LOGI("wifi", "reconnect counter=%d, max=%d", + wifi_sta_reconnects, conf_wifi_sta_reconnects); + if (++wifi_sta_reconnects >= conf_wifi_sta_reconnects) { + break; + } + } + esp_err_t e = esp_wifi_connect(); + if (e != ESP_OK) { + ESP_LOGI("wifi", "error attempting to reconnect: 0x%04x", e); + } + } + break; + } + default: + break; + } +} + +STATIC void require_if(mp_obj_t wlan_if, int if_no) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(wlan_if); + if (self->if_id != if_no) { + mp_raise_msg(&mp_type_OSError, if_no == WIFI_IF_STA ? MP_ERROR_TEXT("STA required") : MP_ERROR_TEXT("AP required")); + } +} + +STATIC mp_obj_t get_wlan(size_t n_args, const mp_obj_t *args) { + static int initialized = 0; + if (!initialized) { + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_LOGD("modnetwork", "Initializing WiFi"); + esp_exceptions(esp_wifi_init(&cfg)); + esp_exceptions(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_LOGD("modnetwork", "Initialized"); + initialized = 1; + } + + int idx = (n_args > 0) ? mp_obj_get_int(args[0]) : WIFI_IF_STA; + if (idx == WIFI_IF_STA) { + return MP_OBJ_FROM_PTR(&wlan_sta_obj); + } else if (idx == WIFI_IF_AP) { + return MP_OBJ_FROM_PTR(&wlan_ap_obj); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("invalid WLAN interface identifier")); + } +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(get_wlan_obj, 0, 1, get_wlan); + +STATIC mp_obj_t network_wlan_active(size_t n_args, const mp_obj_t *args) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + wifi_mode_t mode; + if (!wifi_started) { + mode = WIFI_MODE_NULL; + } else { + esp_exceptions(esp_wifi_get_mode(&mode)); + } + + int bit = (self->if_id == WIFI_IF_STA) ? WIFI_MODE_STA : WIFI_MODE_AP; + + if (n_args > 1) { + bool active = mp_obj_is_true(args[1]); + mode = active ? (mode | bit) : (mode & ~bit); + if (mode == WIFI_MODE_NULL) { + if (wifi_started) { + esp_exceptions(esp_wifi_stop()); + wifi_started = false; + } + } else { + esp_exceptions(esp_wifi_set_mode(mode)); + if (!wifi_started) { + esp_exceptions(esp_wifi_start()); + wifi_started = true; + } + } + } + + return (mode & bit) ? mp_const_true : mp_const_false; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_active_obj, 1, 2, network_wlan_active); + +STATIC mp_obj_t network_wlan_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_ssid, ARG_key, ARG_bssid }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_bssid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + wifi_config_t wifi_sta_config = {0}; + + // configure any parameters that are given + if (n_args > 1) { + size_t len; + const char *p; + if (args[ARG_ssid].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_ssid].u_obj, &len); + memcpy(wifi_sta_config.sta.ssid, p, MIN(len, sizeof(wifi_sta_config.sta.ssid))); + } + if (args[ARG_key].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_key].u_obj, &len); + memcpy(wifi_sta_config.sta.password, p, MIN(len, sizeof(wifi_sta_config.sta.password))); + } + if (args[ARG_bssid].u_obj != mp_const_none) { + p = mp_obj_str_get_data(args[ARG_bssid].u_obj, &len); + if (len != sizeof(wifi_sta_config.sta.bssid)) { + mp_raise_ValueError(NULL); + } + wifi_sta_config.sta.bssid_set = 1; + memcpy(wifi_sta_config.sta.bssid, p, sizeof(wifi_sta_config.sta.bssid)); + } + esp_exceptions(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config)); + } + + wifi_sta_reconnects = 0; + // connect to the WiFi AP + MP_THREAD_GIL_EXIT(); + esp_exceptions(esp_wifi_connect()); + MP_THREAD_GIL_ENTER(); + wifi_sta_connect_requested = true; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(network_wlan_connect_obj, 1, network_wlan_connect); + +STATIC mp_obj_t network_wlan_disconnect(mp_obj_t self_in) { + wifi_sta_connect_requested = false; + esp_exceptions(esp_wifi_disconnect()); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_disconnect_obj, network_wlan_disconnect); + +STATIC mp_obj_t network_wlan_status(size_t n_args, const mp_obj_t *args) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args == 1) { + if (self->if_id == WIFI_IF_STA) { + // Case of no arg is only for the STA interface + if (wifi_sta_connected) { + // Happy path, connected with IP + return MP_OBJ_NEW_SMALL_INT(STAT_GOT_IP); + } else if (wifi_sta_connect_requested + && (conf_wifi_sta_reconnects == 0 + || wifi_sta_reconnects < conf_wifi_sta_reconnects)) { + // No connection or error, but is requested = Still connecting + return MP_OBJ_NEW_SMALL_INT(STAT_CONNECTING); + } else if (wifi_sta_disconn_reason == 0) { + // No activity, No error = Idle + return MP_OBJ_NEW_SMALL_INT(STAT_IDLE); + } else { + // Simply pass the error through from ESP-identifier + return MP_OBJ_NEW_SMALL_INT(wifi_sta_disconn_reason); + } + } + return mp_const_none; + } + + // one argument: return status based on query parameter + switch ((uintptr_t)args[1]) { + case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_stations): { + // return list of connected stations, only if in soft-AP mode + require_if(args[0], WIFI_IF_AP); + wifi_sta_list_t station_list; + esp_exceptions(esp_wifi_ap_get_sta_list(&station_list)); + wifi_sta_info_t *stations = (wifi_sta_info_t *)station_list.sta; + mp_obj_t list = mp_obj_new_list(0, NULL); + for (int i = 0; i < station_list.num; ++i) { + mp_obj_tuple_t *t = mp_obj_new_tuple(1, NULL); + t->items[0] = mp_obj_new_bytes(stations[i].mac, sizeof(stations[i].mac)); + mp_obj_list_append(list, t); + } + return list; + } + case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_rssi): { + // return signal of AP, only in STA mode + require_if(args[0], WIFI_IF_STA); + + wifi_ap_record_t info; + esp_exceptions(esp_wifi_sta_get_ap_info(&info)); + return MP_OBJ_NEW_SMALL_INT(info.rssi); + } + default: + mp_raise_ValueError(MP_ERROR_TEXT("unknown status param")); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_status_obj, 1, 2, network_wlan_status); + +STATIC mp_obj_t network_wlan_scan(mp_obj_t self_in) { + // check that STA mode is active + wifi_mode_t mode; + esp_exceptions(esp_wifi_get_mode(&mode)); + if ((mode & WIFI_MODE_STA) == 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("STA must be active")); + } + + mp_obj_t list = mp_obj_new_list(0, NULL); + wifi_scan_config_t config = { 0 }; + config.show_hidden = true; + MP_THREAD_GIL_EXIT(); + esp_err_t status = esp_wifi_scan_start(&config, 1); + MP_THREAD_GIL_ENTER(); + if (status == 0) { + uint16_t count = 0; + esp_exceptions(esp_wifi_scan_get_ap_num(&count)); + if (count == 0) { + // esp_wifi_scan_get_ap_records must be called to free internal buffers from the scan. + // But it returns an error if wifi_ap_records==NULL. So allocate at least 1 AP entry. + // esp_wifi_scan_get_ap_records will then return the actual number of APs in count. + count = 1; + } + wifi_ap_record_t *wifi_ap_records = calloc(count, sizeof(wifi_ap_record_t)); + esp_exceptions(esp_wifi_scan_get_ap_records(&count, wifi_ap_records)); + for (uint16_t i = 0; i < count; i++) { + mp_obj_tuple_t *t = mp_obj_new_tuple(6, NULL); + uint8_t *x = memchr(wifi_ap_records[i].ssid, 0, sizeof(wifi_ap_records[i].ssid)); + int ssid_len = x ? x - wifi_ap_records[i].ssid : sizeof(wifi_ap_records[i].ssid); + t->items[0] = mp_obj_new_bytes(wifi_ap_records[i].ssid, ssid_len); + t->items[1] = mp_obj_new_bytes(wifi_ap_records[i].bssid, sizeof(wifi_ap_records[i].bssid)); + t->items[2] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].primary); + t->items[3] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].rssi); + t->items[4] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].authmode); + t->items[5] = mp_const_false; // XXX hidden? + mp_obj_list_append(list, MP_OBJ_FROM_PTR(t)); + } + free(wifi_ap_records); + } + return list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_scan_obj, network_wlan_scan); + +STATIC mp_obj_t network_wlan_isconnected(mp_obj_t self_in) { + wlan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->if_id == WIFI_IF_STA) { + return mp_obj_new_bool(wifi_sta_connected); + } else { + wifi_sta_list_t sta; + esp_wifi_ap_get_sta_list(&sta); + return mp_obj_new_bool(sta.num != 0); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_isconnected_obj, network_wlan_isconnected); + +STATIC mp_obj_t network_wlan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (n_args != 1 && kwargs->used != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); + } + + wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + bool is_wifi = self->if_id == WIFI_IF_AP || self->if_id == WIFI_IF_STA; + + wifi_config_t cfg; + if (is_wifi) { + esp_exceptions(esp_wifi_get_config(self->if_id, &cfg)); + } + + if (kwargs->used != 0) { + if (!is_wifi) { + goto unknown; + } + + for (size_t i = 0; i < kwargs->alloc; i++) { + if (mp_map_slot_is_filled(kwargs, i)) { + int req_if = -1; + + switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + case MP_QSTR_mac: { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(kwargs->table[i].value, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len != 6) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length")); + } + esp_exceptions(esp_wifi_set_mac(self->if_id, bufinfo.buf)); + break; + } + case MP_QSTR_ssid: + case MP_QSTR_essid: { + req_if = WIFI_IF_AP; + size_t len; + const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); + len = MIN(len, sizeof(cfg.ap.ssid)); + memcpy(cfg.ap.ssid, s, len); + cfg.ap.ssid_len = len; + break; + } + case MP_QSTR_hidden: { + req_if = WIFI_IF_AP; + cfg.ap.ssid_hidden = mp_obj_is_true(kwargs->table[i].value); + break; + } + case MP_QSTR_security: + case MP_QSTR_authmode: { + req_if = WIFI_IF_AP; + cfg.ap.authmode = mp_obj_get_int(kwargs->table[i].value); + break; + } + case MP_QSTR_key: + case MP_QSTR_password: { + req_if = WIFI_IF_AP; + size_t len; + const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); + len = MIN(len, sizeof(cfg.ap.password) - 1); + memcpy(cfg.ap.password, s, len); + cfg.ap.password[len] = 0; + break; + } + case MP_QSTR_channel: { + uint8_t primary; + wifi_second_chan_t secondary; + // Get the current value of secondary + esp_exceptions(esp_wifi_get_channel(&primary, &secondary)); + primary = mp_obj_get_int(kwargs->table[i].value); + esp_err_t err = esp_wifi_set_channel(primary, secondary); + if (err == ESP_ERR_INVALID_ARG) { + // May need to swap secondary channel above to below or below to above + secondary = ( + (secondary == WIFI_SECOND_CHAN_ABOVE) + ? WIFI_SECOND_CHAN_BELOW + : (secondary == WIFI_SECOND_CHAN_BELOW) + ? WIFI_SECOND_CHAN_ABOVE + : WIFI_SECOND_CHAN_NONE); + esp_exceptions(esp_wifi_set_channel(primary, secondary)); + } + break; + } + case MP_QSTR_hostname: + case MP_QSTR_dhcp_hostname: { + const char *s = mp_obj_str_get_str(kwargs->table[i].value); + esp_exceptions(tcpip_adapter_set_hostname(self->if_id, s)); + break; + } + case MP_QSTR_max_clients: { + req_if = WIFI_IF_AP; + cfg.ap.max_connection = mp_obj_get_int(kwargs->table[i].value); + break; + } + case MP_QSTR_reconnects: { + int reconnects = mp_obj_get_int(kwargs->table[i].value); + req_if = WIFI_IF_STA; + // parameter reconnects == -1 means to retry forever. + // here means conf_wifi_sta_reconnects == 0 to retry forever. + conf_wifi_sta_reconnects = (reconnects == -1) ? 0 : reconnects + 1; + break; + } + case MP_QSTR_txpower: { + int8_t power = (mp_obj_get_float(kwargs->table[i].value) * 4); + esp_exceptions(esp_wifi_set_max_tx_power(power)); + break; + } + case MP_QSTR_protocol: { + esp_exceptions(esp_wifi_set_protocol(self->if_id, mp_obj_get_int(kwargs->table[i].value))); + break; + } + default: + goto unknown; + } + + // We post-check interface requirements to save on code size + if (req_if >= 0) { + require_if(args[0], req_if); + } + } + } + + esp_exceptions(esp_wifi_set_config(self->if_id, &cfg)); + + return mp_const_none; + } + + // Get config + + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); + } + + int req_if = -1; + mp_obj_t val = mp_const_none; + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_mac: { + uint8_t mac[6]; + switch (self->if_id) { + case WIFI_IF_AP: // fallthrough intentional + case WIFI_IF_STA: + esp_exceptions(esp_wifi_get_mac(self->if_id, mac)); + return mp_obj_new_bytes(mac, sizeof(mac)); + default: + goto unknown; + } + } + case MP_QSTR_ssid: + case MP_QSTR_essid: + switch (self->if_id) { + case WIFI_IF_STA: + val = mp_obj_new_str((char *)cfg.sta.ssid, strlen((char *)cfg.sta.ssid)); + break; + case WIFI_IF_AP: + val = mp_obj_new_str((char *)cfg.ap.ssid, cfg.ap.ssid_len); + break; + default: + req_if = WIFI_IF_AP; + } + break; + case MP_QSTR_hidden: + req_if = WIFI_IF_AP; + val = mp_obj_new_bool(cfg.ap.ssid_hidden); + break; + case MP_QSTR_security: + case MP_QSTR_authmode: + req_if = WIFI_IF_AP; + val = MP_OBJ_NEW_SMALL_INT(cfg.ap.authmode); + break; + case MP_QSTR_channel: { + uint8_t channel; + wifi_second_chan_t second; + esp_exceptions(esp_wifi_get_channel(&channel, &second)); + val = MP_OBJ_NEW_SMALL_INT(channel); + break; + } + case MP_QSTR_hostname: + case MP_QSTR_dhcp_hostname: { + const char *s; + esp_exceptions(tcpip_adapter_get_hostname(self->if_id, &s)); + val = mp_obj_new_str(s, strlen(s)); + break; + } + case MP_QSTR_max_clients: { + val = MP_OBJ_NEW_SMALL_INT(cfg.ap.max_connection); + break; + } + case MP_QSTR_reconnects: + req_if = WIFI_IF_STA; + int rec = conf_wifi_sta_reconnects - 1; + val = MP_OBJ_NEW_SMALL_INT(rec); + break; + case MP_QSTR_txpower: { + int8_t power; + esp_exceptions(esp_wifi_get_max_tx_power(&power)); + val = mp_obj_new_float(power * 0.25); + break; + } + case MP_QSTR_protocol: { + uint8_t protocol_bitmap; + esp_exceptions(esp_wifi_get_protocol(self->if_id, &protocol_bitmap)); + val = MP_OBJ_NEW_SMALL_INT(protocol_bitmap); + break; + } + default: + goto unknown; + } + + // We post-check interface requirements to save on code size + if (req_if >= 0) { + require_if(args[0], req_if); + } + + return val; + +unknown: + mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); +} +MP_DEFINE_CONST_FUN_OBJ_KW(network_wlan_config_obj, 1, network_wlan_config); + +STATIC const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&network_wlan_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&network_wlan_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_wlan_disconnect_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_wlan_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_scan), MP_ROM_PTR(&network_wlan_scan_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_wlan_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_wlan_config_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_ifconfig_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + wlan_if_type, + MP_QSTR_WLAN, + MP_TYPE_FLAG_NONE, + locals_dict, &wlan_if_locals_dict + ); + +STATIC const wlan_if_obj_t wlan_sta_obj = {{&wlan_if_type}, WIFI_IF_STA}; +STATIC const wlan_if_obj_t wlan_ap_obj = {{&wlan_if_type}, WIFI_IF_AP}; + +#endif // MICROPY_PY_NETWORK_WLAN diff --git a/microros_ws/extra_packages/micropython/src/qstrdefsport.h b/microros_ws/extra_packages/micropython/src/qstrdefsport.h new file mode 100644 index 0000000..47e2238 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/qstrdefsport.h @@ -0,0 +1,31 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2014 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// qstrs specific to this port, only needed if they aren't auto-generated +// *FORMAT-OFF* + +// Entries for sys.path +Q(/lib) diff --git a/microros_ws/extra_packages/micropython/src/uart.c b/microros_ws/extra_packages/micropython/src/uart.c new file mode 100644 index 0000000..f6493dc --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/uart.c @@ -0,0 +1,104 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "driver/uart.h" +#include "soc/uart_periph.h" + +#include "py/runtime.h" +#include "py/mphal.h" +#include "uart.h" + +STATIC void uart_irq_handler(void *arg); + +void uart_stdout_init(void) { + uart_config_t uartcfg = { + .baud_rate = MICROPY_HW_UART_REPL_BAUD, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 0 + }; + uart_param_config(MICROPY_HW_UART_REPL, &uartcfg); + + const uint32_t rxbuf = 129; // IDF requires > 128 min + const uint32_t txbuf = 0; + + uart_driver_install(MICROPY_HW_UART_REPL, rxbuf, txbuf, 0, NULL, 0); + + uart_isr_handle_t handle; + uart_isr_free(MICROPY_HW_UART_REPL); + uart_isr_register(MICROPY_HW_UART_REPL, uart_irq_handler, NULL, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, &handle); + uart_enable_rx_intr(MICROPY_HW_UART_REPL); +} + +int uart_stdout_tx_strn(const char *str, size_t len) { + size_t remaining = len; + // TODO add a timeout + for (;;) { + int ret = uart_tx_chars(MICROPY_HW_UART_REPL, str, remaining); + if (ret == -1) { + return -1; + } + remaining -= ret; + if (remaining <= 0) { + break; + } + str += ret; + ulTaskNotifyTake(pdFALSE, 1); + } + return len - remaining; +} + +// all code executed in ISR must be in IRAM, and any const data must be in DRAM +STATIC void IRAM_ATTR uart_irq_handler(void *arg) { + volatile uart_dev_t *uart = &UART0; + #if CONFIG_IDF_TARGET_ESP32S3 + uart->int_clr.rxfifo_full_int_clr = 1; + uart->int_clr.rxfifo_tout_int_clr = 1; + #else + uart->int_clr.rxfifo_full = 1; + uart->int_clr.rxfifo_tout = 1; + uart->int_clr.frm_err = 1; + #endif + while (uart->status.rxfifo_cnt) { + #if CONFIG_IDF_TARGET_ESP32 + uint8_t c = uart->fifo.rw_byte; + #elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + uint8_t c = READ_PERI_REG(UART_FIFO_AHB_REG(0)); // UART0 + #endif + if (c == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + } else { + // this is an inline function so will be in IRAM + ringbuf_put(&stdin_ringbuf, c); + } + } +} diff --git a/microros_ws/extra_packages/micropython/src/uart.h b/microros_ws/extra_packages/micropython/src/uart.h new file mode 100644 index 0000000..e3c7482 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/uart.h @@ -0,0 +1,47 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * Development of the code in this file was sponsored by Microbric Pty Ltd + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP32_UART_H +#define MICROPY_INCLUDED_ESP32_UART_H + +// Whether to enable the REPL on a UART. +#ifndef MICROPY_HW_ENABLE_UART_REPL +#define MICROPY_HW_ENABLE_UART_REPL (!CONFIG_USB_ENABLED && !CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG) +#endif + +#ifndef MICROPY_HW_UART_REPL +#define MICROPY_HW_UART_REPL (UART_NUM_0) +#endif + +#ifndef MICROPY_HW_UART_REPL_BAUD +#define MICROPY_HW_UART_REPL_BAUD (115200) +#endif + +void uart_stdout_init(void); +int uart_stdout_tx_strn(const char *str, size_t len); + +#endif // MICROPY_INCLUDED_ESP32_UART_H diff --git a/microros_ws/extra_packages/micropython/src/usb.c b/microros_ws/extra_packages/micropython/src/usb.c new file mode 100644 index 0000000..80612cd --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/usb.c @@ -0,0 +1,91 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" +#include "usb.h" + +#if CONFIG_USB_ENABLED + +#include "tinyusb.h" +#include "tusb_cdc_acm.h" + +#define CDC_ITF TINYUSB_CDC_ACM_0 + +static uint8_t usb_rx_buf[CONFIG_USB_CDC_RX_BUFSIZE]; + +static void usb_callback_rx(int itf, cdcacm_event_t *event) { + // TODO: what happens if more chars come in during this function, are they lost? + for (;;) { + size_t len = 0; + esp_err_t ret = tinyusb_cdcacm_read(itf, usb_rx_buf, sizeof(usb_rx_buf), &len); + if (ret != ESP_OK) { + break; + } + if (len == 0) { + break; + } + for (size_t i = 0; i < len; ++i) { + if (usb_rx_buf[i] == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + } else { + ringbuf_put(&stdin_ringbuf, usb_rx_buf[i]); + } + } + } +} + +void usb_init(void) { + // Initialise the USB with defaults. + tinyusb_config_t tusb_cfg = {0}; + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + + // Initialise the USB serial interface. + tinyusb_config_cdcacm_t amc_cfg = { + .usb_dev = TINYUSB_USBDEV_0, + .cdc_port = CDC_ITF, + .rx_unread_buf_sz = 256, + .callback_rx = &usb_callback_rx, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL + }; + ESP_ERROR_CHECK(tusb_cdc_acm_init(&amc_cfg)); + +} + +void usb_tx_strn(const char *str, size_t len) { + // Write out the data to the CDC interface, but only while the USB host is connected. + uint64_t timeout = esp_timer_get_time() + (uint64_t)(MICROPY_HW_USB_CDC_TX_TIMEOUT * 1000); + while (tud_cdc_n_connected(CDC_ITF) && len && esp_timer_get_time() < timeout) { + size_t l = tinyusb_cdcacm_write_queue(CDC_ITF, (uint8_t *)str, len); + str += l; + len -= l; + tud_cdc_n_write_flush(CDC_ITF); + } +} + +#endif // CONFIG_USB_ENABLED diff --git a/microros_ws/extra_packages/micropython/src/usb.h b/microros_ws/extra_packages/micropython/src/usb.h new file mode 100644 index 0000000..009bf42 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/usb.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP32_USB_H +#define MICROPY_INCLUDED_ESP32_USB_H + +#define MICROPY_HW_USB_CDC_TX_TIMEOUT (500) + +void usb_init(void); +void usb_tx_strn(const char *str, size_t len); + +#endif // MICROPY_INCLUDED_ESP32_USB_H diff --git a/microros_ws/extra_packages/micropython/src/usb_serial_jtag.c b/microros_ws/extra_packages/micropython/src/usb_serial_jtag.c new file mode 100644 index 0000000..a7d06a3 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/usb_serial_jtag.c @@ -0,0 +1,98 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Patrick Van Oosterwijck + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" +#include "usb_serial_jtag.h" + +#if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG + +#include "hal/usb_serial_jtag_ll.h" +#include "esp_intr_alloc.h" +#include "soc/periph_defs.h" + +#define USB_SERIAL_JTAG_BUF_SIZE (64) + +static uint8_t rx_buf[USB_SERIAL_JTAG_BUF_SIZE]; +static volatile bool terminal_connected = false; + +static void usb_serial_jtag_isr_handler(void *arg) { + uint32_t flags = usb_serial_jtag_ll_get_intsts_mask(); + + if (flags & USB_SERIAL_JTAG_INTR_SOF) { + usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SOF); + } + + if (flags & USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT) { + usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); + size_t req_len = ringbuf_free(&stdin_ringbuf); + if (req_len > USB_SERIAL_JTAG_BUF_SIZE) { + req_len = USB_SERIAL_JTAG_BUF_SIZE; + } + size_t len = usb_serial_jtag_ll_read_rxfifo(rx_buf, req_len); + for (size_t i = 0; i < len; ++i) { + if (rx_buf[i] == mp_interrupt_char) { + mp_sched_keyboard_interrupt(); + } else { + ringbuf_put(&stdin_ringbuf, rx_buf[i]); + } + } + mp_hal_wake_main_task_from_isr(); + } +} + +void usb_serial_jtag_init(void) { + usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | + USB_SERIAL_JTAG_INTR_SOF); + usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | + USB_SERIAL_JTAG_INTR_SOF); + ESP_ERROR_CHECK(esp_intr_alloc(ETS_USB_SERIAL_JTAG_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, + usb_serial_jtag_isr_handler, NULL, NULL)); +} + +void usb_serial_jtag_tx_strn(const char *str, size_t len) { + while (len) { + size_t l = len; + if (l > USB_SERIAL_JTAG_PACKET_SZ_BYTES) { + l = USB_SERIAL_JTAG_PACKET_SZ_BYTES; + } + portTickType start_tick = xTaskGetTickCount(); + while (!usb_serial_jtag_ll_txfifo_writable()) { + portTickType now_tick = xTaskGetTickCount(); + if (!terminal_connected || now_tick > (start_tick + pdMS_TO_TICKS(200))) { + terminal_connected = false; + return; + } + } + terminal_connected = true; + l = usb_serial_jtag_ll_write_txfifo((const uint8_t *)str, l); + usb_serial_jtag_ll_txfifo_flush(); + str += l; + len -= l; + } +} + +#endif // CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG diff --git a/microros_ws/extra_packages/micropython/src/usb_serial_jtag.h b/microros_ws/extra_packages/micropython/src/usb_serial_jtag.h new file mode 100644 index 0000000..4eebb19 --- /dev/null +++ b/microros_ws/extra_packages/micropython/src/usb_serial_jtag.h @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Patrick Van Oosterwijck + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP32_USB_SERIAL_JTAG_H +#define MICROPY_INCLUDED_ESP32_USB_SERIAL_JTAG_H + +void usb_serial_jtag_init(void); +void usb_serial_jtag_tx_strn(const char *str, size_t len); + +#endif // MICROPY_INCLUDED_ESP32_USB_SERIAL_JTAG_H