This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
tamiwiki:users:6r1d:diymall_esp32_s3_fixture [2025/10/18 12:15] – 6r1d | tamiwiki:users:6r1d:diymall_esp32_s3_fixture [2025/10/19 01:32] (current) – [Tracing code] 6r1d | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | There's something | + | There’s something |
- | Or a bad board. Mostly that. | + | Or a non-working one. |
+ | //Usually// it's one of those two. | ||
- | And I've been using a fixture by [[http:// | + | ====== Introduction ====== |
- | It's easy to buy on AliExpress, for example, [[https:// | + | |
- | I sat with a scope and recorded | + | I've been using a fixture by [[http:// |
+ | |||
+ | {{: | ||
+ | |||
+ | It is very helpful as a fixture. It is not helpful whatsoever when it comes to proper documentation. If you open the [[http:// | ||
+ | |||
+ | So, the fixture is easy to buy on AliExpress, for example, [[https:// | ||
+ | |||
+ | Thus, I've traced | ||
+ | |||
+ | ====== Pin assignments ====== | ||
+ | |||
+ | ^ ESP32-S3-Wroom contact pad ^ Datasheet reference ^ Arduino pin ^ Board pin ^ | ||
+ | | 39 | IO1 | 1 | D36 | | ||
+ | | 38 | IO2 | 2 | D35 | | ||
+ | | 4 | IO4 | 4 | D1 | | ||
+ | | 5 | IO5 | 5 | D2 | | ||
+ | | 6 | IO6 | 6 | D3 | | ||
+ | | 7 | IO7 | 7 | D4 | | ||
+ | | 12 | IO8 | 8 | D9 | | ||
+ | | 17 | IO9 | 9 | D14 | | ||
+ | | 18 | IO10 | 10 | D15 | | ||
+ | | 19 | IO11 | 11 | D16 | | ||
+ | | 20 | IO12 | 12 | D17 | | ||
+ | | 21 | IO13 | 13 | D18 | | ||
+ | | 22 | IO14 | 14 | D19 | | ||
+ | | 8 | IO15 | 15 | D5 | | ||
+ | | 9 | IO16 | 16 | D6 | | ||
+ | | 10 | IO17 | 17 | D7 | | ||
+ | | 11 | IO18 | 18 | D8 | | ||
+ | | 23 | IO21 | 21 | D20 | | ||
+ | | 31 | IO38 | 38 | D28 | | ||
+ | | 32 | IO39 | 39 | D29 | | ||
+ | | 33 | IO40 | 40 | D30 | | ||
+ | | 34 | IO41 | 41 | D31 | | ||
+ | | 35 | IO42 | 42 | D32 | | ||
+ | | 24 | IO47 | 47 | D21 | | ||
+ | | 25 | IO48 | 48 | D22 | | ||
+ | | 36 | RXD0 | 36 | D33 | | ||
+ | | 37 | TXD0 | 37 | D34 | | ||
+ | |||
+ | ===== Notes ===== | ||
+ | |||
+ | * GPIO pin 48 is the onboard LED, usually blue. | ||
+ | * Initially, I thought that onboard USB-UART is connected to something else than RXD0 / TXD0. It was not. It is the main UART. | ||
+ | |||
+ | ====== Tracing code ====== | ||
+ | |||
+ | You might disagree with me and want to double-check. Excellent! More eyes (and feedback) mean better information. | ||
+ | |||
+ | Grab your trusty 30-year-old LED soldered to a pair of DuPont sockets and start tracing. If you don’t have one handy (like me), grab your scope instead.((Marie Antoinette reference unintended.)) | ||
+ | |||
+ | For convenience, | ||
+ | ===== ESP-IDF (+ history and PWM debug) ===== | ||
+ | |||
+ | This code gives you a predictable interactive workflow with history. | ||
+ | |||
+ | < | ||
+ | > help | ||
+ | Commands: | ||
+ | list -> show test-safe GPIOs | ||
+ | status | ||
+ | pin < | ||
+ | < | ||
+ | delay < | ||
+ | pwm <pin> [brightness] | ||
+ | brightness < | ||
+ | freq < | ||
+ | stop -> stop and release pin | ||
+ | |||
+ | > 48 | ||
+ | Stopped. Pin released to INPUT. | ||
+ | Blinking GPIO 48 at 100 ms. | ||
+ | |||
+ | > 42 | ||
+ | Stopped. Pin released to INPUT. | ||
+ | Blinking GPIO 42 at 100 ms. | ||
+ | |||
+ | > 47 | ||
+ | Stopped. Pin released to INPUT. | ||
+ | Blinking GPIO 47 at 100 ms. | ||
+ | |||
+ | > 46 | ||
+ | Restricted: GPIO 46 is not in the test-safe set. | ||
+ | |||
+ | > delay 40 | ||
+ | Set blink delay to 40 ms. | ||
+ | |||
+ | > delay 10 | ||
+ | Set blink delay to 10 ms. | ||
+ | </ | ||
+ | |||
+ | As for using it, you should know the gist at this point. | ||
+ | |||
+ | <code bash> | ||
+ | mkdir pin_tracer && cd pin_tracer | ||
+ | idf.py create-project . && idf.py reconfigure && idf.py set-target esp32s3 | ||
+ | # fill the code | ||
+ | # update the CMake configs | ||
+ | idf.py build | ||
+ | idf.py flash monitor | ||
+ | </ | ||
+ | |||
+ | CMake config (code): | ||
+ | |||
+ | < | ||
+ | idf_component_register(SRCS " | ||
+ | INCLUDE_DIRS " | ||
+ | </ | ||
+ | |||
+ | CMake config (project): | ||
+ | |||
+ | < | ||
+ | # The following five lines of boilerplate have to be in your project' | ||
+ | # CMakeLists in this exact order for cmake to work correctly | ||
+ | cmake_minimum_required(VERSION 3.16) | ||
+ | |||
+ | include($ENV{IDF_PATH}/ | ||
+ | project(.) | ||
+ | </ | ||
+ | |||
+ | <file cpp esp32_s3_pin_tracer.c> | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | |||
+ | /* | ||
+ | * CONFIG | ||
+ | */ | ||
+ | // Defaults | ||
+ | static uint32_t g_blink_delay_ms = 100; | ||
+ | // Console config | ||
+ | #define CLI_LINE_MAX 128 | ||
+ | #define HIST_MAX | ||
+ | |||
+ | // PWM configuration | ||
+ | #define LEDC_TIMER | ||
+ | #define LEDC_MODE | ||
+ | #define LEDC_CHANNEL | ||
+ | #define LEDC_DUTY_RES | ||
+ | #define LEDC_FREQUENCY | ||
+ | |||
+ | // Conservative " | ||
+ | static const gpio_num_t TEST_SAFE_GPIO[] = { | ||
+ | 1, 2, | ||
+ | 4, 5, 6, 7, | ||
+ | 8, 9, 10, 11, 12, 13, 14, | ||
+ | 15, 16, 17, 18, | ||
+ | /* 19,20 excluded (USB D-/D+) */ | ||
+ | 21, | ||
+ | /* 22..34 not bonded on typical modules */ | ||
+ | /* 35,36,37 excluded (PSRAM on some variants) */ | ||
+ | 38, 39, 40, 41, 42, /* JTAG-capable if configured */ | ||
+ | /* 43,44 excluded (UART0 console) */ | ||
+ | 47, 48 /* may be 1.8 V on R16V */ | ||
+ | }; | ||
+ | static const size_t TEST_SAFE_GPIO_COUNT = sizeof(TEST_SAFE_GPIO) / sizeof(TEST_SAFE_GPIO[0]); | ||
+ | |||
+ | // ====== RUNTIME STATE ====== | ||
+ | static gpio_num_t g_current_pin = -1; | ||
+ | static bool g_blinking = false; | ||
+ | static bool g_pwm_mode = false; | ||
+ | static uint8_t g_pwm_brightness = 50; // 0-99 scale | ||
+ | static uint32_t g_pwm_frequency = LEDC_FREQUENCY; | ||
+ | static TaskHandle_t g_blink_task = NULL; | ||
+ | |||
+ | /* ================== GPIO HELPERS ================== */ | ||
+ | |||
+ | static bool is_test_safe(gpio_num_t gpio) | ||
+ | { | ||
+ | for (size_t i = 0; i < TEST_SAFE_GPIO_COUNT; | ||
+ | if (TEST_SAFE_GPIO[i] == gpio) return true; | ||
+ | } | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | static void print_test_safe_pins(void) | ||
+ | { | ||
+ | printf(" | ||
+ | for (size_t i = 0; i < TEST_SAFE_GPIO_COUNT; | ||
+ | printf(" | ||
+ | } | ||
+ | printf(" | ||
+ | printf(" | ||
+ | } | ||
+ | |||
+ | static void release_pin(gpio_num_t pin) | ||
+ | { | ||
+ | if (pin >= 0) { | ||
+ | gpio_set_level(pin, | ||
+ | gpio_reset_pin(pin); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | static void stop_pwm(void) | ||
+ | { | ||
+ | if (g_pwm_mode) { | ||
+ | ledc_stop(LEDC_MODE, | ||
+ | g_pwm_mode = false; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | static void stop_blink(void) | ||
+ | { | ||
+ | if (g_blink_task) { | ||
+ | TaskHandle_t t = g_blink_task; | ||
+ | g_blink_task = NULL; // signal task to exit | ||
+ | for (int i = 0; i < 20 && eTaskGetState(t) != eDeleted; ++i) { | ||
+ | vTaskDelay(pdMS_TO_TICKS(5)); | ||
+ | } | ||
+ | } | ||
+ | g_blinking = false; | ||
+ | } | ||
+ | |||
+ | static void stop_all(void) | ||
+ | { | ||
+ | stop_blink(); | ||
+ | stop_pwm(); | ||
+ | release_pin(g_current_pin); | ||
+ | g_current_pin = -1; | ||
+ | printf(" | ||
+ | } | ||
+ | |||
+ | static void blink_task(void *arg) | ||
+ | { | ||
+ | gpio_num_t pin = (gpio_num_t)(intptr_t)arg; | ||
+ | gpio_config_t io = { | ||
+ | .pin_bit_mask = 1ULL << pin, | ||
+ | .mode = GPIO_MODE_OUTPUT, | ||
+ | .pull_up_en = GPIO_PULLUP_DISABLE, | ||
+ | .pull_down_en = GPIO_PULLDOWN_DISABLE, | ||
+ | .intr_type = GPIO_INTR_DISABLE | ||
+ | }; | ||
+ | gpio_config(& | ||
+ | gpio_set_level(pin, | ||
+ | |||
+ | while (g_blink_task == xTaskGetCurrentTaskHandle()) { | ||
+ | gpio_set_level(pin, | ||
+ | vTaskDelay(pdMS_TO_TICKS(g_blink_delay_ms)); | ||
+ | gpio_set_level(pin, | ||
+ | vTaskDelay(pdMS_TO_TICKS(g_blink_delay_ms)); | ||
+ | } | ||
+ | |||
+ | release_pin(pin); | ||
+ | vTaskDelete(NULL); | ||
+ | } | ||
+ | |||
+ | static bool setup_pwm(gpio_num_t gpio) | ||
+ | { | ||
+ | // Stop previous PWM if any | ||
+ | if (g_pwm_mode) { | ||
+ | ledc_stop(LEDC_MODE, | ||
+ | } | ||
+ | |||
+ | // Configure LEDC timer | ||
+ | ledc_timer_config_t ledc_timer = { | ||
+ | .speed_mode | ||
+ | .timer_num | ||
+ | .duty_resolution | ||
+ | .freq_hz | ||
+ | .clk_cfg | ||
+ | }; | ||
+ | esp_err_t ret = ledc_timer_config(& | ||
+ | if (ret != ESP_OK) { | ||
+ | printf(" | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | // Configure LEDC channel | ||
+ | ledc_channel_config_t ledc_channel = { | ||
+ | .speed_mode | ||
+ | .channel | ||
+ | .timer_sel | ||
+ | .intr_type | ||
+ | .gpio_num | ||
+ | .duty = 0, // Start at 0 duty | ||
+ | .hpoint | ||
+ | }; | ||
+ | ret = ledc_channel_config(& | ||
+ | if (ret != ESP_OK) { | ||
+ | printf(" | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | return true; | ||
+ | } | ||
+ | |||
+ | static void set_pwm_brightness(uint8_t level) | ||
+ | { | ||
+ | if (level > 99) level = 99; | ||
+ | g_pwm_brightness = level; | ||
+ | |||
+ | // Convert 0-99 scale to 0-255 duty cycle | ||
+ | uint32_t duty = (level * 255) / 99; | ||
+ | ledc_set_duty(LEDC_MODE, | ||
+ | ledc_update_duty(LEDC_MODE, | ||
+ | |||
+ | printf(" | ||
+ | } | ||
+ | |||
+ | static void set_pwm_frequency(uint32_t freq) | ||
+ | { | ||
+ | if (freq < 100) freq = 100; | ||
+ | if (freq > 50000) freq = 50000; // Conservative max for LEDC | ||
+ | |||
+ | g_pwm_frequency = freq; | ||
+ | |||
+ | if (g_pwm_mode) { | ||
+ | // Reconfigure PWM with new frequency | ||
+ | ledc_timer_config_t ledc_timer = { | ||
+ | .speed_mode | ||
+ | .timer_num | ||
+ | .duty_resolution | ||
+ | .freq_hz | ||
+ | .clk_cfg | ||
+ | }; | ||
+ | esp_err_t ret = ledc_timer_config(& | ||
+ | if (ret == ESP_OK) { | ||
+ | printf(" | ||
+ | // Restore brightness | ||
+ | set_pwm_brightness(g_pwm_brightness); | ||
+ | } else { | ||
+ | printf(" | ||
+ | } | ||
+ | } else { | ||
+ | printf(" | ||
+ | } | ||
+ | } | ||
+ | |||
+ | static void start_blink(gpio_num_t gpio) | ||
+ | { | ||
+ | if (!is_test_safe(gpio)) { | ||
+ | printf(" | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | stop_all(); // Stop any previous mode | ||
+ | |||
+ | g_current_pin = gpio; | ||
+ | g_blinking = true; | ||
+ | |||
+ | if (xTaskCreatePinnedToCore(blink_task, | ||
+ | tskIDLE_PRIORITY + 1, & | ||
+ | printf(" | ||
+ | g_blinking = false; | ||
+ | g_current_pin = -1; | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | printf(" | ||
+ | } | ||
+ | |||
+ | static void start_pwm(gpio_num_t gpio, uint8_t brightness) | ||
+ | { | ||
+ | if (!is_test_safe(gpio)) { | ||
+ | printf(" | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | stop_all(); // Stop any previous mode | ||
+ | |||
+ | if (!setup_pwm(gpio)) { | ||
+ | printf(" | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | g_current_pin = gpio; | ||
+ | g_pwm_mode = true; | ||
+ | set_pwm_brightness(brightness); | ||
+ | |||
+ | printf(" | ||
+ | } | ||
+ | |||
+ | /* ================== COMMAND PARSER ================== */ | ||
+ | |||
+ | static void trim(char *s) | ||
+ | { | ||
+ | size_t len = strlen(s); | ||
+ | while (len && (s[len-1] == ' | ||
+ | size_t i = 0; | ||
+ | while (s[i] && isspace((unsigned char)s[i])) i++; | ||
+ | if (i) memmove(s, s+i, strlen(s+i)+1); | ||
+ | } | ||
+ | |||
+ | static bool all_digits(const char *s) | ||
+ | { | ||
+ | if (!*s) return false; | ||
+ | for (const char *p = s; *p; ++p) { | ||
+ | if (!isdigit((unsigned char)*p)) return false; | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | static void handle_command(char *line) | ||
+ | { | ||
+ | trim(line); | ||
+ | if (!*line) return; | ||
+ | |||
+ | char cmd[64]; | ||
+ | strncpy(cmd, | ||
+ | cmd[sizeof(cmd)-1] = 0; | ||
+ | for (char *p = cmd; *p; ++p) *p = (char)tolower((unsigned char)*p); | ||
+ | |||
+ | if (strcmp(cmd, | ||
+ | print_test_safe_pins(); | ||
+ | return; | ||
+ | } | ||
+ | if (strcmp(cmd, | ||
+ | printf(" | ||
+ | | ||
+ | if (g_blinking) { | ||
+ | printf(" | ||
+ | } else if (g_pwm_mode) { | ||
+ | printf(" | ||
+ | | ||
+ | } else { | ||
+ | printf(" | ||
+ | } | ||
+ | return; | ||
+ | } | ||
+ | if (strcmp(cmd, | ||
+ | stop_all(); | ||
+ | return; | ||
+ | } | ||
+ | if (strncmp(cmd, | ||
+ | int gpio = atoi(line + 4); | ||
+ | start_blink((gpio_num_t)gpio); | ||
+ | return; | ||
+ | } | ||
+ | if (strncmp(cmd, | ||
+ | int v = atoi(line + 6); | ||
+ | if (v < 10) v = 10; | ||
+ | g_blink_delay_ms = (uint32_t)v; | ||
+ | printf(" | ||
+ | return; | ||
+ | } | ||
+ | if (strncmp(cmd, | ||
+ | // Parse "pwm <pin> < | ||
+ | char *args = line + 4; | ||
+ | trim(args); | ||
+ | |||
+ | int gpio = -1, brightness = g_pwm_brightness; | ||
+ | |||
+ | if (sscanf(args, | ||
+ | if (brightness < 0) brightness = 0; | ||
+ | if (brightness > 99) brightness = 99; | ||
+ | start_pwm((gpio_num_t)gpio, | ||
+ | } else { | ||
+ | printf(" | ||
+ | printf(" | ||
+ | } | ||
+ | return; | ||
+ | } | ||
+ | if (strncmp(cmd, | ||
+ | // Allow both " | ||
+ | char *arg = (cmd[6] == ' ') ? line + 7 : line + 11; | ||
+ | int brightness = atoi(arg); | ||
+ | if (brightness >= 0 && brightness <= 99) { | ||
+ | if (g_pwm_mode) { | ||
+ | set_pwm_brightness((uint8_t)brightness); | ||
+ | } else { | ||
+ | printf(" | ||
+ | } | ||
+ | } else { | ||
+ | printf(" | ||
+ | } | ||
+ | return; | ||
+ | } | ||
+ | if (strncmp(cmd, | ||
+ | char *arg = (cmd[4] == ' ') ? line + 5 : line + 10; | ||
+ | int freq = atoi(arg); | ||
+ | if (freq > 0) { | ||
+ | set_pwm_frequency((uint32_t)freq); | ||
+ | } else { | ||
+ | printf(" | ||
+ | } | ||
+ | return; | ||
+ | } | ||
+ | if (strcmp(cmd, | ||
+ | printf(" | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | | ||
+ | return; | ||
+ | } | ||
+ | if (all_digits(line)) { | ||
+ | start_blink((gpio_num_t)atoi(line)); | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | printf(" | ||
+ | } | ||
+ | |||
+ | /* ================== CONSOLE ================== */ | ||
+ | static void console_task(void *arg) | ||
+ | { | ||
+ | const uart_port_t uart_num = UART_NUM_0; | ||
+ | |||
+ | char | ||
+ | size_t pos = 0; // length of buffer | ||
+ | size_t cursor = 0; // cursor index in [0..pos] | ||
+ | |||
+ | char hist[HIST_MAX][CLI_LINE_MAX] = {{0}}; | ||
+ | int hist_count = 0; // number of valid entries | ||
+ | int hist_head | ||
+ | int hist_view | ||
+ | |||
+ | // helpers | ||
+ | #define WRITE_STR(s) uart_write_bytes(uart_num, | ||
+ | |||
+ | auto void prompt(void) { | ||
+ | WRITE_STR(" | ||
+ | fflush(stdout); | ||
+ | } | ||
+ | |||
+ | auto void redraw_line(void) { | ||
+ | // Clear line, reprint prompt + buffer, then move cursor left if needed | ||
+ | WRITE_STR(" | ||
+ | if (pos) uart_write_bytes(uart_num, | ||
+ | if (pos > cursor) { | ||
+ | char seq[16]; int n = snprintf(seq, | ||
+ | uart_write_bytes(uart_num, | ||
+ | } | ||
+ | } | ||
+ | |||
+ | auto void push_history(const char *cmd) { | ||
+ | if (!cmd[0]) return; | ||
+ | int last = (hist_head - 1 + HIST_MAX) % HIST_MAX; | ||
+ | if (hist_count > 0 && strncmp(hist[last], | ||
+ | strncpy(hist[hist_head], | ||
+ | hist[hist_head][CLI_LINE_MAX - 1] = 0; | ||
+ | hist_head = (hist_head + 1) % HIST_MAX; | ||
+ | if (hist_count < HIST_MAX) hist_count++; | ||
+ | } | ||
+ | |||
+ | auto void load_history(int idx) { | ||
+ | strncpy(line, | ||
+ | line[CLI_LINE_MAX - 1] = 0; | ||
+ | pos = cursor = strlen(line); | ||
+ | redraw_line(); | ||
+ | } | ||
+ | |||
+ | auto bool in_history(void) { return hist_view != -1; } | ||
+ | |||
+ | prompt(); | ||
+ | |||
+ | enum { ESC_IDLE, ESC_ESC, ESC_CSI, ESC_TILDE } esc = ESC_IDLE; | ||
+ | char csi_param_buf[4] = {0}; | ||
+ | int csi_param_len = 0; | ||
+ | |||
+ | while (1) { | ||
+ | uint8_t ch; | ||
+ | int got = uart_read_bytes(uart_num, | ||
+ | if (got != 1) continue; | ||
+ | |||
+ | // Escape handling (arrows/ | ||
+ | if (esc == ESC_ESC) { | ||
+ | if (ch == ' | ||
+ | // Alt-b / Alt-f (word left/ | ||
+ | if (ch == ' | ||
+ | // word-left | ||
+ | if (cursor > 0) { | ||
+ | while (cursor > 0 && isspace((unsigned char)line[cursor-1])) cursor--; | ||
+ | while (cursor > 0 && !isspace((unsigned char)line[cursor-1])) cursor--; | ||
+ | redraw_line(); | ||
+ | } | ||
+ | esc = ESC_IDLE; continue; | ||
+ | } | ||
+ | if (ch == ' | ||
+ | // word-right | ||
+ | if (cursor < pos) { | ||
+ | while (cursor < pos && !isspace((unsigned char)line[cursor])) cursor++; | ||
+ | while (cursor < pos && | ||
+ | redraw_line(); | ||
+ | } | ||
+ | esc = ESC_IDLE; continue; | ||
+ | } | ||
+ | // Unknown ESC seq -> ignore | ||
+ | esc = ESC_IDLE; | ||
+ | continue; | ||
+ | } else if (esc == ESC_CSI) { | ||
+ | if (ch >= ' | ||
+ | if (csi_param_len < (int)sizeof(csi_param_buf)-1) csi_param_buf[csi_param_len++] = (char)ch; | ||
+ | continue; | ||
+ | } | ||
+ | if (ch == ' | ||
+ | // Handle [3~ delete, [1~ home, [4~ end | ||
+ | int p = atoi(csi_param_buf); | ||
+ | if (p == 3) { // Delete (forward) | ||
+ | if (cursor < pos) { | ||
+ | memmove(& | ||
+ | pos--; | ||
+ | line[pos] = 0; | ||
+ | redraw_line(); | ||
+ | } | ||
+ | } else if (p == 1) { // Home | ||
+ | cursor = 0; redraw_line(); | ||
+ | } else if (p == 4) { // End | ||
+ | cursor = pos; redraw_line(); | ||
+ | } | ||
+ | esc = ESC_IDLE; continue; | ||
+ | } | ||
+ | // Final byte for standard arrows/ | ||
+ | if (ch == ' | ||
+ | if (hist_count) { | ||
+ | if (!in_history()) hist_view = (hist_head - 1 + HIST_MAX) % HIST_MAX; | ||
+ | else { | ||
+ | int oldest = (hist_head - hist_count + HIST_MAX) % HIST_MAX; | ||
+ | if (hist_view != oldest) hist_view = (hist_view - 1 + HIST_MAX) % HIST_MAX; | ||
+ | } | ||
+ | load_history(hist_view); | ||
+ | } | ||
+ | } else if (ch == ' | ||
+ | if (in_history()) { | ||
+ | int newest = (hist_head - 1 + HIST_MAX) % HIST_MAX; | ||
+ | if (hist_view != newest) { hist_view = (hist_view + 1) % HIST_MAX; load_history(hist_view); | ||
+ | else { hist_view = -1; pos = cursor = 0; line[0] = 0; redraw_line(); | ||
+ | } | ||
+ | } else if (ch == ' | ||
+ | if (cursor < pos) { cursor++; redraw_line(); | ||
+ | } else if (ch == ' | ||
+ | if (cursor > 0) { cursor--; redraw_line(); | ||
+ | } else if (ch == ' | ||
+ | cursor = 0; redraw_line(); | ||
+ | } else if (ch == ' | ||
+ | cursor = pos; redraw_line(); | ||
+ | } | ||
+ | esc = ESC_IDLE; | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | // Start of escape? | ||
+ | if (ch == 0x1B) { esc = ESC_ESC; continue; } | ||
+ | |||
+ | // CR / LF -> execute | ||
+ | if (ch == ' | ||
+ | WRITE_STR(" | ||
+ | line[pos] = 0; | ||
+ | if (pos) { push_history(line); | ||
+ | handle_command(line); | ||
+ | pos = cursor = 0; line[0] = 0; | ||
+ | prompt(); | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | // Ctrl-A / Ctrl-E: home/end | ||
+ | if (ch == 0x01) { cursor = 0; redraw_line(); | ||
+ | if (ch == 0x05) { cursor = pos; redraw_line(); | ||
+ | |||
+ | // Backspace / Delete-left | ||
+ | if (ch == 0x08 || ch == 0x7F) { | ||
+ | if (cursor > 0) { | ||
+ | memmove(& | ||
+ | cursor--; pos--; | ||
+ | line[pos] = 0; | ||
+ | redraw_line(); | ||
+ | } | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | // Ctrl-U: clear line | ||
+ | if (ch == 0x15) { | ||
+ | pos = cursor = 0; line[0] = 0; redraw_line(); | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | // Ctrl-L: redraw | ||
+ | if (ch == 0x0C) { redraw_line(); | ||
+ | |||
+ | // Ctrl-W: delete previous word | ||
+ | if (ch == 0x17) { | ||
+ | size_t start = cursor; | ||
+ | while (start > 0 && isspace((unsigned char)line[start - 1])) start--; | ||
+ | while (start > 0 && !isspace((unsigned char)line[start - 1])) start--; | ||
+ | if (start < cursor) { | ||
+ | memmove(& | ||
+ | pos -= (cursor - start); | ||
+ | cursor = start; | ||
+ | line[pos] = 0; | ||
+ | redraw_line(); | ||
+ | } | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | // Printable ASCII: insert at cursor | ||
+ | if (isprint(ch)) { | ||
+ | if (pos + 1 < CLI_LINE_MAX) { | ||
+ | if (in_history()) { hist_view = -1; /* keep text */ } | ||
+ | memmove(& | ||
+ | line[cursor++] = (char)ch; | ||
+ | pos++; | ||
+ | line[pos] = 0; | ||
+ | redraw_line(); | ||
+ | } | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | // ignore other control bytes | ||
+ | } | ||
+ | |||
+ | // Prevent task from returning (FreeRTOS requirement) | ||
+ | while (1) { | ||
+ | vTaskDelay(pdMS_TO_TICKS(1000)); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /* ================== APP ================== */ | ||
+ | |||
+ | void app_main(void) | ||
+ | { | ||
+ | const int baud = 115200; | ||
+ | const uart_port_t uart_num = UART_NUM_0; | ||
+ | uart_config_t cfg = { | ||
+ | .baud_rate = baud, | ||
+ | .data_bits = UART_DATA_8_BITS, | ||
+ | .parity | ||
+ | .stop_bits = UART_STOP_BITS_1, | ||
+ | .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, | ||
+ | .source_clk = UART_SCLK_DEFAULT, | ||
+ | }; | ||
+ | ESP_ERROR_CHECK(uart_driver_install(uart_num, | ||
+ | ESP_ERROR_CHECK(uart_param_config(uart_num, | ||
+ | uart_vfs_dev_use_driver(uart_num); | ||
+ | uart_vfs_dev_port_set_rx_line_endings(uart_num, | ||
+ | uart_vfs_dev_port_set_tx_line_endings(uart_num, | ||
+ | |||
+ | vTaskDelay(pdMS_TO_TICKS(200)); | ||
+ | |||
+ | printf(" | ||
+ | printf(" | ||
+ | print_test_safe_pins(); | ||
+ | |||
+ | xTaskCreatePinnedToCore(console_task, | ||
+ | tskIDLE_PRIORITY + 2, NULL, tskNO_AFFINITY); | ||
+ | |||
+ | vTaskDelete(NULL); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===== Arduino (basic version) ===== | ||
+ | |||
+ | Flash from Arduino IDE. Use the ESP32 board package by Espressif, pick ESP32S3 Dev Module, pick 115200 baud in Serial Monitor. | ||
+ | |||
+ | <file cpp esp32_s3_pin_tracer.ino> | ||
+ | #include < | ||
+ | |||
+ | // ====== CONFIG ====== | ||
+ | // Blink timing (change at runtime with: delay < | ||
+ | static uint32_t BLINK_DELAY_MS = 100; | ||
+ | |||
+ | // Conservative " | ||
+ | // Excludes pins that commonly serve boot strapping, USB D-/D+, PSRAM/ | ||
+ | // which can interfere with programming, | ||
+ | // | ||
+ | // Notes (summarized from the ESP32-S3 module/chip docs): | ||
+ | // - 0, 3, 45, 46 are strapping/ | ||
+ | // - 19, 20 are the on-chip USB D-/D+ differential pair; toggling them breaks USB comms. | ||
+ | // - 35, 36, 37 are wired to Octal PSRAM on some variants; not available as GPIO there. | ||
+ | // - 43 (U0TXD), 44 (U0RXD) are UART0 console pins used by Serial; poking them disrupts logs/ | ||
+ | // - 47, 48 form a differential SPI clock pair; safe as GPIO but mind 1.8 V I/O level on some R16V variants. | ||
+ | const uint8_t TEST_SAFE_GPIO[] = { | ||
+ | 1, 2, // ADC/ | ||
+ | 4, 5, 6, 7, // " | ||
+ | 8, 9, 10, 11, 12, 13, 14, // " | ||
+ | 15, 16, 17, 18, // " | ||
+ | // 19,20 excluded (USB D-/D+) | ||
+ | 21, // plain GPIO | ||
+ | // 22..34 don't exist on S3 modules | ||
+ | // 35,36,37 excluded (PSRAM on some variants) | ||
+ | 38, 39, 40, 41, 42, // JTAG-capable if configured, but OK as GPIO when JTAG is not in use | ||
+ | // 43,44 excluded (UART0 TX/RX used by Serial) | ||
+ | 47, 48 // differential clock pair; OK as GPIO (watch 1.8 V I/O on some R16V variants) | ||
+ | }; | ||
+ | const size_t TEST_SAFE_GPIO_COUNT = sizeof(TEST_SAFE_GPIO) / sizeof(TEST_SAFE_GPIO[0]); | ||
+ | |||
+ | // ====== RUNTIME STATE ====== | ||
+ | static int currentPin = -1; | ||
+ | static bool blinking = false; | ||
+ | |||
+ | // ====== HELPERS ====== | ||
+ | bool isTestSafe(int gpio) { | ||
+ | for (size_t i = 0; i < TEST_SAFE_GPIO_COUNT; | ||
+ | if ((int)TEST_SAFE_GPIO[i] == gpio) return true; | ||
+ | } | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | void printTestSafePins() { | ||
+ | Serial.println(F(" | ||
+ | for (size_t i = 0; i < TEST_SAFE_GPIO_COUNT; | ||
+ | Serial.print(F(i ? ", " : " | ||
+ | Serial.print(TEST_SAFE_GPIO[i]); | ||
+ | } | ||
+ | Serial.println(); | ||
+ | Serial.println(F(" | ||
+ | } | ||
+ | |||
+ | void stopBlink() { | ||
+ | if (currentPin >= 0) { | ||
+ | digitalWrite(currentPin, | ||
+ | pinMode(currentPin, | ||
+ | } | ||
+ | blinking = false; | ||
+ | currentPin = -1; | ||
+ | Serial.println(F(" | ||
+ | } | ||
+ | |||
+ | void startBlink(int gpio) { | ||
+ | if (!isTestSafe(gpio)) { | ||
+ | Serial.print(F(" | ||
+ | Serial.print(gpio); | ||
+ | Serial.println(F(" | ||
+ | return; | ||
+ | } | ||
+ | if (blinking && currentPin == gpio) { | ||
+ | Serial.print(F(" | ||
+ | Serial.println(gpio); | ||
+ | return; | ||
+ | } | ||
+ | // switch pin if needed | ||
+ | if (blinking && currentPin != gpio) stopBlink(); | ||
+ | |||
+ | currentPin = gpio; | ||
+ | pinMode(currentPin, | ||
+ | digitalWrite(currentPin, | ||
+ | blinking = true; | ||
+ | |||
+ | Serial.print(F(" | ||
+ | Serial.print(currentPin); | ||
+ | Serial.print(F(" | ||
+ | Serial.print(BLINK_DELAY_MS); | ||
+ | Serial.println(F(" | ||
+ | } | ||
+ | |||
+ | // Parse commands like: | ||
+ | // | ||
+ | // pin 10 | ||
+ | // delay 250 | ||
+ | // | ||
+ | void handleCommand(String line) { | ||
+ | line.trim(); | ||
+ | if (line.length() == 0) return; | ||
+ | |||
+ | line.toLowerCase(); | ||
+ | if (line == " | ||
+ | printTestSafePins(); | ||
+ | return; | ||
+ | } | ||
+ | if (line == " | ||
+ | stopBlink(); | ||
+ | return; | ||
+ | } | ||
+ | if (line.startsWith(" | ||
+ | int gpio = line.substring(4).toInt(); | ||
+ | startBlink(gpio); | ||
+ | return; | ||
+ | } | ||
+ | if (line.startsWith(" | ||
+ | int v = line.substring(6).toInt(); | ||
+ | if (v < 10) v = 10; // clamp a bit | ||
+ | BLINK_DELAY_MS = (uint32_t)v; | ||
+ | Serial.print(F(" | ||
+ | Serial.print(BLINK_DELAY_MS); | ||
+ | Serial.println(F(" | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | // Single-number shortcut: just type the GPIO number | ||
+ | bool allDigits = true; | ||
+ | for (size_t i = 0; i < (size_t)line.length(); | ||
+ | if (!isDigit(line[i])) { | ||
+ | allDigits = false; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | if (allDigits) { | ||
+ | startBlink(line.toInt()); | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | Serial.println(F(" | ||
+ | Serial.println(F(" | ||
+ | Serial.println(F(" | ||
+ | Serial.println(F(" | ||
+ | Serial.println(F(" | ||
+ | Serial.println(F(" | ||
+ | } | ||
+ | |||
+ | void setup() { | ||
+ | Serial.begin(115200); | ||
+ | // give USB-Serial/ | ||
+ | delay(400); | ||
+ | Serial.println(F(" | ||
+ | Serial.println(F(" | ||
+ | printTestSafePins(); | ||
+ | } | ||
+ | |||
+ | void loop() { | ||
+ | // Process serial line input | ||
+ | static String line; | ||
+ | while (Serial.available()) { | ||
+ | char c = (char)Serial.read(); | ||
+ | if (c == ' | ||
+ | if (c == ' | ||
+ | handleCommand(line); | ||
+ | line = ""; | ||
+ | } else { | ||
+ | line += c; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Blink the selected pin | ||
+ | if (blinking && currentPin >= 0) { | ||
+ | digitalWrite(currentPin, | ||
+ | delay(BLINK_DELAY_MS); | ||
+ | digitalWrite(currentPin, | ||
+ | delay(BLINK_DELAY_MS); | ||
+ | } else { | ||
+ | delay(5); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | ====== Feedback ====== | ||
+ | |||
+ | |||
+ | If you’d like to suggest corrections or aren’t satisfied with how I wrote this short article, feel free to ping me on [[https:// | ||
+ | |||
+ | This topic might deserve to be expanded into a document outside of my personal page. | ||
+ | Then again, I’m not sure how many people around here actually use ESP32-S3. |