Skip to content

ESP32S3-8048S070C Factory App — Input Module

ESP32S3-8048S070C Factory App — Input Module

Section titled “ESP32S3-8048S070C Factory App — Input Module”

The esp32s3_8048s070c_factory_app_input module provides all human-interface input handling for the ESP32-S3 8048S070C board’s factory/recovery application. It consolidates four independent input channels — physical buttons, rotary encoder, capacitive touch, and buzzer feedback — into a polled, non-blocking interface consumed by the main recovery loop.

Board context: The ESP32-S3 8048S070C is a 7-inch 800×480 RGB panel board with a GT911 capacitive touchscreen. On this specific hardware variant, BUTTON_n_PIN, ENCODER_A/B_PIN, and BUZZER_PIN are all defined as GPIO_NUM_NC — the buttons, encoder, and buzzer are electrically absent. Touch is the sole active input. All drivers include GPIO_NUM_NC guards, so they degrade gracefully to no-ops rather than faulting, and the code stays portable across board variants that do populate these peripherals.


  1. Module Structure
  2. Architecture Overview
  3. Component Reference
  4. Data Flow
  5. Process Flows
  6. Cross-Board Compatibility (GPIO_NUM_NC Guards)
  7. Integration with the Factory App
  8. Hardware Configuration Reference

boards/esp32s3_8048s070c/Factory/main/
├── buttons.c # GPIO-based 3-button driver with debounce
├── encoder.c # Hardware PCNT rotary encoder (quadrature)
├── touch.c # GT911 I2C capacitive touch (polling, rescaled)
├── touch.h # touch_point_t type + public API
└── buzzer.c # Bit-bang square-wave buzzer feedback

This module is a child of esp32s3_8048s070c_factory_app. Its output feeds exclusively into the main recovery menu loop documented in esp32s3_8048s070c_factory_app_main. The analogous module on the PiBot pendant board is pibot_pendant_v1_0_factory_app_input — the driver code is architecturally identical; only hw_config.h pin assignments differ.


esp32s3_8048s070c_factory_app_display

esp32s3_8048s070c_factory_app_main

Hardware Peripheral Drivers

esp32s3_8048s070c_factory_app_input

button_id_t

int clicks

touch_point_t

button_id_t

visual feedback

audible feedback

buttons.c

GPIO polling + debounce

(BTN_1 / BTN_2 / BTN_3)

encoder.c

PCNT quadrature decoder

(CW / CCW clicks)

touch.c

GT911 I2C polling

+ coordinate rescale

buzzer.c

Bit-bang beep

2700 Hz / 40 ms

touch_gt911 component

hardware/common/drivers/touch_gt911

bus_i2c component

hardware/common/drivers/bus_i2c

ESP-IDF PCNT driver

driver/pulse_cnt.h

ESP-IDF GPIO driver

driver/gpio.h

app_main() loop

100 ms poll cycle

dispatch_button()

menu_move() / execute()

touch_hint_hit_test()

zone to button_id_t

draw_button_hints()

draw_button_hint_pressed()


A lightweight GPIO input driver for up to three physical buttons. All buttons are active-LOW with internal pull-ups enabled.

FunctionSignatureDescription
buttons_initvoid buttons_init(void)Configures GPIO pins as inputs with pull-ups. No-op if all pins are GPIO_NUM_NC.
button_is_pressedbool button_is_pressed(button_id_t btn)Instantaneous non-blocking level check. Returns true when the GPIO reads LOW.
button_wait_pressbutton_id_t button_wait_press(uint32_t timeout_ms)Blocking poll: waits for a press+release cycle with debounce. Returns BTN_NONE on timeout. Pass 0 for infinite wait.
pin_bit (internal)static uint64_t pin_bit(gpio_num_t pin)Safe bitmask helper — returns 0 for GPIO_NUM_NC, avoiding undefined shift behaviour on negative pin numbers.
typedef enum { BTN_NONE = -1, BTN_1 = 0, BTN_2 = 1, BTN_3 = 2 } button_id_t;
ButtonRecovery Menu Action
BTN_1Navigate up (menu_move(-1))
BTN_2Navigate down (menu_move(+1))
BTN_3Execute selected item
ConstantValuePurpose
DEBOUNCE_MS50 msApplied twice: on press detection and after release
poll_ms20 msScan interval inside button_wait_press
gpio_config_t io_conf = {
.pin_bit_mask = /* union of valid pin bits */,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE, // polling, no ISR
};

Uses the ESP32-S3 hardware PCNT (Pulse Counter) peripheral for noise-immune quadrature decoding. No FreeRTOS queue or ISR callback is used — the counter is polled from the main loop.

FunctionSignatureDescription
encoder_initesp_err_t encoder_init(void)Configures PCNT unit, two quadrature channels, glitch filter, and pull-ups. No-op if pins are GPIO_NUM_NC.
encoder_readint encoder_read(void)Returns accumulated clicks since last call. Positive = clockwise, negative = counter-clockwise, 0 = no movement. Clears the counter after reading.
ParameterValueNotes
High limit+1000Prevents wrap-around between 100 ms polls
Low limit−1000Symmetric
Glitch filter1000 nsMatches the main firmware’s board_init.c setting
Pulses per detent4Standard quadrature encoder (4 edges per physical click)
Channel A: edge_gpio = ENCODER_A_PIN, level_gpio = ENCODER_B_PIN
Channel B: edge_gpio = ENCODER_B_PIN, level_gpio = ENCODER_A_PIN
Edge actions (Channel A): DECREASE on rising, INCREASE on falling
Level actions (Channel A): KEEP on HIGH level, INVERSE on LOW level
Edge actions (Channel B): INCREASE on rising, DECREASE on falling
Level actions (Channel B): KEEP on HIGH level, INVERSE on LOW level

This configuration produces 4 PCNT pulses per physical detent, making clicks = raw_count / 4.

int enc = encoder_read();
if (enc > 0) menu_move(-1); // CW → up
else if (enc < 0) menu_move(+1); // CCW → down

A thin polling wrapper over the shared touch_gt911 hardware component — the same driver the main firmware uses for this board via board_init.c. No custom GT911 register access is implemented; the component is reused as-is.

typedef struct {
bool pressed; // true when screen is touched
int16_t x; // pixel X, scaled to [0, SCREEN_WIDTH)
int16_t y; // pixel Y, scaled to [0, SCREEN_HEIGHT)
} touch_point_t;

When pressed == false, x and y are -1.

FunctionSignatureDescription
touch_initvoid touch_init(void)Initialises I2C bus and configures the GT911 controller.
touch_readtouch_point_t touch_read(void)Non-blocking: reads GT911 state and returns a rescaled coordinate. Returns {false, -1, -1} if not initialised.

The GT911 on this panel reports internal raw coordinates that do not match the physical 800×480 resolution (measured ~468×253 on real hardware). The driver reads back the chip’s actual max values and rescales:

pt.x = (int)data.x * SCREEN_WIDTH / touch_gt911_get_x_max();
pt.y = (int)data.y * SCREEN_HEIGHT / touch_gt911_get_y_max();

This is identical to what the main firmware’s board_init.c does.

static const touch_gt911_config_t config = {
.i2c_addr = TOUCH_I2C_ADDR_LIST,
.i2c_port = TOUCH_I2C_PORT_IDX,
.i2c_clk_speed = TOUCH_I2C_FREQ_HZ,
.rst_pin = TOUCH_RST_PIN,
.int_pin = TOUCH_IRQ_PIN,
.swap_xy = TOUCH_SWAP_XY_FLAG,
.invert_x = TOUCH_MIRROR_X_FLAG,
.invert_y = TOUCH_MIRROR_Y_FLAG,
.x_max = 0, // auto-detected from GT911 config registers
.y_max = 0, // auto-detected from GT911 config registers
};

All TOUCH_* macros are defined in hw_config.h. For the shared GT911 component interface, see touch_drivers.


A minimal bit-bang driver that generates a short square-wave beep as audible confirmation for touch-button presses. Uses the same technique as the custom bootloader’s hooks.c — no PWM/LEDC peripheral needed.

FunctionSignatureDescription
buzzer_initvoid buzzer_init(void)Configures BUZZER_PIN as a GPIO output driven LOW. No-op if pin is GPIO_NUM_NC.
buzzer_beep_shortvoid buzzer_beep_short(void)Emits a single 40 ms beep at 2700 Hz by toggling the GPIO. Blocks for the full duration.
pin_bit (internal)static uint64_t pin_bit(gpio_num_t pin)Identical safe bitmask helper as in buttons.c.
ConstantValueFormula
BEEP_FREQ_HZ2700 HzFixed
BEEP_DURATION_MS40 msFixed
half_period_us~185 µs1 000 000 / (2 × 2700)
cycles1082700 × 40 / 1000

⚠ Blocking call: buzzer_beep_short() calls esp_rom_delay_us() in a tight loop and blocks for ~40 ms. It is called only from the main task after a touch event is processed. LVGL is not active in the factory app, so this does not create any UI responsiveness concern.


Main Recovery Loop (app_main)

Input Drivers - this module

Physical Hardware

yes

yes

yes

button_id_t

Physical buttons

(GPIO_NUM_NC on this board)

Rotary encoder

(GPIO_NUM_NC on this board)

GT911 touchscreen

(active on this board)

Buzzer

(GPIO_NUM_NC on this board)

button_wait_press(100)

returns button_id_t

encoder_read()

returns int clicks

touch_read()

returns touch_point_t

buzzer_beep_short()

enc not 0?

btn not BTN_NONE?

touch.pressed

and not was_pressed?

touch_hint_hit_test(x, y)

dispatch_button(btn)

menu_move() or

execute_selected_action()

draw_button_hint_pressed()

buzzer_beep_short()


No

No

Yes

Yes - BTN_n

No - glitch

Yes - confirmed

button_wait_press(timeout_ms) called

Poll all 3 buttons every 20 ms

Any button LOW?

Timeout expired?

return BTN_NONE

vTaskDelay 50 ms debounce

BTN_n still LOW?

Wait for BTN_n to go HIGH (release)

vTaskDelay 50 ms post-release debounce

return BTN_n

The double-debounce (press + release) ensures:

  • Contact bounce spikes during the press stroke are rejected.
  • The returned button ID is available only once the user has fully released the key, preventing repeated triggers on a single press.

The display module renders three virtual button icons (UP, DOWN, OK) at the bottom of the 800×480 screen at fixed horizontal centres BTN_HINT_CX1, BTN_HINT_CX2, BTN_HINT_CX3. The hit-test function maps a raw tap coordinate to the nearest icon using midpoint boundaries rather than equal thirds, because the icons are clustered around screen centre rather than spread across the full width.

No

Yes

Yes - above footer

No - in footer zone

Yes

No

Yes

No

touch_read() returns touch_point_t

touch.pressed

and not touch_was_pressed?

Update touch_was_pressed - continue loop

touch_hint_hit_test(touch.x, touch.y)

y less than BTN_HINT_BASE_Y?

return BTN_NONE

x less than (CX1+CX2)/2?

return BTN_1 (Up)

x less than (CX2+CX3)/2?

return BTN_2 (Down)

return BTN_3 (OK / Execute)

draw_button_hint_pressed(vbtn)

buzzer_beep_short()

vTaskDelay 80 ms - visual hold

draw_button_hints() - restore normal state

dispatch_button(vbtn)

The leading-edge detection (touch.pressed && !touch_was_pressed) ensures only the initial touch frame triggers an action, preventing continuous repeat while a finger rests on the screen.


No

Yes

No

Yes

Yes - CW

No - CCW (clicks < 0)

encoder_read() called

s_initialized?

return 0

pcnt_unit_get_count() returns raw count

clicks = raw_count / PULSES_PER_DETENT

clicks not 0?

return 0

pcnt_unit_clear_count()

return clicks

Main loop: clicks > 0?

menu_move(-1) - up

menu_move(+1) - down

Remainder loss: Clearing the counter discards pulses that have not yet accumulated into a full detent click. At the 100 ms poll interval this is negligible for normal user interaction speeds.


6. Cross-Board Compatibility (GPIO_NUM_NC Guards)

Section titled “6. Cross-Board Compatibility (GPIO_NUM_NC Guards)”

Every driver in this module contains an explicit guard against GPIO_NUM_NC (−1). This makes the same source code usable across all boards in the BSP family regardless of which peripherals are physically populated.

GPIO_NUM_NC guard pattern

Board variants using identical driver code

all pins valid

NC pins yield 0 or false

pibot_pendant_v1_0

Buttons: yes Encoder: yes

Buzzer: yes Touch: yes

esp32s3_8048s070c

Buttons: no Encoder: no

Buzzer: no Touch: yes

Future variant

(partial population)

pin_bit(pin)

returns 0 for invalid pin

buttons_init

pin_mask == 0 → early return

encoder_init

GPIO_IS_VALID false → early return

buzzer_init

GPIO_IS_VALID false → early return

buzzer_beep_short

GPIO_IS_VALID false → early return

pin_bit() — shared safe bitmask helper (duplicated in buttons.c and buzzer.c):

static uint64_t pin_bit(gpio_num_t pin) {
return GPIO_IS_VALID_GPIO(pin) ? (1ULL << pin) : 0ULL;
}

This avoids undefined behaviour from left-shifting by a negative number when pin is GPIO_NUM_NC (−1).


This module sits between raw hardware and the recovery menu logic. It has no awareness of menu state itself — it only reports input events.

esp32s3_8048s070c_factory_app

esp32s3_8048s070c_factory_app_storage

esp32s3_8048s070c_factory_app_display

esp32s3_8048s070c_factory_app_main

esp32s3_8048s070c_factory_app_input - this module

button_id_t

int clicks

touch_point_t

restore normal

press feedback

audible feedback

init sequence

sdcard_mount / sdcard_unmount

buttons.c

app_main()

encoder.c

touch.c

dispatch_button()

menu_move()

execute_selected_action()

draw_button_hints()

draw_button_hint_pressed()

buzzer.c

Initialisation order in app_main():

  1. restore_otadata_from_backup() — partition management
  2. Display init (ek9716_init, gfx_init)
  3. buttons_init()
  4. encoder_init()
  5. touch_init()
  6. buzzer_init()
  7. Menu construction + draw_menu()
  8. Enter the main poll loop

Main loop poll cycle (~100 ms):

StepCallBlocking?Notes
1encoder_read()NoChecked first to keep encoder responsive
2button_wait_press(100)Yes, up to 100 msYields CPU via vTaskDelay throughout
3touch_read()NoLeading-edge detection via touch_was_pressed

All pin assignments are defined in hw_config.h (not part of this module). The table below reflects the values effective for this board:

MacroValue on this boardDriver
BUTTON_1_PINGPIO_NUM_NCbuttons.c
BUTTON_2_PINGPIO_NUM_NCbuttons.c
BUTTON_3_PINGPIO_NUM_NCbuttons.c
ENCODER_A_PINGPIO_NUM_NCencoder.c
ENCODER_B_PINGPIO_NUM_NCencoder.c
BUZZER_PINGPIO_NUM_NCbuzzer.c
TOUCH_I2C_PORT_IDXboard-definedtouch.c
TOUCH_I2C_SDA_PINboard-definedtouch.c
TOUCH_I2C_SCL_PINboard-definedtouch.c
TOUCH_I2C_FREQ_HZboard-definedtouch.c
TOUCH_I2C_ADDR_LISTboard-definedtouch.c
TOUCH_RST_PINboard-definedtouch.c
TOUCH_IRQ_PINboard-definedtouch.c
SCREEN_WIDTH800touch.c — rescale target
SCREEN_HEIGHT480touch.c — rescale target

Related documentation: