diff --git a/ci/build_all_projects.sh b/ci/build_all_projects.sh index 4fb432c..26647c0 100755 --- a/ci/build_all_projects.sh +++ b/ci/build_all_projects.sh @@ -17,7 +17,7 @@ PROJECTS=( gpio_interrupts image_part jpeg_demo jpeg_webserver_demo mjpeg_streaming_wifi mobilenet_raw parse_meta people_tracker script_node_communication spatial_image_detections spatial_location_calculator speed_benchmark spi_in_landmark spi_in_passthrough two_streams - uart_echo + uart_echo ota_test ) for project in "${PROJECTS[@]}"; do diff --git a/flash_test/CMakeLists.txt b/flash_test/CMakeLists.txt new file mode 100644 index 0000000..74582a8 --- /dev/null +++ b/flash_test/CMakeLists.txt @@ -0,0 +1,11 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +set(EXTRA_COMPONENT_DIRS + ../components +) + +project(spi-slave-sender) diff --git a/flash_test/Makefile b/flash_test/Makefile new file mode 100644 index 0000000..e4af518 --- /dev/null +++ b/flash_test/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := spi-slave-sender + +include $(IDF_PATH)/make/project.mk + diff --git a/flash_test/main/CMakeLists.txt b/flash_test/main/CMakeLists.txt new file mode 100644 index 0000000..5780aea --- /dev/null +++ b/flash_test/main/CMakeLists.txt @@ -0,0 +1,10 @@ +# Create library +set(SOURCES + app_main.cpp +) + +set(INCLUDE_DIRS + . +) + +idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ${INCLUDE_DIRS} REQUIRES depthai-spi-api) diff --git a/flash_test/main/app_main.cpp b/flash_test/main/app_main.cpp new file mode 100644 index 0000000..7c77150 --- /dev/null +++ b/flash_test/main/app_main.cpp @@ -0,0 +1,134 @@ +#include +#include +#include + +#include +#include + +#include + +#include "esp_flash.h" +#include "esp_flash_spi_init.h" +#include "esp_partition.h" +#include "esp_vfs.h" +#include "esp_system.h" + +static const char *TAG = "example"; + +extern "C" { + void app_main(); +} + +static esp_flash_t* example_init_ext_flash(void) +{ + const spi_bus_config_t bus_config = { + .mosi_io_num = VSPI_IOMUX_PIN_NUM_MOSI, + .miso_io_num = VSPI_IOMUX_PIN_NUM_MISO, + .sclk_io_num = VSPI_IOMUX_PIN_NUM_CLK, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + }; + + const esp_flash_spi_device_config_t device_config = { + .host_id = VSPI_HOST, + .cs_io_num = VSPI_IOMUX_PIN_NUM_CS, + .io_mode = SPI_FLASH_DIO, + .speed = ESP_FLASH_40MHZ, + .input_delay_ns = 0, + .cs_id = 0 + }; + + ESP_LOGI(TAG, "Initializing external SPI Flash"); + ESP_LOGI(TAG, "Pin assignments:"); + ESP_LOGI(TAG, "MOSI: %2d MISO: %2d SCLK: %2d CS: %2d", + bus_config.mosi_io_num, bus_config.miso_io_num, + bus_config.sclk_io_num, device_config.cs_io_num + ); + + // Initialize the SPI bus + ESP_ERROR_CHECK(spi_bus_initialize(VSPI_HOST, &bus_config, 1)); + + // Add device to the SPI bus + esp_flash_t* ext_flash; + ESP_ERROR_CHECK(spi_bus_add_flash_device(&ext_flash, &device_config)); + + // Probe the Flash chip and initialize it + esp_err_t err = esp_flash_init(ext_flash); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize external Flash: %s (0x%x)", esp_err_to_name(err), err); + return NULL; + } + + // Print out the ID and size + uint32_t id; + ESP_ERROR_CHECK(esp_flash_read_id(ext_flash, &id)); + + // esp_flash_init doesn't calculate chip size for MT25QU01GBBB correctly so we'll manually set it here. + if(id==0x20bb21){ + ext_flash->size = 1024*1024*1024; + } + ESP_LOGI(TAG, "Initialized external Flash, size=%d KB, ID=0x%x", ext_flash->size / 1024, id); + + return ext_flash; +} + + +void readTest(esp_flash_t* flash, uint32_t readAddr, uint32_t readLen){ + // read test... + char *readBuf = (char *) malloc(readLen); + ESP_ERROR_CHECK(esp_flash_read(flash, readBuf, readAddr, readLen)); + + int entriesPerLine = 8; + for(int i=0; i>12). + uint32_t eraseLen = 4096 * ((writeLen-1)>>12) + 4096; + ESP_ERROR_CHECK(esp_flash_erase_region(flash, writeAddr, eraseLen)); + + // write test... + char *writeBuf = (char *) malloc(writeLen); + for(int i=0; i + 1. WIFI SSID: WIFI network to which your PC is also connected to. + 2. WIFI Password: WIFI password + +* In order to test the OTA demo : + 1. compile and burn the firmware `idf.py -p PORT flash` + 2. run `idf.py -p PORT monitor` and note down the IP assigned to your ESP module. The default port is 80 + 3. post something to the server: + `curl -X POST --data-binary @pipeline.dap http:///upload_dap/` + or + `curl -X POST --data-binary @bootlaoder.bin http:///upload_bl/` + +Note: An example of generating a real dap file can be found in the standalone-jpeg example: +https://github.com/luxonis/depthai-experiments/tree/master/gen2-spi/standalone-jpeg + +Just pass in "save" as the first argument. +`python3 main.py save` diff --git a/ota_test/main/CMakeLists.txt b/ota_test/main/CMakeLists.txt new file mode 100644 index 0000000..409178c --- /dev/null +++ b/ota_test/main/CMakeLists.txt @@ -0,0 +1,21 @@ +# Create library +set(SOURCES + main.cpp + OtaServer.cpp + FlashMT25Q.cpp +) + +set(INCLUDE_DIRS + . + $ENV{IDF_PATH}/examples/common_components/protocol_examples_common/include +) + + +set(REQUIRES + esp_http_server + depthai-spi-api + protocol_examples_common + spiffs +) + +idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ${INCLUDE_DIRS} REQUIRES ${REQUIRES} PRIV_REQUIRES ${PRIV_REQUIRES} EMBED_FILES "favicon.ico" "upload_script.html") diff --git a/ota_test/main/FlashMT25Q.cpp b/ota_test/main/FlashMT25Q.cpp new file mode 100644 index 0000000..6942571 --- /dev/null +++ b/ota_test/main/FlashMT25Q.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +#include + +#include +#include + +#include + +static const char *TAG = "FlashMT25Q"; +#define PIPELINE_OFFSET 1024*1024 + +FlashMT25Q::~FlashMT25Q(){ +} + +FlashMT25Q::FlashMT25Q(){ + const spi_bus_config_t bus_config = { + .mosi_io_num = VSPI_IOMUX_PIN_NUM_MOSI, + .miso_io_num = VSPI_IOMUX_PIN_NUM_MISO, + .sclk_io_num = VSPI_IOMUX_PIN_NUM_CLK, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + }; + + const esp_flash_spi_device_config_t device_config = { + .host_id = VSPI_HOST, + .cs_io_num = VSPI_IOMUX_PIN_NUM_CS, + .io_mode = SPI_FLASH_DIO, + .speed = ESP_FLASH_40MHZ, + .input_delay_ns = 0, + .cs_id = 0, + }; + + ESP_LOGI(TAG, "Initializing external SPI Flash"); + ESP_LOGI(TAG, "Pin assignments: "); + ESP_LOGI(TAG, "MOSI: %2d MISO: %2d SCLK: %2d CS: %2d", + bus_config.mosi_io_num, bus_config.miso_io_num, + bus_config.sclk_io_num, device_config.cs_io_num + ); + + // Initialize the SPI bus + ESP_ERROR_CHECK(spi_bus_initialize(VSPI_HOST, &bus_config, 1)); + + // Add device to the SPI bus + ESP_ERROR_CHECK(spi_bus_add_flash_device(&ext_flash, &device_config)); + + // Probe the Flash chip and initialize it + esp_err_t err = esp_flash_init(ext_flash); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize external Flash: %s (0x%x)", esp_err_to_name(err), err); + return; + } + + // Print out the ID and size + uint32_t id; + ESP_ERROR_CHECK(esp_flash_read_id(ext_flash, &id)); + + // esp_flash_init doesn't calculate chip size for MT25QU01GBBB correctly so we'll manually set it here. + if(id==0x20bb21){ + ext_flash->size = 1024*1024*1024; + } + ESP_LOGI(TAG, "Initialized external Flash, size=%d KB, ID=0x%x", ext_flash->size / 1024, id); +} + +void FlashMT25Q::read(char* readBuf, uint32_t readAddr, uint32_t readLen){ + ESP_ERROR_CHECK(esp_flash_read(ext_flash, readBuf, readAddr, readLen)); +} + +void FlashMT25Q::write(char* writeBuf, uint32_t writeAddr, uint32_t writeLen){ + ESP_ERROR_CHECK(esp_flash_write(ext_flash, writeBuf, writeAddr, writeLen)); +} + +void FlashMT25Q::eraseRegion(uint32_t eraseAddr, uint32_t eraseLen){ + // disable write protect. + esp_flash_set_chip_write_protect(ext_flash, false); + + // erase first... adjust erase length based off writeLen. for MT25QU01GBBB flash we have 4k sectors ((writeLen-1)>>12). + uint32_t eraseLenPadded = 4096 * ((eraseLen-1)>>12) + 4096; + ESP_ERROR_CHECK(esp_flash_erase_region(ext_flash, eraseAddr, eraseLenPadded)); +} + + +void FlashMT25Q::readTest(uint32_t readAddr, uint32_t readLen){ + // read test... + char *readBuf = (char *) malloc(readLen); + ESP_ERROR_CHECK(esp_flash_read(ext_flash, readBuf, readAddr, readLen)); + + int entriesPerLine = 8; + for(int i=0; i>12). + uint32_t eraseLen = 4096 * ((writeLen-1)>>12) + 4096; + ESP_ERROR_CHECK(esp_flash_erase_region(ext_flash, writeAddr, eraseLen)); + + // write test... + char *writeBuf = (char *) malloc(writeLen); + for(int i=0; i +#include +#include +#include +#include +#include + +#include "esp_err.h" +#include "esp_log.h" +#include "esp_vfs.h" +#include "esp_spiffs.h" +#include "esp_http_server.h" + +#include +#include "FlashMT25Q.hpp" + +/* Scratch buffer size */ +#define SCRATCH_BUFSIZE 8192 + +#define BL_FLASH_START 0 +#define DAP_FLASH_START 1024*1024 + +struct ota_server_data { + /* Scratch buffer for temporary storage during file transfer */ + char scratch[SCRATCH_BUFSIZE]; +}; + +static const char *TAG = "OtaServer"; +static FlashMT25Q myExtFlash; + +// When flashing we want to hold down the reset pin of the MyriadX to avoid confilct reading shared NOR flash. +static void initMXReset(){ + ESP_ERROR_CHECK(gpio_reset_pin(GPIO_NUM_33)); + ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_33, GPIO_MODE_INPUT_OUTPUT)); + ESP_ERROR_CHECK(gpio_set_pull_mode(GPIO_NUM_33, GPIO_PULLUP_PULLDOWN)); + ESP_ERROR_CHECK(gpio_set_drive_capability(GPIO_NUM_33, GPIO_DRIVE_CAP_3)); + ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_33, 1)); +} + +static void setMXReset(bool val){ + ESP_ERROR_CHECK(gpio_set_level(GPIO_NUM_33, !val)); +} + +static esp_err_t receive_and_write(httpd_req_t *req, uint32_t writeOffset){ + ESP_LOGI(TAG, "Receiving file..."); + + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *buf = ((struct ota_server_data *)req->user_ctx)->scratch; + int received; + + /* Content length of the request gives + * the size of the file being uploaded */ + int remaining = req->content_len; + + //----------------------------------------------------------------------------------------------------- + // hold the MX in reset. need sleep? + setMXReset(true); + + // erase the region we'll be writing to. + myExtFlash.eraseRegion(writeOffset, req->content_len); + ESP_LOGI(TAG, "Region erased! %d to %d", DAP_FLASH_START, DAP_FLASH_START+req->content_len); + //----------------------------------------------------------------------------------------------------- + while (remaining > 0) { + + ESP_LOGI(TAG, "Remaining size : %d", remaining); + /* Receive the file part by part into a buffer */ + if ((received = httpd_req_recv(req, buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) { + if (received == HTTPD_SOCK_ERR_TIMEOUT) { + /* Retry if timeout occurred */ + continue; + } + + ESP_LOGE(TAG, "File reception failed!"); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file"); + return ESP_FAIL; + } + + /* Write buffer content to file on storage */ + //----------------------------------------------------------------------------------------------------- + myExtFlash.write(buf, writeOffset, received); + writeOffset += received; + //----------------------------------------------------------------------------------------------------- + + /* Keep track of remaining size of + * the file left to be uploaded */ + remaining -= received; + } + + //----------------------------------------------------------------------------------------------------- + // release reset on MX. + myExtFlash.readTest(DAP_FLASH_START, 100); + setMXReset(false); + //----------------------------------------------------------------------------------------------------- + + /* Redirect onto root to see the updated file list */ + httpd_resp_set_status(req, "303 See Other"); + httpd_resp_set_hdr(req, "Location", "/"); + httpd_resp_sendstr(req, "File uploaded successfully"); + return ESP_OK; +} + +/* Handler to upload a ota onto the server */ +static esp_err_t upload_bl_post_handler(httpd_req_t *req) +{ + return receive_and_write(req, BL_FLASH_START); +} + +/* Handler to upload a ota onto the server */ +static esp_err_t upload_dap_post_handler(httpd_req_t *req) +{ + return receive_and_write(req, DAP_FLASH_START); +} + +static esp_err_t redirect(httpd_req_t *req) +{ + httpd_resp_set_status(req, "307 Temporary Redirect"); + httpd_resp_set_hdr(req, "Location", "/"); + httpd_resp_send(req, NULL, 0); // Response body can be empty + return ESP_OK; +} + +/* Handler to respond with an icon file embedded in flash. + * Browsers expect to GET website icon at URI /favicon.ico. + * This can be overridden by uploading file with same name */ +static esp_err_t get_favicon(httpd_req_t *req) +{ + extern const unsigned char favicon_ico_start[] asm("_binary_favicon_ico_start"); + extern const unsigned char favicon_ico_end[] asm("_binary_favicon_ico_end"); + const size_t favicon_ico_size = (favicon_ico_end - favicon_ico_start); + httpd_resp_set_type(req, "image/x-icon"); + httpd_resp_send(req, (const char *)favicon_ico_start, favicon_ico_size); + return ESP_OK; +} + +// Get upload_script.html +static esp_err_t get_html(httpd_req_t *req) +{ + /* Get handle to embedded file upload script */ + extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start"); + extern const unsigned char upload_script_end[] asm("_binary_upload_script_html_end"); + const size_t upload_script_size = (upload_script_end - upload_script_start); + + httpd_resp_send_chunk(req, (const char *)upload_script_start, upload_script_size); + + /* Send empty chunk to signal HTTP response completion */ + httpd_resp_sendstr_chunk(req, NULL); + return ESP_OK; +} + + +/* Function to start the ota server */ +esp_err_t start_ota_server() +{ + // initialize gpio for holding MX in reset + initMXReset(); + + static struct ota_server_data *server_data = NULL; + + if (server_data) { + ESP_LOGE(TAG, "OTA server already started"); + return ESP_ERR_INVALID_STATE; + } + + /* Allocate memory for server data */ + server_data = (ota_server_data*) calloc(1, sizeof(struct ota_server_data)); + if (!server_data) { + ESP_LOGE(TAG, "Failed to allocate memory for server data"); + return ESP_ERR_NO_MEM; + } + + httpd_handle_t server = NULL; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + + /* Use the URI wildcard matching function in order to + * allow the same handler to respond to multiple different + * target URIs which match the wildcard scheme */ + config.uri_match_fn = httpd_uri_match_wildcard; + + ESP_LOGI(TAG, "Starting HTTP Server"); + if (httpd_start(&server, &config) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start OTA server!"); + return ESP_FAIL; + } + + /* URI handler for uploading pipeline to server */ + httpd_uri_t dap_upload = { + .uri = "/upload_dap", + .method = HTTP_POST, + .handler = upload_dap_post_handler, + .user_ctx = server_data // Pass server data as context + }; + httpd_register_uri_handler(server, &dap_upload); + + /* URI handler for uploading bootloader to server */ + httpd_uri_t bl_upload = { + .uri = "/upload_bl", + .method = HTTP_POST, + .handler = upload_bl_post_handler, + .user_ctx = server_data // Pass server data as context + }; + httpd_register_uri_handler(server, &bl_upload); + + /* URI handler for getting HTML */ + httpd_uri_t favicon = { + .uri = "/favicon.ico", + .method = HTTP_GET, + .handler = get_favicon, + .user_ctx = server_data + }; + httpd_register_uri_handler(server, &favicon); + + /* URI handler for getting HTML */ + httpd_uri_t html = { + .uri = "/", + .method = HTTP_GET, + .handler = get_html, + .user_ctx = server_data + }; + httpd_register_uri_handler(server, &html); + + httpd_uri_t redirect_handler = { + .uri = "/*", + .method = HTTP_GET, + .handler = redirect, + .user_ctx = server_data + }; + httpd_register_uri_handler(server, &redirect_handler); + + return ESP_OK; +} diff --git a/ota_test/main/favicon.ico b/ota_test/main/favicon.ico new file mode 100644 index 0000000..971b594 Binary files /dev/null and b/ota_test/main/favicon.ico differ diff --git a/ota_test/main/main.cpp b/ota_test/main/main.cpp new file mode 100644 index 0000000..798e277 --- /dev/null +++ b/ota_test/main/main.cpp @@ -0,0 +1,43 @@ +/* HTTP File Server Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include + +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_spiffs.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "protocol_examples_common.h" + +extern "C" { + void app_main(); +} + +static const char *TAG="example"; + +esp_err_t start_ota_server(); + +void app_main(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + /* Start the file server */ + ESP_ERROR_CHECK(start_ota_server()); +} diff --git a/ota_test/main/upload_script.html b/ota_test/main/upload_script.html new file mode 100644 index 0000000..24536d9 --- /dev/null +++ b/ota_test/main/upload_script.html @@ -0,0 +1,67 @@ + + + +

Upload bootloader or pipeline to ESP32

+ + + + + + \ No newline at end of file diff --git a/ota_test/partitions_example.csv b/ota_test/partitions_example.csv new file mode 100644 index 0000000..0eebc19 --- /dev/null +++ b/ota_test/partitions_example.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, spiffs, , 0xF0000, diff --git a/ota_test/sdkconfig.defaults b/ota_test/sdkconfig.defaults new file mode 100644 index 0000000..3030693 --- /dev/null +++ b/ota_test/sdkconfig.defaults @@ -0,0 +1,4 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024