From 3bc3cd44209af6b6a914b090271b0a82ff87dc0e Mon Sep 17 00:00:00 2001 From: Renze Nicolai Date: Sun, 5 Jun 2022 02:28:13 +0200 Subject: [PATCH 1/7] Webusb mode --- main/main.c | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/main/main.c b/main/main.c index 3c01c49..83e3cf5 100644 --- a/main/main.c +++ b/main/main.c @@ -49,6 +49,7 @@ #include "menus/start.h" #include "factory_test.h" +#include "fpga_download.h" extern const uint8_t wallpaper_png_start[] asm("_binary_wallpaper_png_start"); extern const uint8_t wallpaper_png_end[] asm("_binary_wallpaper_png_end"); @@ -235,12 +236,40 @@ void app_main(void) { /* Start WiFi */ wifi_init(); - /* Rick that roll */ - play_bootsound(); + /* Check WebUSB mode */ + + uint8_t webusb_mode; + res = rp2040_get_webusb_mode(rp2040, &webusb_mode); + if (res != ESP_OK) { + ESP_LOGE(TAG, "Failed to read WebUSB mode: %d", res); + display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "Failed to read WebUSB mode", NULL, NULL); + esp_restart(); + } + + ESP_LOGI(TAG, "WebUSB mode 0x%02X", webusb_mode); + + if (webusb_mode == 0x00) { // Normal boot + /* Rick that roll */ + play_bootsound(); - /* Launcher menu */ - while (true) { - menu_start(rp2040->queue, pax_buffer, ili9341, app_description->version); + /* Launcher menu */ + while (true) { + menu_start(rp2040->queue, pax_buffer, ili9341, app_description->version); + } + } else if (webusb_mode == 0x01) { + display_boot_screen(pax_buffer, ili9341, "WebUSB mode"); + /*while (true) { + + }*/ + } else if (webusb_mode == 0x02) { + display_boot_screen(pax_buffer, ili9341, "FPGA download mode"); + while (true) { + fpga_download(rp2040->queue, get_ice40(), pax_buffer, ili9341); + } + } else { + char buffer[64]; + snprintf(buffer, sizeof(buffer), "Invalid mode 0x%02X", webusb_mode); + display_boot_screen(pax_buffer, ili9341, buffer); } free(framebuffer); From 2e05968be79aabe56dde52069c3d09fd7c7fbfa3 Mon Sep 17 00:00:00 2001 From: Renze Nicolai Date: Sun, 5 Jun 2022 02:29:32 +0200 Subject: [PATCH 2/7] Update RP2040 submodule --- components/mch2022-rp2040 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/mch2022-rp2040 b/components/mch2022-rp2040 index 2b4cbce..3c859b5 160000 --- a/components/mch2022-rp2040 +++ b/components/mch2022-rp2040 @@ -1 +1 @@ -Subproject commit 2b4cbce95827f6da3841584b8f6e80930f269f21 +Subproject commit 3c859b52f3f8cc3b3969d6a6d57a12f22781f291 From 57a9b0c32e3ff7dba891e0518af24afd2f6034c7 Mon Sep 17 00:00:00 2001 From: Renze Nicolai Date: Mon, 6 Jun 2022 01:07:58 +0200 Subject: [PATCH 3/7] Add webusb mode --- main/CMakeLists.txt | 1 + main/include/webusb.h | 8 +++ main/main.c | 11 ++-- main/webusb.c | 117 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 main/include/webusb.h create mode 100644 main/webusb.c diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index a381bbb..fd2f49b 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -25,6 +25,7 @@ idf_component_register( "animation.c" "button_test.c" "adc_test.c" + "webusb.c" INCLUDE_DIRS "." "include" "menus" diff --git a/main/include/webusb.h b/main/include/webusb.h new file mode 100644 index 0000000..2cd234c --- /dev/null +++ b/main/include/webusb.h @@ -0,0 +1,8 @@ +#pragma once + +#include "ice40.h" +#include "pax_gfx.h" +#include "ili9341.h" +#include + +void webusb_main(xQueueHandle buttonQueue, pax_buf_t* pax_buffer, ILI9341* ili9341); diff --git a/main/main.c b/main/main.c index 83e3cf5..51c9644 100644 --- a/main/main.c +++ b/main/main.c @@ -50,6 +50,7 @@ #include "factory_test.h" #include "fpga_download.h" +#include "webusb.h" extern const uint8_t wallpaper_png_start[] asm("_binary_wallpaper_png_start"); extern const uint8_t wallpaper_png_end[] asm("_binary_wallpaper_png_end"); @@ -73,6 +74,8 @@ void display_fatal_error(pax_buf_t* pax_buffer, ILI9341* ili9341, const char* li void app_main(void) { esp_err_t res; + audio_init(); + const esp_app_desc_t *app_description = esp_ota_get_app_description(); ESP_LOGI(TAG, "App version: %s", app_description->version); //ESP_LOGI(TAG, "Project name: %s", app_description->project_name); @@ -116,8 +119,6 @@ void app_main(void) { display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "NVS failed to initialize", "Flash may be corrupted", NULL); esp_restart(); } - - audio_init(); display_boot_screen(pax_buffer, ili9341, "Starting..."); @@ -258,9 +259,9 @@ void app_main(void) { } } else if (webusb_mode == 0x01) { display_boot_screen(pax_buffer, ili9341, "WebUSB mode"); - /*while (true) { - - }*/ + while (true) { + webusb_main(rp2040->queue, pax_buffer, ili9341); + } } else if (webusb_mode == 0x02) { display_boot_screen(pax_buffer, ili9341, "FPGA download mode"); while (true) { diff --git a/main/webusb.c b/main/webusb.c new file mode 100644 index 0000000..995fe11 --- /dev/null +++ b/main/webusb.c @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "driver/uart.h" +#include "hardware.h" +#include "managed_i2c.h" +#include "pax_gfx.h" +#include "ice40.h" +#include "system_wrapper.h" +#include "graphics_wrapper.h" +#include "esp32/rom/crc.h" + +void webusb_install_uart() { + fflush(stdout); + ESP_ERROR_CHECK(uart_driver_install(0, 2048, 0, 0, NULL, 0)); + uart_config_t uart_config = { + .baud_rate = 921600, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_APB, + }; + ESP_ERROR_CHECK(uart_param_config(0, &uart_config)); +} + +void webusb_uninstall_uart() { + uart_driver_delete(0); +} + +bool webusb_read_stdin(uint8_t* buffer, uint32_t len, uint32_t timeout) { + int read = uart_read_bytes(0, buffer, len, timeout / portTICK_PERIOD_MS); + return (read == len); +} + +bool webusb_uart_sync(uint32_t* length, uint32_t* crc) { + uint8_t rx_buffer[4*3]; + webusb_read_stdin(rx_buffer, sizeof(rx_buffer), 100); + if (memcmp(rx_buffer, "WUSB", 4) != 0) return false; + memcpy((uint8_t*) length, &rx_buffer[4 * 1], 4); + memcpy((uint8_t*) crc, &rx_buffer[4 * 2], 4); + return true; +} + +bool webusb_uart_load(uint8_t* buffer, uint32_t length) { + return webusb_read_stdin(buffer, length, 3000); +} + +void webusb_uart_mess(const char *mess) { + uart_write_bytes(0, mess, strlen(mess)); +} + +void webusb_print_status(pax_buf_t* pax_buffer, ILI9341* ili9341, char* message) { + pax_noclip(pax_buffer); + pax_background(pax_buffer, 0x325aa8); + pax_draw_text(pax_buffer, 0xFFFFFFFF, NULL, 18, 0, 20*0, "WebUSB mode"); + pax_draw_text(pax_buffer, 0xFFFFFFFF, NULL, 18, 0, 20*1, message); + ili9341_write(ili9341, pax_buffer->buf); +} + +void webusb_main(xQueueHandle buttonQueue, pax_buf_t* pax_buffer, ILI9341* ili9341) { + webusb_install_uart(); + + while (true) { + webusb_print_status(pax_buffer, ili9341, "Waiting..."); + + // 1) Wait for WUSB followed by data length as uint32 and CRC32 of the data as uint32 + uint32_t length, crc; + while (!webusb_uart_sync(&length, &crc)) { + webusb_uart_mess("WUSB"); + } + + webusb_print_status(pax_buffer, ili9341, "Receiving..."); + + // 2) Allocate RAM for the data to be received + uint8_t* buffer = malloc(length); + if (buffer == NULL) { + webusb_uart_mess("EMEM"); + webusb_print_status(pax_buffer, ili9341, "Error: malloc failed"); + vTaskDelay(100 / portTICK_PERIOD_MS); + continue; + } + + // 3) Receive data into the buffer + if (!webusb_uart_load(buffer, length)) { + free(buffer); + webusb_uart_mess("ERCV"); + webusb_print_status(pax_buffer, ili9341, "Error: receive failed"); + vTaskDelay(100 / portTICK_PERIOD_MS); + continue; + } + + // 4) Check CRC + uint32_t checkCrc = crc32_le(0, buffer, length); + + if (checkCrc != crc) { + free(buffer); + webusb_uart_mess("ECRC"); + webusb_print_status(pax_buffer, ili9341, "Error: CRC invalid"); + vTaskDelay(100 / portTICK_PERIOD_MS); + continue; + } + + webusb_uart_mess("OKOK"); + webusb_print_status(pax_buffer, ili9341, "Packet received"); + + // To-do: parse packet + } + + webusb_uninstall_uart(); +} From dbbeb6df44f4f9c453eab14ad393d9da259e0db2 Mon Sep 17 00:00:00 2001 From: Renze Nicolai Date: Mon, 6 Jun 2022 01:08:07 +0200 Subject: [PATCH 4/7] Check USB and battery voltages during factory test --- main/factory_test.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/main/factory_test.c b/main/factory_test.c index ad286be..4e154c4 100644 --- a/main/factory_test.c +++ b/main/factory_test.c @@ -60,6 +60,20 @@ bool test_stuck_buttons(uint32_t* rc) { return (state == 0x0000); } +bool test_adc_vbat(uint32_t* rc) { + float value = 0; + esp_err_t res = rp2040_read_vbat(get_rp2040(), &value); + *rc = value * 100; + return ((res == ESP_OK) && (value < 4.3) && (value > 3.9)); +} + +bool test_adc_vusb(uint32_t* rc) { + float value = 0; + esp_err_t res = rp2040_read_vusb(get_rp2040(), &value); + *rc = value * 100; + return ((res == ESP_OK) && (value > 4.5)); +} + bool test_sd_power(uint32_t* rc) { *rc = 0x00000000; // Init all GPIO pins for SD card and LED @@ -106,8 +120,12 @@ bool run_basic_tests(pax_buf_t* pax_buffer, ILI9341* ili9341) { RUN_TEST_MANDATORY("ICE40", test_ice40_init); RUN_TEST_MANDATORY("BNO055", test_bno055_init); RUN_TEST_MANDATORY("BME680", test_bme680_init); - RUN_TEST_MANDATORY("STUCK BUTTONS", test_stuck_buttons); - RUN_TEST_MANDATORY("SD/LED POWER", test_sd_power); + + /* Run tests */ + RUN_TEST("STUCK BUTTONS", test_stuck_buttons); + RUN_TEST("SD/LED POWER", test_sd_power); + RUN_TEST("Battery voltage", test_adc_vbat); + RUN_TEST("USB voltage", test_adc_vusb); error: From 0f660dca5d1746c289e4fee4e6105e3710f56065 Mon Sep 17 00:00:00 2001 From: Renze Nicolai Date: Mon, 6 Jun 2022 04:01:20 +0200 Subject: [PATCH 5/7] Fix percentage calculation for battery --- main/menus/start.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/main/menus/start.c b/main/menus/start.c index fa6e54a..ce2b703 100644 --- a/main/menus/start.c +++ b/main/menus/start.c @@ -140,8 +140,12 @@ void menu_start(xQueueHandle buttonQueue, pax_buf_t* pax_buffer, ILI9341* ili934 usb_voltage = 0; } - battery_percent = ((battery_voltage - 3.7) * 100) / (4.1 - 3.7); - if (battery_percent > 100) battery_percent = 100; + if (battery_voltage >= 3.6) { + battery_percent = ((battery_voltage - 3.6) * 100) / (4.2 - 3.6); + if (battery_percent > 100) battery_percent = 100; + } else { + battery_percent = 0; + } battery_charging = (usb_voltage > 4.0) && (battery_percent < 100); From 5c4e99738c62ceb3425aca1367d9ae4ed7aa50ef Mon Sep 17 00:00:00 2001 From: Renze Nicolai Date: Mon, 6 Jun 2022 04:01:37 +0200 Subject: [PATCH 6/7] Update RP2040 firmware --- main/rp2040_updater.c | 2 +- resources/rp2040_firmware.bin | Bin 36380 -> 39420 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/main/rp2040_updater.c b/main/rp2040_updater.c index e05663b..41b8896 100644 --- a/main/rp2040_updater.c +++ b/main/rp2040_updater.c @@ -47,7 +47,7 @@ void rp2040_updater(RP2040* rp2040, pax_buf_t* pax_buffer, ILI9341* ili9341) { restart(); } - if (fw_version < 0x02) { // Update required + if (fw_version < 0x03) { // Update required display_rp2040_update_state(pax_buffer, ili9341, "Starting bootloader..."); rp2040_reboot_to_bootloader(rp2040); esp_restart(); diff --git a/resources/rp2040_firmware.bin b/resources/rp2040_firmware.bin index 917aafd561508b01342925a80c0e991666144c85..c445a2834bc5a07fb15509b8510026976f362f68 100755 GIT binary patch delta 15996 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 bj6eP^N6z6O>hB@ycaU2rmJoG3o~r)`YszVI delta 13027 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 Date: Mon, 6 Jun 2022 04:01:49 +0200 Subject: [PATCH 7/7] Add better fault indication --- main/main.c | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/main/main.c b/main/main.c index 51c9644..1b3dab4 100644 --- a/main/main.c +++ b/main/main.c @@ -71,6 +71,24 @@ void display_fatal_error(pax_buf_t* pax_buffer, ILI9341* ili9341, const char* li ili9341_write(ili9341, pax_buffer->buf); } +void stop() { + ESP_LOGW(TAG, "*** HALTED ***"); + gpio_set_direction(GPIO_SD_PWR, GPIO_MODE_OUTPUT); + gpio_set_level(GPIO_SD_PWR, 1); + ws2812_init(GPIO_LED_DATA); + uint8_t led_off[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t led_red[15] = {0, 50, 0, 0, 50, 0, 0, 50, 0, 0, 50, 0, 0, 50, 0}; + uint8_t led_red2[15] = {0, 0xFF, 0, 0, 0xFF, 0, 0, 0xFF, 0, 0, 0xFF, 0, 0, 0xFF, 0}; + while (true) { + ws2812_send_data(led_red2, sizeof(led_red2)); + vTaskDelay(pdMS_TO_TICKS(200)); + ws2812_send_data(led_red, sizeof(led_red)); + vTaskDelay(pdMS_TO_TICKS(200)); + ws2812_send_data(led_off, sizeof(led_off)); + vTaskDelay(pdMS_TO_TICKS(200)); + } +} + void app_main(void) { esp_err_t res; @@ -117,7 +135,7 @@ void app_main(void) { if (res != ESP_OK) { ESP_LOGE(TAG, "NVS init failed: %d", res); display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "NVS failed to initialize", "Flash may be corrupted", NULL); - esp_restart(); + stop(); } display_boot_screen(pax_buffer, ili9341, "Starting..."); @@ -125,13 +143,13 @@ void app_main(void) { if (bsp_rp2040_init() != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize the RP2040 co-processor"); display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "RP2040 co-processor error", NULL, NULL); - esp_restart(); + stop(); } RP2040* rp2040 = get_rp2040(); if (rp2040 == NULL) { ESP_LOGE(TAG, "rp2040 is NULL"); - esp_restart(); + stop(); } rp2040_updater(rp2040, pax_buffer, ili9341); // Handle RP2040 firmware update & bootloader mode @@ -142,7 +160,7 @@ void app_main(void) { if (rp2040_get_uid(rp2040, rp2040_uid) != ESP_OK) { ESP_LOGE(TAG, "Failed to get RP2040 UID"); display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "Failed to read UID", NULL, NULL); - esp_restart(); + stop(); } printf("RP2040 UID: %02X%02X%02X%02X%02X%02X%02X%02X\n", rp2040_uid[0], rp2040_uid[1], rp2040_uid[2], rp2040_uid[3], rp2040_uid[4], rp2040_uid[5], rp2040_uid[6], rp2040_uid[7]);*/ @@ -150,13 +168,13 @@ void app_main(void) { if (bsp_ice40_init() != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize the ICE40 FPGA"); display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "ICE40 FPGA error", NULL, NULL); - esp_restart(); + stop(); } ICE40* ice40 = get_ice40(); if (ice40 == NULL) { ESP_LOGE(TAG, "ice40 is NULL"); - esp_restart(); + stop(); } /*display_boot_screen(pax_buffer, ili9341, "Initializing BNO055..."); @@ -164,13 +182,13 @@ void app_main(void) { if (bsp_bno055_init() != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize the BNO055 position sensor"); display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "BNO055 sensor error", "Check I2C bus", "Remove SAO and try again"); - esp_restart(); + stop(); } BNO055* bno055 = get_bno055(); if (bno055 == NULL) { ESP_LOGE(TAG, "bno055 is NULL"); - esp_restart(); + stop(); }*/ /*display_boot_screen(pax_buffer, ili9341, "Initializing BME680..."); @@ -178,13 +196,13 @@ void app_main(void) { if (bsp_bme680_init() != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize the BME680 position sensor"); display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "BME680 sensor error", "Check I2C bus", "Remove SAO and try again"); - esp_restart(); + stop(); } BME680* bme680 = get_bme680(); if (bme680 == NULL) { ESP_LOGE(TAG, "bme680 is NULL"); - esp_restart(); + stop(); }*/ //display_boot_screen(pax_buffer, ili9341, "Initializing AppFS..."); @@ -194,7 +212,7 @@ void app_main(void) { if (res != ESP_OK) { ESP_LOGE(TAG, "AppFS init failed: %d", res); display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "AppFS failed to initialize", "Flash may be corrupted", NULL); - esp_restart(); + stop(); } //display_boot_screen(pax_buffer, ili9341, "Initializing filesystem..."); @@ -244,7 +262,7 @@ void app_main(void) { if (res != ESP_OK) { ESP_LOGE(TAG, "Failed to read WebUSB mode: %d", res); display_fatal_error(pax_buffer, ili9341, "Failed to initialize", "Failed to read WebUSB mode", NULL, NULL); - esp_restart(); + stop(); } ESP_LOGI(TAG, "WebUSB mode 0x%02X", webusb_mode);