AI-assisted ESP-IDF + LVGL firmware for the JC3636K518C round knob display
Ten rungs. Each one is small enough to finish with your AI in 10–15 minutes and leaves the firmware building, flashable and host-tested. Climb in order; rung 6 and rung 10 are where the guardrails earn their keep.
Template layout (what the scaffold gives you):
components/board/ pins and capabilities — the ONLY place a GPIO number may appear
components/ui/ display + touch + LVGL bring-up, knob poll + pure decoder, backlight
components/theme/ design tokens and the colour = state policy (no LVGL dependency)
components/haptic/ DRV2605L click
components/net/ Wi-Fi join, HTTP GET, streamed HTTP PUT through one 16 KB PSRAM chunk
components/app/ the state machine: knob/touch events in, theme/haptic/net calls out
main/main.c boot order: NVS → ui → haptic → net → app
test/host/ gcc + Unity suites for the pure modules; scripts/test.sh runs them
scripts/ build.sh (Docker), flash.sh (host esptool --no-stub), test.sh, check-board-pins.sh
API you will touch: knob_init(cb), haptic_click(), theme_state_color(state),
theme_state_motion_ms(state), net_init(), net_is_connected(), net_rssi(),
net_http_get(url, buf, cap), net_http_put_stream(url, total_len, rewind_cb, read_cb, ctx, progress_cb).
Receiver for rungs 9–10: python3 templates/receiver/receiver.py --port 8080 on your laptop
(same Wi-Fi as the knob). It prints bytes received, throughput, and which byte ranges it holds.
The template already runs a title, a knob counter, a tap toggle, a state ring, a haptic click
and an RSSI line. Every rung adapts that working app; nothing is built from nothing.
Widgets live in components/app/app_screen.c, behaviour in components/app/app.c.
app_screen.c, set the title to ‘lv_font_montserrat_28 or LVGL 8 calls (lv_obj_set_style_local_*).
The template pins LVGL 9.6.components/app/app_screen.c.on_knob_step in app.c wrap the counter at 0..99 in both directions.”knob_decode.c with PCNT or a quadrature driver.
This knob is not quadrature; keep the decoder.components/app/app.c.lv_obj circle at the bottom of the safe circle with its own
LV_EVENT_SHORT_CLICKED handler that flips a bool and re-renders.”LV_EVENT_CLICKED also fires after a long press, and the screen already uses
LV_EVENT_LONG_PRESSED. Handlers never call into net or block.components/app/app_screen.c, components/app/app.c.theme_state_color()
and the motion period from theme_state_motion_ms() for a breathing ring or arc.theme_state_color() and animate its opacity with
the state’s motion period.”0xC0392B in app.c; or an animation that redraws the full screen every
frame (no TE line → tearing, and the QSPI bus is the bottleneck).components/app/app.c, components/theme/ only if you add a state.haptic_play() effect per state transition, exactly once each.”components/app/app.c.test/host/ covers it.scripts/test.sh runs your new suite green, and scripts/check-board-pins.sh
still passes.app_logic.c with no hardware
includes, and add test/host/test_app_logic.c following test_knob_decode.c.”components/app/app_logic.c, test/host/test_app_logic.c, scripts/test.sh.app.c to its own 1 s lv_timer, append ‘weak’ below
−75 dBm, and log the internal heap once after the first IP.”wifi_secrets.h (it is gitignored, keep it so) or polls Wi-Fi from
the LVGL task with a blocking wait.components/net/include/wifi_secrets.h, components/app/app.c.net_http_get() a URL into a 1 KB
buffer, parse one field with cJSON, and show it.”components/app/app.c, maybe components/net/net_http.c.net_http_put_stream() with a read callback that fills the shared chunk
from a counter pattern; update an arc from the progress callback; log
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL) periodically.”malloc(2 * 1024 * 1024) in internal RAM; building the whole body before sending;
forgetting Content-Length so the server rejects the stream.components/app/app.c, components/net/.upload_plan.c, ask the receiver
which parts it has, and retry the missing ones with a bounded retry and delay.”components/net/, components/app/app.c, test/host/test_upload_plan.c.Good prompts (specific, name the file, state the constraint):
components/board/include/board_jc3636k518c.h first; use only those macros for pins.”esp_lvgl_port 2.x exposes; do not use LVGL 8 style functions.”scripts/build.sh and scripts/test.sh and show me the output before you say it works.”heap_caps_malloc(.., MALLOC_CAP_SPIRAM) and explain why.”Bad prompts (and why they bite):