├── .gitignore ├── .gitmodules ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .vscode ├── settings.json └── tasks.json ├── README.md ├── firmware ├── CMakeLists.txt ├── README.md ├── components │ └── wasm3 │ │ ├── CMakeLists.txt │ │ ├── linker.lf │ │ └── wasm3_extras.cpp ├── main │ ├── CMakeLists.txt │ ├── common.h │ ├── main.cpp │ ├── msc_flash.c │ ├── settings.cpp │ ├── status.c │ ├── storage.c │ ├── usb.c │ └── wasm.cpp ├── partitions.csv └── sdkconfig.defaults └── wasm ├── Makefile └── hello.c /.gitignore: -------------------------------------------------------------------------------- 1 | wasm/*.wasm 2 | firmware/build 3 | firmware/sdkconfig 4 | firmware/sdkconfig.old 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "firmware/components/wasm3/wasm3"] 2 | path = firmware/components/wasm3/wasm3 3 | url = https://github.com/wasm3/wasm3.git 4 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM espressif/idf:latest 2 | 3 | ARG EMSDK_VER=2.0.8 4 | 5 | RUN cd /opt \ 6 | && git clone -b ${EMSDK_VER} https://github.com/emscripten-core/emsdk.git \ 7 | && cd emsdk \ 8 | && ./emsdk install ${EMSDK_VER} \ 9 | && ./emsdk activate ${EMSDK_VER} \ 10 | && rm -rf zips 11 | 12 | ENV EMSDK_PATH=/opt/emsdk 13 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.startupEditor": "readme" 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build (firmware)", 8 | "type": "shell", 9 | "command": "source ${IDF_PATH}/export.sh 1>/dev/null 2>/dev/null && idf.py -C firmware build dfu", 10 | "problemMatcher": [] 11 | }, 12 | { 13 | "label": "Build (wasm)", 14 | "type": "shell", 15 | "command": "source ${EMSDK_PATH}/emsdk_env.sh 2>/dev/null && EM_CACHE=$HOME/.emcache emmake make -C wasm all", 16 | "problemMatcher": [], 17 | "group": { 18 | "kind": "build", 19 | "isDefault": true 20 | } 21 | }, 22 | { 23 | "label": "Clean (wasm)", 24 | "type": "shell", 25 | "command": "source ${EMSDK_PATH}/emsdk_env.sh 2>/dev/null && EM_CACHE=$HOME/.emcache emmake make -C wasm clean", 26 | "problemMatcher": [] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32-S2 WebAssembly Demo 2 | 3 | This project will demonstrate WebAssembly code running on ESP32-S2. There are two parts in this project: 4 | 5 | * ESP-IDF application for the ESP32-S2, inside [firmware](firmware/) directory. 6 | 7 | It contains [WASM3](https://github.com/wasm3/wasm3) WebAssembly interpreter, mass storage device emulation and some glue code to make everything work. 8 | 9 | * C application inside [wasm](wasm/) directory, which is compiled to WebAssembly using [Emscripten](https://emscripten.org/docs/introducing_emscripten/about_emscripten.html) compiler. 10 | 11 | ## Demo overview 12 | 13 | The demo has the following steps: 14 | 15 | 1. Set up ESP32-S2 hardware. 16 | 2. Build the firmware for the ESP32-S2 and flash it to the board. 17 | 3. A USB drive (mass storage device) emulated by ESP32-S2 will appear in the system. 18 | 4. Build the WebAssembly application. 19 | 5. Copy the WebAssembly application to USB drive. 20 | 6. Eject the USB drive. 21 | 7. The firmware will load and execute WebAssembly module, then go back to step 3. 22 | 23 | ## Getting started with Gitpod workspace 24 | 25 | To get started, create a project from this template by clicking "Use this template" on Github. 26 | 27 | Sign into [gitpod.io](https://gitpod.io/) and create a new workspace based on the project you have created. 28 | 29 | The workspace contains the project itself, ESP-IDF and Emscripten SDK. 30 | 31 | Inside the workspace, three _tasks_ are defined: 32 | 33 | * Build (firmware) — builds firmware (application for ESP32-S2) 34 | * Build (wasm) — builds WebAssmebly module 35 | * Clean (wasm) — cleans WebAssembly module output 36 | 37 | ## Step by step 38 | 39 | ### Step 1: set up hardware 40 | 41 | 1. Connect a USB cable to an ESP32-S2 development board: GPIO19: white, GPIO20: green, GND: black. Note: you need an ESP32-S2 development board with PSRAM! 42 | 2. Plug USB cable into the PC, while holding "BOOT" button. 43 | 3. Check Device Manager (Windows), lsusb (Linux), System Information (macOS) and verify that an ESP32-S2 device is in the list. 44 | 45 | ### Step 2: build and flash the firmware 46 | 47 | 1. Press F1, select "Tasks: Run task", choose "Build (firmware)" task. This will build the application for ESP32-S2. 48 | 2. DFU binary will be located in [firmware/build/dfu.bin](firmware/build/dfu.bin). Right-click this file and choose "Download". Save it somewhere on your computer. 49 | 3. Install dfu-util if you don't have it yet. On Linux and macOS it can be installed using the package managers. On Windows it is installed together with IDF. 50 | 4. Reboot the development board into download mode: press and hold Boot button, click Reset button, release Boot. 51 | 5. Flash the board with dfu-util: `dfu-util -D dfu.bin`. 52 | 6. Reset the board by pressing Reset button. 53 | 54 | ### Step 3: ESP32-S2 USB drive will appear in the system 55 | 56 | This USB mass storage device is emulated by the application flashed in the previous step. The volume name should be `NO NAME`. 57 | 58 | At the same time, LED on the ESP32-S2 board will turn blue. 59 | 60 | ### Step 4: build WebAssembly module 61 | 62 | Press F1, select "Tasks: Run task", choose "Build (wasm)" task. This will build the webassembly module. 63 | 64 | ### Step 5: copy WebAssembly module to ESP32-S2 USB drive 65 | 66 | Locate [wasm/hello.wasm](wasm/hello.wasm), right-click, choose "Download", select location in the root directory of the USB drive. 67 | 68 | ### Step 6: eject the USB drive 69 | 70 | Eject the USB device from the computer (using Explorer, Finder, etc.) 71 | 72 | ### Step 7: the firmware will execute the WebAssembly module 73 | 74 | The firmware will select the latest wasm file and try to run it. 75 | 76 | The LED will turn green when the execution starts. 77 | 78 | Output from WebAssembly module will go to UART console of the ESP32-S2. Use some serial monitor to see the output, for example `python3 -m serial.tools.miniterm /dev/ttyUSB0 115200`. The example program [wasm/hello.c](wasm/hello.c) will print "Hello world" to the console. 79 | 80 | When the module finishes executing, the USB mass storage device will appear in the system again, going back to step 3. 81 | 82 | Note, if the WebAssembly interpreter crashes and the chip resets, it will go into USB disk mode and let you upload a new program. 83 | 84 | ## Next steps 85 | 86 | Webassmebly module is located in [wasm/hello.c](wasm/hello.c). It can call functions exported from C by the firmware. The exported functions are defined in [firmware/main/wasm.cpp](firmware/main/wasm.cpp). See `delay_ms` function definition and `mod.link_optional` calls for an example. 87 | 88 | There are also _a few_ WASI functions defined in [m3_api_esp_wasi.c](firmware/components/wasm3/wasm3/platforms/embedded/esp32-idf-wasi/main/m3_api_esp_wasi.c). 89 | 90 | The development board features an LED. Can you make the LED blink or change colors from WebAssembly? 91 | 92 | There is a `void status_rgb(int r, int g, int b)` function that you can use, arguments `r`, `g`, `b` can be in [0, 255] range. 93 | 94 | You can also look at the list of WASI functions implemented in [m3_api_esp_wasi.c](firmware/components/wasm3/wasm3/platforms/embedded/esp32-idf-wasi/main/m3_api_esp_wasi.c) and try to implement some more. For example, can you make your WebAssembly module write to a file? On the ESP32-S2, the filesystem is mounted to the `/data` directory. 95 | 96 | -------------------------------------------------------------------------------- /firmware/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about build system see 2 | # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html 3 | # The following five lines of boilerplate have to be in your project's 4 | # CMakeLists in this exact order for cmake to work correctly 5 | cmake_minimum_required(VERSION 3.5) 6 | 7 | set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/led_strip) 8 | set(COMPONENTS main) 9 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 10 | project(wasm3-msc-demo) 11 | 12 | -------------------------------------------------------------------------------- /firmware/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrr/esp32s2-wasm-demo/b490ec246a625b253da75d991ae9d2952044e03d/firmware/README.md -------------------------------------------------------------------------------- /firmware/components/wasm3/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(APP_SOURCES "wasm3_extras.cpp") 2 | 3 | idf_component_register(SRCS ${APP_SOURCES} 4 | LDFRAGMENTS linker.lf) 5 | 6 | idf_build_get_property(build_dir BUILD_DIR) 7 | add_subdirectory(wasm3/source) 8 | add_subdirectory(wasm3/platforms/cpp/wasm3_cpp) 9 | 10 | target_sources(${COMPONENT_TARGET} PRIVATE wasm3/platforms/embedded/esp32-idf-wasi/main/m3_api_esp_wasi.c) 11 | target_include_directories(${COMPONENT_TARGET} PUBLIC wasm3/platforms/embedded/esp32-idf-wasi/main) 12 | 13 | target_link_libraries(${COMPONENT_TARGET} PUBLIC m3 wasm3_cpp) 14 | 15 | target_compile_options(m3 PUBLIC -DM3_IN_IRAM -DESP32 -O3 -freorder-blocks) 16 | -------------------------------------------------------------------------------- /firmware/components/wasm3/linker.lf: -------------------------------------------------------------------------------- 1 | [mapping:wasm3] 2 | archive: libm3.a 3 | entries: 4 | m3_core (noflash_text) 5 | m3_exec (noflash_text) 6 | m3_compile (noflash_text) 7 | -------------------------------------------------------------------------------- /firmware/components/wasm3/wasm3_extras.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrr/esp32s2-wasm-demo/b490ec246a625b253da75d991ae9d2952044e03d/firmware/components/wasm3/wasm3_extras.cpp -------------------------------------------------------------------------------- /firmware/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "main.cpp" "usb.c" "msc_flash.c" "storage.c" "settings.cpp" "status.c" "wasm.cpp" 2 | INCLUDE_DIRS "." 3 | REQUIRES wasm3 usb tinyusb wear_levelling fatfs vfs led_strip) 4 | 5 | idf_component_get_property(tinyusb tinyusb COMPONENT_LIB) 6 | target_link_libraries(${COMPONENT_LIB} INTERFACE $ $) 7 | -------------------------------------------------------------------------------- /firmware/main/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "esp_err.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define LED_GPIO 18 12 | 13 | esp_err_t storage_init_wl(void); 14 | esp_err_t storage_mount_fat(const char* base_path); 15 | size_t storage_get_size(void); 16 | size_t storage_get_sector_size(void); 17 | esp_err_t storage_unmount_fat(void); 18 | esp_err_t storage_read_sector(size_t addr, size_t size, void* dest); 19 | esp_err_t storage_write_sector(size_t addr, size_t size, const void* src); 20 | 21 | void status_init(void); 22 | void status_red(void); 23 | void status_green(void); 24 | void status_blue(void); 25 | void status_rgb(int r, int g, int b); 26 | 27 | typedef struct { 28 | size_t wasm_task_stack_size; 29 | size_t wasm_env_stack_size; 30 | } wasm_example_settings_t; 31 | 32 | esp_err_t settings_load(const char* filename, wasm_example_settings_t* out_settings); 33 | 34 | void msc_allow_mount(bool allow); 35 | void msc_on_eject(void); 36 | 37 | void usb_init(void); 38 | 39 | void wasm_run(const char* wasm_file_name, size_t wasm_task_stack_size, size_t wasm_env_stack_size); 40 | 41 | #ifdef __cplusplus 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /firmware/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "esp_log.h" 10 | #include "esp_heap_caps.h" 11 | #include "freertos/FreeRTOS.h" 12 | #include "freertos/task.h" 13 | #include "common.h" 14 | 15 | static const char* TAG = "main"; 16 | #define BASE_PATH "/data" 17 | 18 | static std::string get_latest_wasm_file(void); 19 | static void create_readme_file(void); 20 | static void alloc_failed_hook(size_t size, uint32_t caps, const char * function_name); 21 | static wasm_example_settings_t s_settings; 22 | void msc_on_eject(void); 23 | static void run_latest_wasm(void); 24 | static void set_running_flag(void); 25 | static void clear_running_flag(void); 26 | static bool is_running_flag_set(void); 27 | static TaskHandle_t s_main_task_handle; 28 | 29 | 30 | extern "C" void app_main(void) 31 | { 32 | s_main_task_handle = xTaskGetCurrentTaskHandle(); 33 | heap_caps_register_failed_alloc_callback(&alloc_failed_hook); 34 | status_init(); 35 | status_red(); 36 | 37 | ESP_LOGI(TAG, "Initializing USB..."); 38 | msc_allow_mount(false); 39 | usb_init(); 40 | 41 | ESP_LOGI(TAG, "Initializing filesystem..."); 42 | ESP_ERROR_CHECK( storage_init_wl() ); 43 | ESP_ERROR_CHECK( storage_mount_fat(BASE_PATH) ); 44 | status_green(); 45 | 46 | create_readme_file(); 47 | ESP_LOGI(TAG, "Loading settings..."); 48 | ESP_ERROR_CHECK( settings_load(BASE_PATH "/settings.txt", &s_settings) ); 49 | 50 | while (true) { 51 | if (is_running_flag_set()) { 52 | ESP_LOGI(TAG, "WASM didn't finish last time, skipping..."); 53 | clear_running_flag(); 54 | } else { 55 | ESP_LOGI(TAG, "Running WASM..."); 56 | set_running_flag(); 57 | run_latest_wasm(); 58 | clear_running_flag(); 59 | } 60 | 61 | ESP_LOGI(TAG, "Unmounting filesystem..."); 62 | ESP_ERROR_CHECK( storage_unmount_fat() ); 63 | 64 | status_blue(); 65 | ESP_LOGI(TAG, "Waiting for USB..."); 66 | msc_allow_mount(true); 67 | uint32_t notify_val = 0; 68 | xTaskNotifyWait(0, 1, ¬ify_val, portMAX_DELAY); 69 | 70 | status_green(); 71 | ESP_LOGI(TAG, "Mounting filesystem..."); 72 | ESP_ERROR_CHECK( storage_mount_fat(BASE_PATH) ); 73 | } 74 | } 75 | 76 | void msc_on_eject(void) 77 | { 78 | ESP_LOGI(TAG, "USB eject callback called"); 79 | xTaskNotifyGive(s_main_task_handle); 80 | } 81 | 82 | static void create_readme_file(void) 83 | { 84 | const char* readme_txt_name = BASE_PATH "/README.MD"; 85 | FILE* readme_txt = fopen(readme_txt_name, "r"); 86 | if (readme_txt == NULL) { 87 | ESP_LOGW(TAG, "README.MD doesn't exist yet, creating"); 88 | readme_txt = fopen(readme_txt_name, "w"); 89 | fprintf(readme_txt, "ESP32-S2 WASM3 demo\n"); 90 | fprintf(readme_txt, "-------------------\n\n"); 91 | fprintf(readme_txt, "You can save .wasm files to this mass storage device.\n"); 92 | fprintf(readme_txt, "Eject this drive after saving the file.\n"); 93 | fprintf(readme_txt, "The latest wasm file will be executed.\n"); 94 | fclose(readme_txt); 95 | } 96 | } 97 | 98 | static void run_latest_wasm(void) 99 | { 100 | std::string wasm_file = get_latest_wasm_file(); 101 | if (!wasm_file.size()) { 102 | ESP_LOGW(TAG, "Nothing to execute"); 103 | return; 104 | } 105 | ESP_LOGI(TAG, "Running %s", wasm_file.c_str()); 106 | wasm_run(wasm_file.c_str(), s_settings.wasm_task_stack_size, s_settings.wasm_env_stack_size); 107 | } 108 | 109 | 110 | std::string get_latest_wasm_file(void) 111 | { 112 | ESP_LOGI(TAG, "Files list:"); 113 | time_t latest_mtime = 0; 114 | std::string latest_mtime_file; 115 | DIR* dir = opendir(BASE_PATH); 116 | while (true) { 117 | struct dirent* de = readdir(dir); 118 | if (!de) { 119 | break; 120 | } 121 | 122 | struct stat st = {}; 123 | char full_name[512]; 124 | snprintf(full_name, sizeof(full_name), BASE_PATH "/%s", de->d_name); 125 | stat(full_name, &st); 126 | time_t mtime = st.st_mtime; 127 | struct tm mtm; 128 | localtime_r(&mtime, &mtm); 129 | bool is_wasm = false; 130 | FILE* f = fopen(full_name, "rb"); 131 | if (f) { 132 | char hdr[4]; 133 | const char hdr_expected[] = {0x00, 0x61, 0x73, 0x6d}; 134 | if (fread(hdr, 1, 4, f) == 4 && memcmp(hdr, hdr_expected, 4) == 0) { 135 | is_wasm = true; 136 | } 137 | fclose(f); 138 | } 139 | char* str_time = asctime(&mtm); 140 | str_time[strlen(str_time) - 1] = 0; 141 | ESP_LOGI(TAG, "File: %s mtime: %s%s", full_name, str_time, is_wasm?" [WASM]":""); 142 | if (is_wasm && mtime > latest_mtime) { 143 | latest_mtime = mtime; 144 | latest_mtime_file = full_name; 145 | } 146 | } 147 | closedir(dir); 148 | return latest_mtime_file; 149 | } 150 | 151 | static void alloc_failed_hook(size_t size, uint32_t caps, const char * function_name) 152 | { 153 | ESP_LOGE(TAG, "Failed to allocate %d bytes (%s). Available %d bytes.", size, function_name, heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); 154 | } 155 | 156 | static void set_running_flag(void) 157 | { 158 | FILE* running = fopen(BASE_PATH "/.running", "w"); 159 | fclose(running); 160 | } 161 | 162 | static void clear_running_flag(void) 163 | { 164 | unlink(BASE_PATH "/.running"); 165 | } 166 | 167 | static bool is_running_flag_set(void) 168 | { 169 | FILE* running = fopen(BASE_PATH "/.running", "r"); 170 | fclose(running); 171 | return running != NULL; 172 | } 173 | -------------------------------------------------------------------------------- /firmware/main/msc_flash.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 Espressif Systems (Shanghai) CO LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | // The mass storage class creates a mountable USB device into which files can be dropped. 17 | // The access to the underlying block device is provided by functions in storage.c. 18 | // The module contains the following callbacks from the tinyusb USB stack. 19 | // - tud_msc_inquiry_cb - returns string identifiers about the device. 20 | // - tud_msc_test_unit_ready_cb - return the availability of the device. It is available in the beginning and while it 21 | // is mounted. It becomes unavailable after ejecting the device. 22 | // - tud_msc_capacity_cb - returns the device capacity. 23 | // - tud_msc_start_stop_cb - handles disc ejection. 24 | // - tud_msc_scsi_cb - desired actions to SCSI disc commands can be handler there. 25 | // - tud_msc_read10_cb - invoked in order to read from the disc. 26 | // - tud_msc_write10_cb - invoked in order to write the disc. 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "esp_err.h" 33 | #include "esp_log.h" 34 | #include "common.h" 35 | #include "tusb.h" 36 | 37 | static const char *TAG = "msc"; 38 | static bool s_allow_mount = true; 39 | 40 | #define CONFIG_BRIDGE_MSC_VOLUME_LABEL "WASM3" 41 | 42 | void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) 43 | { 44 | (void) lun; 45 | 46 | const char vid[8] = "ESP"; 47 | const char pid[16] = "Flash Storage"; 48 | const char rev[4] = "0.1"; 49 | 50 | ESP_LOGI(TAG, "tud_msc_inquiry_cb() invoked"); 51 | 52 | memcpy(vendor_id, vid, strlen(vid)); 53 | memcpy(product_id, pid, strlen(pid)); 54 | memcpy(product_rev, rev, strlen(rev)); 55 | } 56 | 57 | bool tud_msc_test_unit_ready_cb(uint8_t lun) 58 | { 59 | (void) lun; 60 | 61 | ESP_LOGD(TAG, "tud_msc_test_unit_ready_cb() invoked"); 62 | 63 | if (!s_allow_mount) { 64 | tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); 65 | return false; 66 | } 67 | 68 | return true; 69 | } 70 | 71 | void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) 72 | { 73 | (void) lun; 74 | 75 | ESP_LOGI(TAG, "tud_msc_capacity_cb() invoked"); 76 | size_t size = storage_get_size(); 77 | size_t sec_size = storage_get_sector_size(); 78 | *block_count = size / sec_size; 79 | *block_size = sec_size; 80 | } 81 | 82 | bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) 83 | { 84 | (void) lun; 85 | (void) power_condition; 86 | 87 | ESP_LOGI(TAG, "tud_msc_start_stop_cb() invoked, power_condition=%d, start=%d, load_eject=%d", power_condition, 88 | start, load_eject); 89 | 90 | if (load_eject) { 91 | if (start) { 92 | ESP_LOGI(TAG, "MSC START"); 93 | } else { 94 | ESP_LOGI(TAG, "MSC EJECT"); 95 | s_allow_mount = false; 96 | msc_on_eject(); 97 | } 98 | } 99 | 100 | return true; 101 | } 102 | 103 | int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) 104 | { 105 | ESP_LOGD(TAG, "tud_msc_read10_cb() invoked, lun=%d, lba=%d, offset=%d, bufsize=%d", lun, lba, offset, bufsize); 106 | 107 | size_t addr = lba * storage_get_sector_size() + offset; 108 | esp_err_t err = storage_read_sector(addr, bufsize, buffer); 109 | if (err != ESP_OK) { 110 | ESP_LOGE(TAG, "storage_read_sector failed: 0x%x", err); 111 | return 0; 112 | } 113 | return bufsize; 114 | } 115 | 116 | int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) 117 | { 118 | ESP_LOGD(TAG, "tud_msc_write10_cb() invoked, lun=%d, lba=%d, offset=%d", lun, lba, offset); 119 | 120 | size_t addr = lba * storage_get_sector_size() + offset; 121 | esp_err_t err = storage_write_sector(addr, bufsize, buffer); 122 | if (err != ESP_OK) { 123 | ESP_LOGE(TAG, "storage_write_sector failed: 0x%x", err); 124 | return 0; 125 | } 126 | return bufsize; 127 | 128 | } 129 | 130 | int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) 131 | { 132 | int32_t ret; 133 | 134 | ESP_LOGD(TAG, "tud_msc_scsi_cb() invoked. bufsize=%d", bufsize); 135 | ESP_LOG_BUFFER_HEXDUMP("scsi_cmd", scsi_cmd, 16, ESP_LOG_DEBUG); 136 | 137 | switch (scsi_cmd[0]) { 138 | case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: 139 | ESP_LOGI(TAG, "tud_msc_scsi_cb() invoked: SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL"); 140 | ret = 0; 141 | break; 142 | 143 | default: 144 | ESP_LOGW(TAG, "tud_msc_scsi_cb() invoked: %d", scsi_cmd[0]); 145 | 146 | tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); 147 | 148 | ret = -1; 149 | break; 150 | } 151 | 152 | return ret; 153 | } 154 | 155 | void msc_allow_mount(bool allow) 156 | { 157 | s_allow_mount = allow; 158 | } 159 | -------------------------------------------------------------------------------- /firmware/main/settings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "common.h" 4 | 5 | static void handle_settings_line(wasm_example_settings_t* settings, const char* first, const char* second); 6 | static void create_default_settings_file(const char* filename, const wasm_example_settings_t* settings); 7 | 8 | esp_err_t settings_load(const char* filename, wasm_example_settings_t* out_settings) 9 | { 10 | *out_settings = {}; 11 | out_settings->wasm_task_stack_size = 32 * 1024; 12 | out_settings->wasm_env_stack_size = 8 * 1024; 13 | 14 | FILE* f = fopen(filename, "r"); 15 | if (f == NULL) { 16 | create_default_settings_file(filename, out_settings); 17 | return ESP_OK; 18 | } 19 | 20 | char line[100]; 21 | while (fgets(line, sizeof(line), f) != NULL) { 22 | if (line[0] == '#') { 23 | continue; 24 | } 25 | char* eq_pos = strchr(line, '='); 26 | if (eq_pos == NULL) { 27 | continue; 28 | } 29 | *eq_pos = 0; 30 | const char* first = line; 31 | const char* second = eq_pos + 1; 32 | handle_settings_line(out_settings, first, second); 33 | } 34 | fclose(f); 35 | return ESP_OK; 36 | } 37 | 38 | static void handle_settings_line(wasm_example_settings_t* settings, const char* first, const char* second) 39 | { 40 | if (strcmp(first, "wasm_task_stack_size") == 0) { 41 | settings->wasm_task_stack_size = (size_t) strtol(second, NULL, 0); 42 | } else if (strcmp(first, "wasm_env_stack_size") == 0) { 43 | settings->wasm_env_stack_size = (size_t) strtol(second, NULL, 0); 44 | } 45 | } 46 | 47 | static void create_default_settings_file(const char* filename, const wasm_example_settings_t* settings) 48 | { 49 | FILE* f = fopen(filename, "w"); 50 | fprintf(f, "# stack size for wasm task\nwasm_task_stack_size=%d\n", settings->wasm_task_stack_size); 51 | fprintf(f, "# wasm interpreter stack size\nwasm_env_stack_size=%d\n", settings->wasm_env_stack_size); 52 | fclose(f); 53 | } 54 | -------------------------------------------------------------------------------- /firmware/main/status.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "common.h" 3 | #include "led_strip.h" 4 | 5 | static led_strip_t *s_led; 6 | 7 | 8 | void status_init(void) 9 | { 10 | s_led = led_strip_init(0, LED_GPIO, 1); 11 | s_led->clear(s_led, 50); 12 | } 13 | 14 | void status_red(void) 15 | { 16 | s_led->set_pixel(s_led, 0, 16, 0, 0); 17 | s_led->refresh(s_led, 100); 18 | } 19 | 20 | void status_green(void) 21 | { 22 | s_led->set_pixel(s_led, 0, 0, 16, 0); 23 | s_led->refresh(s_led, 100); 24 | } 25 | 26 | void status_blue(void) 27 | { 28 | s_led->set_pixel(s_led, 0, 0, 0, 16); 29 | s_led->refresh(s_led, 100); 30 | } 31 | 32 | void status_rgb(int r, int g, int b) 33 | { 34 | #define CLAMP(x) MIN(255, MAX(0, x)) 35 | s_led->set_pixel(s_led, CLAMP(r), CLAMP(g), CLAMP(b), 16); 36 | s_led->refresh(s_led, 100); 37 | } 38 | -------------------------------------------------------------------------------- /firmware/main/storage.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "esp_log.h" 6 | #include "esp_vfs.h" 7 | #include "esp_vfs_fat.h" 8 | #include "diskio_impl.h" 9 | #include "diskio_wl.h" 10 | #include "wear_levelling.h" 11 | 12 | 13 | static wl_handle_t s_wl_handle = WL_INVALID_HANDLE; 14 | static bool s_fat_mounted; 15 | static const char* s_base_path; 16 | 17 | static const char* TAG = "storage"; 18 | 19 | esp_err_t storage_init_wl(void) 20 | { 21 | ESP_LOGI(TAG, "Initializing wear levelling"); 22 | esp_err_t err; 23 | 24 | const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL); 25 | if (data_partition == NULL) { 26 | ESP_LOGE(TAG, "Failed to find FATFS partition. Check the partition table."); 27 | return ESP_ERR_NOT_FOUND; 28 | } 29 | 30 | err = wl_mount(data_partition, &s_wl_handle); 31 | if (err != ESP_OK) { 32 | ESP_LOGE(TAG, "failed to mount wear levelling layer (0x%x)", err); 33 | return err; 34 | } 35 | 36 | return ESP_OK; 37 | } 38 | 39 | static inline size_t esp_vfs_fat_get_allocation_unit_size( 40 | size_t sector_size, size_t requested_size) 41 | { 42 | size_t alloc_unit_size = requested_size; 43 | const size_t max_sectors_per_cylinder = 128; 44 | const size_t max_size = sector_size * max_sectors_per_cylinder; 45 | alloc_unit_size = MAX(alloc_unit_size, sector_size); 46 | alloc_unit_size = MIN(alloc_unit_size, max_size); 47 | return alloc_unit_size; 48 | } 49 | 50 | esp_err_t storage_mount_fat(const char* base_path) 51 | { 52 | const size_t workbuf_size = 4096; 53 | void *workbuf = NULL; 54 | esp_err_t err; 55 | 56 | if (s_fat_mounted) { 57 | return ESP_OK; 58 | } 59 | 60 | ESP_LOGI(TAG, "Initializing FAT"); 61 | 62 | // connect driver to FATFS 63 | BYTE pdrv = 0xFF; 64 | if (ff_diskio_get_drive(&pdrv) != ESP_OK) { 65 | ESP_LOGE(TAG, "the maximum count of volumes is already mounted"); 66 | return ESP_ERR_NO_MEM; 67 | } 68 | ESP_LOGD(TAG, "using pdrv=%i", pdrv); 69 | char drv[3] = {(char)('0' + pdrv), ':', 0}; 70 | 71 | err = ff_diskio_register_wl_partition(pdrv, s_wl_handle); 72 | if (err!= ESP_OK) { 73 | ESP_LOGE(TAG, "ff_diskio_register_wl_partition failed pdrv=%d (0x%x)", pdrv, err); 74 | goto fail; 75 | } 76 | FATFS *fs; 77 | err = esp_vfs_fat_register(base_path, drv, 2, &fs); 78 | if (err == ESP_ERR_INVALID_STATE) { 79 | // it's okay, already registered with VFS 80 | } else if (err != ESP_OK) { 81 | ESP_LOGE(TAG, "esp_vfs_fat_register failed (0x%x)", err); 82 | goto fail; 83 | } 84 | 85 | // Try to mount partition 86 | FRESULT fresult = f_mount(fs, drv, 1); 87 | if (fresult != FR_OK) { 88 | ESP_LOGW(TAG, "f_mount failed (%d)", fresult); 89 | if (!((fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR))) { 90 | err = ESP_FAIL; 91 | goto fail; 92 | } 93 | workbuf = ff_memalloc(workbuf_size); 94 | if (workbuf == NULL) { 95 | err = ESP_ERR_NO_MEM; 96 | goto fail; 97 | } 98 | size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size( 99 | CONFIG_WL_SECTOR_SIZE, 100 | 4096); 101 | 102 | ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size); 103 | fresult = f_mkfs(drv, FM_FAT, alloc_unit_size, workbuf, workbuf_size); 104 | if (fresult != FR_OK) { 105 | err = ESP_FAIL; 106 | ESP_LOGE(TAG, "f_mkfs failed (%d)", fresult); 107 | goto fail; 108 | } 109 | free(workbuf); 110 | workbuf = NULL; 111 | ESP_LOGI(TAG, "Mounting again"); 112 | fresult = f_mount(fs, drv, 0); 113 | if (fresult != FR_OK) { 114 | err = ESP_FAIL; 115 | ESP_LOGE(TAG, "f_mount failed after formatting (%d)", fresult); 116 | goto fail; 117 | } 118 | } 119 | s_fat_mounted = true; 120 | s_base_path = base_path; 121 | 122 | return ESP_OK; 123 | 124 | fail: 125 | free(workbuf); 126 | esp_vfs_fat_unregister_path(base_path); 127 | ff_diskio_unregister(pdrv); 128 | s_fat_mounted = false; 129 | ESP_LOGW(TAG, "Failed to mount storage (0x%x)", err); 130 | return err; 131 | } 132 | 133 | esp_err_t storage_unmount_fat(void) 134 | { 135 | if (!s_fat_mounted) { 136 | return ESP_OK; 137 | } 138 | 139 | BYTE pdrv = ff_diskio_get_pdrv_wl(s_wl_handle); 140 | if (pdrv == 0xff) { 141 | return ESP_ERR_INVALID_STATE; 142 | } 143 | char drv[3] = {(char)('0' + pdrv), ':', 0}; 144 | 145 | f_mount(0, drv, 0); 146 | ff_diskio_unregister(pdrv); 147 | ff_diskio_clear_pdrv_wl(s_wl_handle); 148 | esp_err_t err = esp_vfs_fat_unregister_path(s_base_path); 149 | s_base_path = NULL; 150 | s_fat_mounted = false; 151 | 152 | return err; 153 | 154 | } 155 | 156 | size_t storage_get_size(void) 157 | { 158 | assert(s_wl_handle != WL_INVALID_HANDLE); 159 | 160 | return wl_size(s_wl_handle); 161 | } 162 | 163 | size_t storage_get_sector_size(void) 164 | { 165 | assert(s_wl_handle != WL_INVALID_HANDLE); 166 | 167 | return wl_sector_size(s_wl_handle); 168 | } 169 | 170 | esp_err_t storage_read_sector(size_t addr, size_t size, void* dest) 171 | { 172 | assert(s_wl_handle != WL_INVALID_HANDLE); 173 | 174 | return wl_read(s_wl_handle, addr, dest, size); 175 | } 176 | 177 | esp_err_t storage_write_sector(size_t addr, size_t size, const void* src) 178 | { 179 | assert(s_wl_handle != WL_INVALID_HANDLE); 180 | 181 | if (s_fat_mounted) { 182 | ESP_LOGE(TAG, "can't write, FAT mounted"); 183 | return ESP_ERR_INVALID_STATE; 184 | } 185 | size_t sector_size = wl_sector_size(s_wl_handle); 186 | if (addr % sector_size != 0 || size % sector_size != 0) { 187 | return ESP_ERR_INVALID_ARG; 188 | } 189 | esp_err_t err = wl_erase_range(s_wl_handle, addr, size); 190 | if (err != ESP_OK) { 191 | ESP_LOGE(TAG, "wl_erase_range failed (0x%x)", err); 192 | return err; 193 | } 194 | return wl_write(s_wl_handle, addr, src, size); 195 | } 196 | 197 | -------------------------------------------------------------------------------- /firmware/main/usb.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "esp_log.h" 4 | #include "esp_system.h" 5 | #if __has_include("esp_private/periph_ctrl.h") 6 | #include "esp_private/periph_ctrl.h" 7 | #else 8 | #include "driver/periph_ctrl.h" 9 | #endif 10 | #include "driver/gpio.h" 11 | #include "soc/usb_periph.h" 12 | #include "hal/usb_hal.h" 13 | #include "esp32s2/rom/gpio.h" 14 | #include "freertos/FreeRTOS.h" 15 | #include "freertos/task.h" 16 | #include "tusb.h" 17 | #include "sdkconfig.h" 18 | 19 | static const char *TAG = "usb"; 20 | 21 | #define EPNUM_MSC 3 22 | #define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN) 23 | 24 | enum { 25 | ITF_NUM_MSC = 0, 26 | ITF_NUM_TOTAL 27 | }; 28 | 29 | #define MAC_BYTES 6 30 | 31 | static char serial_descriptor[MAC_BYTES * 2 + 1] = {'\0'}; // 2 chars per hexnumber + '\0' 32 | static uint16_t s_desc_str[32]; 33 | 34 | 35 | static tusb_desc_device_t descriptor_config = { 36 | .bLength = sizeof(descriptor_config), 37 | .bDescriptorType = TUSB_DESC_DEVICE, 38 | .bcdUSB = 0x0200, 39 | .bDeviceClass = TUSB_CLASS_MISC, 40 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 41 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 42 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 43 | .idVendor = 0x303A, 44 | .idProduct = 0x4002, 45 | .bcdDevice = 0x100, 46 | .iManufacturer = 0x01, 47 | .iProduct = 0x02, 48 | .iSerialNumber = 0x03, 49 | .bNumConfigurations = 0x01 50 | }; 51 | 52 | static uint8_t const desc_configuration[] = { 53 | // config number, interface count, string index, total length, attribute, power in mA 54 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), 55 | // Interface number, string index, EP Out & EP In address, EP size 56 | TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 4, EPNUM_MSC, 0x80 | EPNUM_MSC, 64), 57 | }; 58 | 59 | 60 | static char const *string_desc_arr[] = { 61 | (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) 62 | "Espressif", // 1: Manufacturer 63 | "WASM3", // 2: Product 64 | serial_descriptor, // 3: Serials 65 | "MSC", 66 | }; 67 | 68 | 69 | uint8_t const *tud_descriptor_configuration_cb(uint8_t index) 70 | { 71 | return desc_configuration; 72 | } 73 | 74 | uint8_t const *tud_descriptor_device_cb(void) 75 | { 76 | return (uint8_t const *) &descriptor_config; 77 | } 78 | 79 | static void init_serial_no() 80 | { 81 | uint8_t m[MAC_BYTES] = {0}; 82 | esp_err_t ret = esp_efuse_mac_get_default(m); 83 | 84 | if (ret != ESP_OK) { 85 | ESP_LOGD(TAG, "Cannot read MAC address and set the device serial number"); 86 | abort(); 87 | } 88 | 89 | snprintf(serial_descriptor, sizeof(serial_descriptor), 90 | "%02X%02X%02X%02X%02X%02X", m[0], m[1], m[2], m[3], m[4], m[5]); 91 | } 92 | 93 | uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) 94 | { 95 | uint8_t chr_count; 96 | 97 | if (index == 0) { 98 | memcpy(&s_desc_str[1], string_desc_arr[0], 2); 99 | chr_count = 1; 100 | } else { 101 | // Convert ASCII string into UTF-16 102 | 103 | if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) { 104 | return NULL; 105 | } 106 | 107 | const char *str = string_desc_arr[index]; 108 | 109 | // Cap at max char 110 | chr_count = strlen(str); 111 | if (chr_count > 31) { 112 | chr_count = 31; 113 | } 114 | 115 | for (uint8_t i = 0; i < chr_count; i++) { 116 | s_desc_str[1 + i] = str[i]; 117 | } 118 | } 119 | 120 | // first byte is length (including header), second byte is string type 121 | s_desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2 * chr_count + 2); 122 | 123 | return s_desc_str; 124 | } 125 | 126 | static void configure_pins(usb_hal_context_t *usb) 127 | { 128 | /* usb_periph_iopins currently configures USB_OTG as USB Device. 129 | * Introduce additional parameters in usb_hal_context_t when adding support 130 | * for USB Host. 131 | */ 132 | for (const usb_iopin_dsc_t *iopin = usb_periph_iopins; iopin->pin != -1; ++iopin) { 133 | if ((usb->use_external_phy) || (iopin->ext_phy_only == 0)) { 134 | gpio_pad_select_gpio(iopin->pin); 135 | if (iopin->is_output) { 136 | gpio_matrix_out(iopin->pin, iopin->func, false, false); 137 | } else { 138 | gpio_matrix_in(iopin->pin, iopin->func, false); 139 | gpio_pad_input_enable(iopin->pin); 140 | } 141 | gpio_pad_unhold(iopin->pin); 142 | } 143 | } 144 | if (!usb->use_external_phy) { 145 | gpio_set_drive_capability(USBPHY_DM_NUM, GPIO_DRIVE_CAP_3); 146 | gpio_set_drive_capability(USBPHY_DP_NUM, GPIO_DRIVE_CAP_3); 147 | } 148 | } 149 | 150 | static void tusb_device_task(void *pvParameters) 151 | { 152 | while (1) { 153 | tud_task(); 154 | } 155 | vTaskDelete(NULL); 156 | } 157 | 158 | 159 | void usb_init(void) 160 | { 161 | init_serial_no(); 162 | 163 | periph_module_reset(PERIPH_USB_MODULE); 164 | periph_module_enable(PERIPH_USB_MODULE); 165 | 166 | usb_hal_context_t hal = { 167 | .use_external_phy = false 168 | }; 169 | usb_hal_init(&hal); 170 | configure_pins(&hal); 171 | 172 | tusb_init(); 173 | 174 | xTaskCreate(tusb_device_task, "tusb_device_task", 4 * 1024, NULL, 5, NULL); 175 | } 176 | -------------------------------------------------------------------------------- /firmware/main/wasm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "freertos/FreeRTOS.h" 6 | #include "freertos/task.h" 7 | 8 | #include "wasm3.h" 9 | #include "wasm3_cpp.h" 10 | #include "m3_api_esp_wasi.h" 11 | #include "common.h" 12 | 13 | /********************************************************************************/ 14 | /***** You can define additional functions to be linked to the module here *****/ 15 | 16 | static void delay_ms(int ms) 17 | { 18 | usleep(ms * 1000); 19 | } 20 | 21 | static void wasm_ext_init(wasm3::module &mod) 22 | { 23 | /* link additional functions defined in this file */ 24 | mod.link_optional("*", "delay_ms", delay_ms); 25 | } 26 | 27 | /********************************************************************************/ 28 | 29 | 30 | static std::string s_wasm_file_name; 31 | static size_t s_wasm_env_stack_size = 8 * 1024; 32 | 33 | class wasi_module: public wasm3::module 34 | { 35 | public: 36 | void link_wasi() { 37 | m3_LinkEspWASI(m_module.get()); 38 | } 39 | }; 40 | 41 | static void wasm_task(void* arg) 42 | { 43 | std::cout << "Loading wasm file " << s_wasm_file_name.c_str() << std::endl; 44 | 45 | try { 46 | wasm3::environment env; 47 | wasm3::runtime runtime = env.new_runtime(s_wasm_env_stack_size); 48 | std::ifstream wasm_file(s_wasm_file_name.c_str(), std::ios::binary | std::ios::in); 49 | if (!wasm_file.is_open()) { 50 | throw std::runtime_error("Failed to open wasm file"); 51 | } 52 | 53 | wasm3::module mod = env.parse_module(wasm_file); 54 | runtime.load(mod); 55 | ((wasi_module*) &mod)->link_wasi(); /* hack, this should be upstreamed to wasm3_cpp.h */ 56 | wasm_ext_init(mod); 57 | 58 | wasm3::function start_fn = runtime.find_function("_start"); 59 | start_fn.call(); 60 | } 61 | catch(std::runtime_error &e) { 62 | std::cerr << "WASM3 error: " << e.what() << std::endl; 63 | } 64 | 65 | vTaskDelete(NULL); 66 | } 67 | 68 | extern "C" void wasm_run(const char* wasm_file_name, size_t wasm_task_stack_size, size_t wasm_env_stack_size) 69 | { 70 | s_wasm_file_name = wasm_file_name; 71 | s_wasm_env_stack_size = wasm_env_stack_size; 72 | xTaskCreate(wasm_task, "wasm_task", wasm_task_stack_size, NULL, 2, NULL); 73 | } 74 | -------------------------------------------------------------------------------- /firmware/partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap 3 | nvs, data, nvs, 0x9000, 0x6000, 4 | phy_init, data, phy, 0xf000, 0x1000, 5 | factory, app, factory, 0x10000, 1M, 6 | storage, data, fat, , 1M, 7 | -------------------------------------------------------------------------------- /firmware/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32s2" 2 | CONFIG_COMPILER_CXX_EXCEPTIONS=y 3 | 4 | # Increased stack size 5 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=32768 6 | 7 | # Increase CPU frequency 8 | CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y 9 | CONFIG_TINYUSB=y 10 | CONFIG_TINYUSB_MSC_ENABLED=y 11 | 12 | CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n 13 | 14 | CONFIG_PARTITION_TABLE_CUSTOM=y 15 | CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" 16 | CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" 17 | CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y 18 | CONFIG_WL_SECTOR_SIZE_512=y 19 | CONFIG_WL_SECTOR_MODE_PERF=y 20 | 21 | CONFIG_FATFS_LFN_HEAP=y 22 | CONFIG_FATFS_API_ENCODING_UTF_8=y 23 | CONFIG_ESP32S2_SPIRAM_SUPPORT=y 24 | CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y 25 | CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=64 26 | -------------------------------------------------------------------------------- /wasm/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS := -s WARN_ON_UNDEFINED_SYMBOLS=0 -Os -g -s INITIAL_MEMORY=65536 -s TOTAL_STACK=8192 2 | PROG := hello.wasm 3 | 4 | all: $(PROG) 5 | $(PROG): hello.c 6 | $(CC) $(CFLAGS) $(EXPORTED_RUNTIME_METHODS_ARG) -o $@ $< 7 | $(PROG): Makefile 8 | clean: 9 | rm -f $(PROG) 10 | .PHONY: all clean 11 | -------------------------------------------------------------------------------- /wasm/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern void delay_ms(int ms); 4 | 5 | int main(void) 6 | { 7 | puts("Hello world\n"); 8 | delay_ms(1000); 9 | puts("Webassembly done\n"); 10 | return 0; 11 | } 12 | --------------------------------------------------------------------------------