diff --git a/components/control_plane/src/control_plane_lifecycle.c b/components/control_plane/src/control_plane_lifecycle.c index 9cf75a3..10a9dbe 100644 --- a/components/control_plane/src/control_plane_lifecycle.c +++ b/components/control_plane/src/control_plane_lifecycle.c @@ -6,11 +6,23 @@ #include "esp_timer.h" #include "domain.h" #include "freertos/FreeRTOS.h" +#include "freertos/queue.h" #include "freertos/semphr.h" #include "freertos/task.h" static const char *TAG = "control_plane_lifecycle"; static SemaphoreHandle_t s_lock; +static QueueHandle_t s_power_key_queue; +static TaskHandle_t s_power_key_task; + +typedef enum { + POWER_KEY_CMD_SHUTDOWN_REQUEST = 0, +} power_key_cmd_t; + +static const uint16_t POWER_KEY_QUEUE_LEN = 4; +static const uint16_t POWER_KEY_TASK_STACK = 4096; +static const uint16_t POWER_KEY_STOP_RETRY_COUNT = 30; +static const uint16_t POWER_KEY_STOP_RETRY_INTERVAL_MS = 100; typedef struct { control_plane_lifecycle_state_t state; esp_err_t last_error; @@ -96,6 +108,89 @@ static esp_err_t lifecycle_stop_system_runtime(void) { return system_runtime_shutdown(); } +static void lifecycle_power_key_event_cb(domain_power_event_t event, void *user_data) { + (void)user_data; + if (event != DOMAIN_POWER_EVENT_SHUTDOWN_REQUEST || s_power_key_queue == NULL) { + return; + } + + power_key_cmd_t cmd = POWER_KEY_CMD_SHUTDOWN_REQUEST; + BaseType_t queued = xQueueSend(s_power_key_queue, &cmd, 0); + if (queued == pdTRUE) { + return; + } + + power_key_cmd_t dropped = POWER_KEY_CMD_SHUTDOWN_REQUEST; + (void)xQueueReceive(s_power_key_queue, &dropped, 0); + (void)xQueueSend(s_power_key_queue, &cmd, 0); +} + +static void lifecycle_power_key_task_main(void *arg) { + (void)arg; + + while (true) { + power_key_cmd_t cmd = POWER_KEY_CMD_SHUTDOWN_REQUEST; + if (xQueueReceive(s_power_key_queue, &cmd, portMAX_DELAY) != pdTRUE) { + continue; + } + + if (cmd != POWER_KEY_CMD_SHUTDOWN_REQUEST) { + continue; + } + + ESP_LOGW(TAG, "POWER_KEY long press detected: stopping services and powering off"); + + esp_err_t stop_rc = ESP_ERR_INVALID_STATE; + for (uint16_t attempt = 0; attempt < POWER_KEY_STOP_RETRY_COUNT; ++attempt) { + stop_rc = control_plane_lifecycle_stop(); + if (stop_rc != ESP_ERR_INVALID_STATE) { + break; + } + vTaskDelay(pdMS_TO_TICKS(POWER_KEY_STOP_RETRY_INTERVAL_MS)); + } + if (stop_rc != ESP_OK && stop_rc != ESP_ERR_INVALID_STATE) { + ESP_LOGW(TAG, "lifecycle stop during power-off failed: %s", esp_err_to_name(stop_rc)); + } + + // Keep a tiny gap so last logs/stop actions can flush before hold release. + vTaskDelay(pdMS_TO_TICKS(50)); + + esp_err_t power_rc = system_runtime_power_off(); + if (power_rc != ESP_OK && power_rc != ESP_ERR_NOT_SUPPORTED) { + ESP_LOGE(TAG, "system runtime power-off failed: %s", esp_err_to_name(power_rc)); + } + } +} + +static esp_err_t lifecycle_power_key_worker_init(void) { + if (s_power_key_queue == NULL) { + s_power_key_queue = xQueueCreate(POWER_KEY_QUEUE_LEN, sizeof(power_key_cmd_t)); + if (s_power_key_queue == NULL) { + return ESP_ERR_NO_MEM; + } + } + + if (s_power_key_task == NULL) { + BaseType_t ok = xTaskCreate(lifecycle_power_key_task_main, + "power_key_ctrl", + POWER_KEY_TASK_STACK, + NULL, + 5, + &s_power_key_task); + if (ok != pdPASS) { + vQueueDelete(s_power_key_queue); + s_power_key_queue = NULL; + return ESP_ERR_NO_MEM; + } + } + + return system_runtime_set_power_event_callback(lifecycle_power_key_event_cb, NULL); +} + +static void lifecycle_power_key_worker_unbind(void) { + (void)system_runtime_set_power_event_callback(NULL, NULL); +} + typedef esp_err_t (*lifecycle_step_fn_t)(void); static esp_err_t lifecycle_run_step_with_retry(const char *stage, lifecycle_step_fn_t fn) { @@ -162,6 +257,12 @@ esp_err_t control_plane_lifecycle_start(void) { } system_started = true; + failed_stage = "power_key_binding"; + rc = lifecycle_power_key_worker_init(); + if (rc != ESP_OK) { + goto start_failed; + } + failed_stage = "printer_protocol"; rc = lifecycle_run_step_with_retry("printer_protocol", lifecycle_start_printer_protocol); if (rc != ESP_OK) { @@ -194,6 +295,7 @@ start_failed: (void)lifecycle_stop_printer_protocol(); } if (system_started) { + lifecycle_power_key_worker_unbind(); (void)lifecycle_stop_system_runtime(); } @@ -228,6 +330,8 @@ esp_err_t control_plane_lifecycle_stop(void) { lifecycle_set_state_locked(CONTROL_PLANE_LIFECYCLE_STATE_STOPPING, ESP_OK, "stopping"); xSemaphoreGive(s_lock); + lifecycle_power_key_worker_unbind(); + esp_err_t first_err = ESP_OK; esp_err_t rc = lifecycle_stop_voice(); diff --git a/components/domain/include/domain.h b/components/domain/include/domain.h index e47b0b7..3ca6425 100644 --- a/components/domain/include/domain.h +++ b/components/domain/include/domain.h @@ -15,6 +15,15 @@ esp_err_t system_runtime_start(void); esp_err_t system_runtime_shutdown(void); esp_err_t system_runtime_bootstrap(void); +typedef enum { + DOMAIN_POWER_EVENT_SHUTDOWN_REQUEST = 0, +} domain_power_event_t; + +typedef void (*domain_power_event_cb_t)(domain_power_event_t event, void *user_data); + +esp_err_t system_runtime_set_power_event_callback(domain_power_event_cb_t cb, void *user_data); +esp_err_t system_runtime_power_off(void); + bool system_runtime_wifi_ready(void); void system_runtime_get_ip(char *buf, size_t buf_len); diff --git a/components/domain/src/system_runtime.c b/components/domain/src/system_runtime.c index dff2e99..8061d7d 100644 --- a/components/domain/src/system_runtime.c +++ b/components/domain/src/system_runtime.c @@ -4,12 +4,60 @@ #include #include "esp_log.h" +#include "freertos/FreeRTOS.h" static const char *TAG = "system_runtime"; #define SYSTEM_RUNTIME_WIFI_HINT_TIMEOUT_MS 220 static bool s_wifi_hint_shown; +static bool s_power_key_ready; +static domain_power_event_cb_t s_power_event_cb; +static void *s_power_event_user_data; +static portMUX_TYPE s_power_event_lock = portMUX_INITIALIZER_UNLOCKED; + +static void system_runtime_dispatch_power_event(domain_power_event_t event) { + domain_power_event_cb_t cb = NULL; + void *user_data = NULL; + + portENTER_CRITICAL(&s_power_event_lock); + cb = s_power_event_cb; + user_data = s_power_event_user_data; + portEXIT_CRITICAL(&s_power_event_lock); + + if (cb != NULL) { + cb(event, user_data); + } +} + +static void system_runtime_power_key_platform_cb(platform_power_key_event_t event, void *user_data) { + (void)user_data; + if (event == PLATFORM_POWER_KEY_EVENT_LONG_PRESS) { + system_runtime_dispatch_power_event(DOMAIN_POWER_EVENT_SHUTDOWN_REQUEST); + } +} + +static esp_err_t system_runtime_power_key_init(void) { + if (s_power_key_ready) { + return ESP_OK; + } + + esp_err_t err = platform_power_key_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "platform_power_key_init failed: %s", esp_err_to_name(err)); + return err; + } + + err = platform_power_key_set_event_callback(system_runtime_power_key_platform_cb, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "platform_power_key_set_event_callback failed: %s", esp_err_to_name(err)); + platform_power_key_deinit(); + return err; + } + + s_power_key_ready = true; + return ESP_OK; +} static void system_runtime_show_wifi_hint(void) { if (s_wifi_hint_shown) { @@ -60,6 +108,11 @@ esp_err_t system_runtime_start(void) { return err; } + err = system_runtime_power_key_init(); + if (err != ESP_OK) { + return err; + } + s_wifi_hint_shown = false; (void)wifi_manager_set_event_callback(system_runtime_wifi_event_handler, NULL); system_runtime_try_show_wifi_hint_for_unprovisioned(); @@ -75,10 +128,28 @@ esp_err_t system_runtime_start(void) { } esp_err_t system_runtime_shutdown(void) { + if (s_power_key_ready) { + platform_power_key_deinit(); + s_power_key_ready = false; + } + (void)wifi_manager_set_event_callback(NULL, NULL); return wifi_manager_stop(); } +esp_err_t system_runtime_set_power_event_callback(domain_power_event_cb_t cb, void *user_data) { + portENTER_CRITICAL(&s_power_event_lock); + s_power_event_cb = cb; + s_power_event_user_data = user_data; + portEXIT_CRITICAL(&s_power_event_lock); + return ESP_OK; +} + +esp_err_t system_runtime_power_off(void) { + ESP_LOGW(TAG, "system power-off requested: releasing POWER_HOLD"); + return platform_power_hold_set(false); +} + esp_err_t system_runtime_bootstrap(void) { return system_runtime_start(); } diff --git a/components/platform/CMakeLists.txt b/components/platform/CMakeLists.txt index 4123ef4..e29a689 100644 --- a/components/platform/CMakeLists.txt +++ b/components/platform/CMakeLists.txt @@ -8,6 +8,7 @@ idf_component_register( "src/display_lcd_decode_image.c" "src/display_st7789p3.c" "src/platform_mic_key.c" + "src/platform_power_key.c" "src/voice_audio.c" "src/runtime_policy.c" INCLUDE_DIRS diff --git a/components/platform/include/platform.h b/components/platform/include/platform.h index 8022f48..26b15f8 100644 --- a/components/platform/include/platform.h +++ b/components/platform/include/platform.h @@ -90,6 +90,21 @@ esp_err_t platform_mic_key_init(void); void platform_mic_key_deinit(void); esp_err_t platform_mic_key_set_event_callback(platform_mic_key_event_cb_t cb, void *user_data); +// ---------- power_key ---------- +typedef enum { + PLATFORM_POWER_KEY_EVENT_LONG_PRESS = 0, + PLATFORM_POWER_KEY_EVENT_PRESS_UP, +} platform_power_key_event_t; + +typedef void (*platform_power_key_event_cb_t)(platform_power_key_event_t event, void *user_data); + +esp_err_t platform_power_key_init(void); +void platform_power_key_deinit(void); +esp_err_t platform_power_key_set_event_callback(platform_power_key_event_cb_t cb, void *user_data); + +// Controls the board hold line: true keeps power on, false releases hold. +esp_err_t platform_power_hold_set(bool hold_on); + // ---------- voice_audio ---------- typedef struct { uint32_t sample_rate; diff --git a/components/platform/src/platform_bootstrap.c b/components/platform/src/platform_bootstrap.c index eedc10d..fb0f9bc 100644 --- a/components/platform/src/platform_bootstrap.c +++ b/components/platform/src/platform_bootstrap.c @@ -239,3 +239,23 @@ esp_err_t platform_bootstrap_init(void) { s_initialized = true; return ESP_OK; } + +esp_err_t platform_power_hold_set(bool hold_on) { + if (CONFIG_TQ_POWER_HOLD_PIN < 0) { + return ESP_ERR_NOT_SUPPORTED; + } + + int level = hold_on ? 1 : 0; + if (!s_board_gpio_initialized) { + return board_gpio_config_output(CONFIG_TQ_POWER_HOLD_PIN, level, "power_hold"); + } + + esp_err_t err = gpio_set_level(CONFIG_TQ_POWER_HOLD_PIN, level); + if (err != ESP_OK) { + ESP_LOGE(TAG, "gpio_set_level failed: power_hold pin=%d level=%d err=%s", + CONFIG_TQ_POWER_HOLD_PIN, + level, + esp_err_to_name(err)); + } + return err; +} diff --git a/components/platform/src/platform_power_key.c b/components/platform/src/platform_power_key.c new file mode 100644 index 0000000..9ed95f1 --- /dev/null +++ b/components/platform/src/platform_power_key.c @@ -0,0 +1,125 @@ +#include "platform.h" + +#include + +#include "button_gpio.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "iot_button.h" +#include "sdkconfig.h" + +static const char *TAG = "platform_power_key"; +static const uint16_t POWER_KEY_SHORT_PRESS_MS = 40; +static const uint16_t POWER_KEY_LONG_PRESS_MS = 2000; + +static button_handle_t s_power_key_button; +static platform_power_key_event_cb_t s_event_cb; +static void *s_event_cb_user_data; +static portMUX_TYPE s_event_cb_lock = portMUX_INITIALIZER_UNLOCKED; + +static void power_key_dispatch_event(platform_power_key_event_t event) { + platform_power_key_event_cb_t cb = NULL; + void *user_data = NULL; + + portENTER_CRITICAL(&s_event_cb_lock); + cb = s_event_cb; + user_data = s_event_cb_user_data; + portEXIT_CRITICAL(&s_event_cb_lock); + + if (cb != NULL) { + cb(event, user_data); + } +} + +static void power_key_long_press_cb(void *button_handle, void *usr_data) { + (void)button_handle; + (void)usr_data; + power_key_dispatch_event(PLATFORM_POWER_KEY_EVENT_LONG_PRESS); +} + +static void power_key_press_up_cb(void *button_handle, void *usr_data) { + (void)button_handle; + (void)usr_data; + power_key_dispatch_event(PLATFORM_POWER_KEY_EVENT_PRESS_UP); +} + +esp_err_t platform_power_key_init(void) { + if (CONFIG_TQ_POWER_KEY_PIN < 0) { + ESP_LOGW(TAG, "POWER_KEY disabled by config: pin=%d", CONFIG_TQ_POWER_KEY_PIN); + return ESP_ERR_NOT_SUPPORTED; + } + + if (s_power_key_button != NULL) { + return ESP_OK; + } + + button_config_t button_cfg = { + .long_press_time = POWER_KEY_LONG_PRESS_MS, + .short_press_time = POWER_KEY_SHORT_PRESS_MS, + }; + button_gpio_config_t gpio_cfg = { + .gpio_num = CONFIG_TQ_POWER_KEY_PIN, + .active_level = 0, + .enable_power_save = false, + .disable_pull = false, + }; + + esp_err_t err = iot_button_new_gpio_device(&button_cfg, &gpio_cfg, &s_power_key_button); + if (err != ESP_OK) { + ESP_LOGE(TAG, + "iot_button_new_gpio_device failed: pin=%d err=%s", + CONFIG_TQ_POWER_KEY_PIN, + esp_err_to_name(err)); + return err; + } + + err = iot_button_register_cb(s_power_key_button, + BUTTON_LONG_PRESS_START, + NULL, + power_key_long_press_cb, + NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "register BUTTON_LONG_PRESS_START failed: %s", esp_err_to_name(err)); + (void)iot_button_delete(s_power_key_button); + s_power_key_button = NULL; + return err; + } + + err = iot_button_register_cb(s_power_key_button, BUTTON_PRESS_UP, NULL, power_key_press_up_cb, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "register BUTTON_PRESS_UP failed: %s", esp_err_to_name(err)); + (void)iot_button_delete(s_power_key_button); + s_power_key_button = NULL; + return err; + } + + ESP_LOGI(TAG, + "POWER_KEY initialized: gpio=%d active_level=low long_press_ms=%u", + CONFIG_TQ_POWER_KEY_PIN, + (unsigned)POWER_KEY_LONG_PRESS_MS); + return ESP_OK; +} + +void platform_power_key_deinit(void) { + if (s_power_key_button != NULL) { + (void)iot_button_delete(s_power_key_button); + s_power_key_button = NULL; + } + + portENTER_CRITICAL(&s_event_cb_lock); + s_event_cb = NULL; + s_event_cb_user_data = NULL; + portEXIT_CRITICAL(&s_event_cb_lock); +} + +esp_err_t platform_power_key_set_event_callback(platform_power_key_event_cb_t cb, void *user_data) { + if (s_power_key_button == NULL) { + return ESP_ERR_INVALID_STATE; + } + + portENTER_CRITICAL(&s_event_cb_lock); + s_event_cb = cb; + s_event_cb_user_data = user_data; + portEXIT_CRITICAL(&s_event_cb_lock); + return ESP_OK; +} diff --git a/docs/gpio-map.md b/docs/gpio-map.md index d36bbe3..ce1db36 100644 --- a/docs/gpio-map.md +++ b/docs/gpio-map.md @@ -1,10 +1,10 @@ # ESP32-S3 GPIO Mapping -本文件记录当前板级 GPIO 分配,来源于硬件对接表(2026-02-26)。 +本文件按当前固件实现校对(代码 + 当前 `sdkconfig`),审计日期:2026-03-02。 -## GPIO Table +## GPIO Table (Current Build) -| Board Pin | GPIO | Signal | Direction | Notes | +| Board Pin | GPIO | Signal | Direction (Current Build) | Notes | | --- | --- | --- | --- | --- | | 1 | GND | GND | - | Ground | | 2 | 3V3 | 3V3 | - | 3.3V Power | @@ -15,7 +15,7 @@ | 7 | IO7 | ES8311_I2C_SCL | Output | Voice codec I2C SCL | | 8 | IO15 | BATTERY_ADC | Input | Battery voltage ADC detect | | 9 | IO16 | PRINT_STB1_2 | Output | Printer strobe 1/2 | -| 10 | IO17 | PRINT_PAPER | Input | Paper detect | +| 10 | IO17 | PRINT_PAPER | Input | Paper-present level is controlled by `CONFIG_TQ_PRINTER_PAPER_PRESENT_LEVEL` (current: `0`, low means paper present) | | 11 | IO18 | NTC_ADC | Input | NTC ADC | | 12 | IO8 | PRINT_OUTA_P | Output | Printer OUTA+ | | 13 | IO19 | PRINT_OUTA_N | Output | Printer OUTA- | @@ -23,16 +23,16 @@ | 15 | IO3 | PRINT_OUTB_P | Output | Printer OUTB+ | | 16 | IO46 | PRINT_OUTB_N | Output | Printer OUTB- | | 17 | IO9 | SPI_SHARED_MOSI | Output | Printer/Screen shared MOSI | -| 18 | IO10 | PRINTER_MISO | Input | Printer MISO | +| 18 | IO10 | PRINTER_MISO | Input | Shared SPI MISO used by current screen driver config | | 19 | IO11 | SPI_SHARED_CLK | Output | Printer/Screen shared CLK | | 20 | IO12 | SCREEN_DC | Output | ST7789 DC | -| 21 | IO13 | SCREEN_CS1 | Output | ST7789 CS | -| 22 | IO14 | MIC_KEY | Input | Mic key (can ADC-mux 2 keys if needed) | +| 21 | IO13 | SCREEN_CS (SCREEN_CS1) | Output | ST7789 CS | +| 22 | IO14 | MIC_KEY | Input | Mic key (supports ADC-mux extension path) | | 23 | IO21 | LED_G | Output | Green LED | -| 24 | IO47 | POWER_KEY | Input | Hold low for 2s to request power-off logic | -| 25 | IO48 | SCREEN_BACKLIGHT | Output | High level turns backlight on | +| 24 | IO47 | POWER_KEY | Input | Active-low power key, long press 2s triggers lifecycle stop and power-off hold release | +| 25 | IO48 | SCREEN_BACKLIGHT | Output | Backlight active level by `CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH` (current: `y`, high = on) | | 26 | IO45 | PRINT_LAT | Output | Printer LAT | -| 27 | IO0 | KEY_PRINT / BOOST_CTRL | Input/Output | Print confirm key or 7.2V boost control | +| 27 | IO0 | KEY_PRINT / BOOST_CTRL | Output (current) / Input (optional) | Current build uses boost mode at boot (`CONFIG_TQ_KEY_PRINT_BOOST_ENABLE_ON_BOOT=y`), so this pin is driven as boost control | | 28 | IO35 | N/A | - | Not available | | 29 | IO36 | N/A | - | Not available | | 30 | IO37 | N/A | - | Not available | @@ -48,18 +48,46 @@ | 40 | GND | GND | - | Ground | | 41 | GND | GND | - | Ground | -## Firmware Mapping Notes +## Additional Runtime Pin Behavior -- `main/Kconfig.projbuild` contains board pin configs prefixed with `TQ_*`. +| Logical Signal | Config | Current Value | Behavior | +| --- | --- | --- | --- | +| SCREEN_RESET | `CONFIG_TQ_SCREEN_RESET_PIN` | `-1` | No dedicated reset pin. Runtime falls back to `CONFIG_TQ_SCREEN_BACKLIGHT_PIN` (`IO48`) as reset pin. | + +## Firmware Mapping Notes (Current Implementation) + +- Board pin mapping is configured through `main/Kconfig.projbuild` (`CONFIG_TQ_*`) and resolved from `sdkconfig` at build time. +- GPIO usage is config-driven. Current code path does not rely on `GPIO_NUM_xx` hardcoded pin numbers. - `components/platform/src/platform_bootstrap.c` initializes critical GPIO on startup: - - `POWER_HOLD` asserted high early. - - `SCREEN_DC/SCREEN_CS/SCREEN_BACKLIGHT` default states. - - `KEY_PRINT` defaults to boost-enable output mode (`CONFIG_TQ_KEY_PRINT_BOOST_ENABLE_ON_BOOT=y`). - - key/sensor inputs (`POWER_KEY`, `MIC_KEY`, `PRINT_PAPER`, ADC inputs) default directions. -- `components/platform/src/display_st7789.c` provides ST7789 driver integration: - - SPI bus (`MOSI=IO9`, `CLK=IO11`) + `esp_lcd_new_panel_st7789`. - - Optional boot test pattern via `CONFIG_TQ_SCREEN_TEST_PATTERN_ON_BOOT`. -- Voice codec and I2S pin defaults are aligned to this table: - - I2C: `SDA=6`, `SCL=7` - - I2S: `MCLK=38`, `BCLK=39`, `WS=41`, `DIN=40`, `DOUT=42` - - PA: `IO20` + - `POWER_HOLD` is asserted high early. + - `LED_R` and `LED_G` default low. + - `SCREEN_CS/DC/BACKLIGHT` are initialized with configured defaults. + - `KEY_PRINT` is output boost control when `CONFIG_TQ_KEY_PRINT_BOOST_ENABLE_ON_BOOT=y`, otherwise input with pull-up. + - `POWER_KEY`, `MIC_KEY`, `PRINT_PAPER` default to input with pull-up. +- Physical power-key handling uses `managed_components/espressif__button`: + - Platform layer (`platform_power_key.c`) reports long-press events (`2s`, active-low). + - Control-plane consumes the shutdown request and performs graceful stop, then calls `platform_power_hold_set(false)` through `system_runtime_power_off()`. +- Thermal printer runtime behavior (`components/platform/src/thermal_printer.c`): + - `STB` lines are active-high. + - `LAT` idles high and is pulsed low-high. + - Shared `MOSI/CLK` are temporarily switched to GPIO bit-bang for printing. + - Paper detect compares GPIO level against `runtime_policy_printer_paper_present_level()`. +- Display stack is: + - `platform_display_*` (`display_st7789.c`) -> `lcd_module_*` (`display_lcd_module.c`) -> `st7789p3_*` (`display_st7789p3.c`). + - Current implementation no longer describes panel setup in `display_st7789.c` directly. + - LCD path includes shared-SPI recovery after printer takes over shared pins. +- Voice path (`components/platform/src/voice_audio.c`): + - I2C defaults to `SDA=6`, `SCL=7`, with internal pull-up enabled in both I2C code paths. + - I2S defaults to `MCLK=38`, `BCLK=39`, `WS=41`, `DOUT=42`, `DIN=40`. + - `PA` control defaults to `IO20` and polarity can be flipped by `CONFIG_TQ_VOICE_CODEC_PA_PIN_REVERSED`. + +## Condition-Driven Behavior (Important) + +| Config | Current | Impact | +| --- | --- | --- | +| `CONFIG_TQ_KEY_PRINT_BOOST_ENABLE_ON_BOOT` | `y` | `KEY_PRINT/BOOST_CTRL` works as output boost-control at boot/runtime instead of key input | +| `CONFIG_TQ_KEY_PRINT_BOOST_ACTIVE_HIGH` | `y` | Boost enable level is high | +| `CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH` | `y` | Backlight ON level is high | +| `CONFIG_TQ_SCREEN_RESET_PIN` | `-1` | Screen reset falls back to backlight pin | +| `CONFIG_TQ_SCREEN_ENABLE` | `y` | Screen driver and its GPIO paths are compiled in | +| `CONFIG_TQ_PRINTER_PAPER_PRESENT_LEVEL` | `0` | Paper detect treats GPIO low as paper present |