TuyaOpen Hardware Vibe Coding
Architecture
TDD (driver registration) → TDL (device management) → App (business logic).
board_register_hardware() registers only the device peripherals the board
already adapted — the ones listed in board-context.md. For those, do not
write TDD code. Any peripheral the user attaches themselves (not in
board-context.md) you must register yourself in usr_board, even when
the SDK already ships its TDD driver — you just call that existing driver with
your pins. Writing a brand-new TDD driver (IC with no SDK driver) is only a
sub-case of usr_board, never the trigger for it. On-chip peripherals are the
other exception (call tal_*/tkl_* directly). See Rules.
Read these files first (your hardware context)
This skill is the authority. Read these yourself before doing anything:
.tuyaopen/used-peripherals.json— the already-confirmed hardware (deviceID:s and/oronchip:<type><n>s). This is your INPUT: generate code for exactly these. Empty/missing → nothing confirmed yet..tuyaopen/board-context.md— a slim index of the board's device peripherals (display/camera/led/button/audio/touch/printer/…): each device'sID:, enableKconfig:, grouping, and per-group registration semantics. Raw per-device data (pins / driver IC / resolution / timing) is not here — read it from.tuyaopen/ide/board.json(the same JSON, full detail)..tuyaopen/ide/platform.json→peripherals— on-chip support + specs (uart/gpio/pwm/i2c/spi/qspi/adc/timer/watchdog/rtc/dma2d/vad/kws). For each type:enabled— does this SoC support it. Iffalse, do NOT generate code for it; tell the user it's unsupported on this platform.count+spec— instance count and valid config (ports, pins, ranges, enums). Read these instead of hardcoding; values differ per platform. Consult before writing any on-chip code.
.tuyaopen/ide/board.json→expansionPins— the only pins the developer may freely wire (the board's broken-out pins), each with thefunctionsit can mux to (GPIO,UART2_TX,I2C1_SCL, …). This — not the full SoC pinout — is the basis for "available pins". Empty/absent → the board is sealed: there are NO free pins; you can only use already-wired onboard peripherals.
Rules (must follow)
- Confirm before code (hard gate). Generate code ONLY for hardware listed in
used-peripherals.json. If the request needs something not yet confirmed, or the catalog has multiple matches / any## group —, ASK the user which one and wait — never auto-pick. When asking the user to choose, prefer an interactive multiple-choice prompt if your tooling supports one (otherwise list the options as text). - Record before code. As soon as the selection is settled, full-overwrite
used-peripherals.json(Step 3) BEFORE writing any code; update it if the set changes. - Pick user-peripheral pins from
expansionPinsonly. When you choose a pin for a user/on-chip peripheral or a usr_board device, pick agpiofromboard.json.expansionPinswhosefunctionsinclude the role you need (e.g. a UART2 TX pin must listUART2_TX; a plain output needsGPIO). Never pick a pin that is not inexpansionPins— it isn't physically accessible. IfexpansionPinsis empty/absent, tell the user the board exposes no free pins and you cannot wire an external peripheral. - "串口 / serial / UART" is ambiguous → ASK first. It may mean the debug/log
console (
PR_*— often a USB-serial the PC already sees; no peripheral, nothing recorded) OR a dedicated user UART (tal_uarton its own instance + pins —onchip:uart<N>). Do NOT assumePR_*; ask which before writing code. - Where a device peripheral registers — decide by ADAPTATION, not by "does
the SDK have a driver". A catalogued device peripheral in
board-context.mdis board-adapted →board_register_hardware()registers it; write no TDD code and noCONFIG_ENABLE_*. A device peripheral the user attaches externally (NOT inboard-context.md) → you register it yourself inusr_board, reusing the SDK's existing TDD driver (e.g. an extra GPIO button → calltdd_gpio_button_registerfromusr_register_hardware()). The SDK shipping the driver does not meanboard_register_hardware()wires your hardware — it only wires what the board adapted. Rule of thumb: any peripheral you wire on yourself goes throughusr_board. - Device vs on-chip. A part with a TDD/TDL driver (button, led, display,
audio, touch, …) is a device — even on a plain GPIO pin — so it follows
the device path above (board-adapted →
board_register_hardware(); external →usr_board), not the raw on-chip path. Only truly raw on-chip use (pin level toggle, edge IRQ, raw uart/i2c/spi/pwm/adc) callstal_*/tkl_*directly with no registration and noCONFIG_ENABLE_*. (A GPIO button is a device, not raw on-chip GPIO.) - Never write platform-level macros (
CONFIG_ENABLE_SPI/_I2C/_GPIO).
Step 1: Read the hardware context
Read the files above. board-context.md is the slim index; its fields:
| Field | Meaning |
|---|---|
## group — <id> |
A multi-peripheral accessory / board configuration |
Kconfig: (under a group) |
Group-level config to write to app_default.config to activate the whole group |
Devices: (under a group) |
Registration semantics — how many logical devices to register (shared bus → 1) |
Note: (under a group) |
Bus sharing, init order, etc. |
- <type> — <name> (ID: <id>) · <interface> |
One device. ID: is the key you record in used-peripherals.json (Step 3) |
Kconfig: (indented under a device) |
That device's enable macro(s) to write to app_default.config |
## Ungrouped devices |
Devices that belong to no group |
Per-device pins, driver IC, resolution and timing are NOT in board-context.md
— look the device up by its ID: in
.tuyaopen/ide/board.json (peripheralPatterns.<type>[], matched on id) and read
pins / model / width×height there. (The enable Kconfig: is already in the index above.)
Step 2: Confirm Hardware Selection
STOP — do not write any code until the user has confirmed.
- Single instance, no groups → proceed.
- Multiple instances of the same type, or any
## group — <id>→ MUST ask:
"Board has multiple
<type>options. Which one?
- <name/group> —
- ..."
Wait for reply. The group's Devices: line tells you how many logical devices to
register; look up each member's ID: in .tuyaopen/ide/board.json for pin/config details.
- Not in board-context.md at all → ask for interface and GPIO details.
Step 3: Record the Confirmed Hardware (before writing code)
The confirmed selection is the input to code generation, not an afterthought.
As soon as the peripheral selection is settled (Step 2) — and BEFORE writing any
code — full-overwrite .tuyaopen/used-peripherals.json with the confirmed set.
Steps 4–5 then generate code for exactly those instances.
peripherals= theID:values from board-context.md for the confirmed instances (e.g.display-rgb-main,camera). Use the confirmed instance, not every option of that type — if the board has several displays and the user chose the 3.5" LCD, list only that one's ID.- List all instances the project will use (already-used + this turn's), not just this turn's additions — it is a full snapshot, not a diff.
- For a custom peripheral added via usr-board with no board-context.md
ID:, use itsusr_boarddevice name as the id. Also record it in.tuyaopen/custom-peripherals.json(id + category + pins) so the hardware view draws a node for it and marks its GPIOs — seeusr-board/SKILL.mdStep 6. - On-chip peripherals use
onchip:<type><n>(e.g.onchip:uart2). Record them as an object with the pins your code occupies so the diagram can mark those GPIOs:{ "id": "onchip:uart2", "pins": [{"role":"tx","gpio":41},{"role":"rx","gpio":40}] }. (A bare string"onchip:uart2"is allowed but won't show pin occupancy.) - If the set changes while you are writing code, update this file again so it always matches what the code targets.
This file drives the Vibe Coding Hardware View diagram in Project Details, and on later turns the host feeds it back to you as the confirmed hardware context — so getting it right here is what lets you skip re-confirming next time.
{
"schemaVersion": 1,
"updatedAt": "2026-01-01T00:00:00Z",
"source": "vibe",
"peripherals": [
"display-rgb-main",
"camera",
{ "id": "onchip:uart2", "pins": [{ "role": "tx", "gpio": 41 }, { "role": "rx", "gpio": 40 }] }
]
}
Step 4: Check Kconfig
Board-adapted peripherals (listed in board-context.md):
- Kconfig to activate — write the
Kconfig:shown in board-context.md toapp_default.config: the group-levelKconfig:for a group, or the device's own indentedKconfig:for a standalone device. - Driver-enable macros (
ENABLE_DISPLAY,DISPLAY_NAME, etc.) — already selected by the board Kconfig internally. Do NOT write these.
New custom peripherals (not in board-context.md, added via usr_board) — you
may need to enable the peripheral in app_default.config, e.g. CONFIG_ENABLE_DISPLAY=y.
Check the matching peripheral skill for the correct macro.
Never write platform-level macros (CONFIG_ENABLE_SPI, CONFIG_ENABLE_I2C,
CONFIG_ENABLE_GPIO) — those are selected by the platform/board Kconfig.
Step 5: Delegate to Peripheral Skill (generate code)
| Type | Skill file |
|---|---|
display |
peripheral-drivers/peripheral-display/SKILL.md |
camera |
peripheral-drivers/peripheral-camera/SKILL.md |
button |
peripheral-drivers/peripheral-button/SKILL.md |
led |
peripheral-drivers/peripheral-led/SKILL.md |
audio |
peripheral-drivers/peripheral-audio/SKILL.md |
touchpad / touch |
peripheral-drivers/peripheral-touch/SKILL.md |
printer |
peripheral-drivers/peripheral-printer/SKILL.md |
ir / infrared / remote |
peripheral-drivers/peripheral-ir/SKILL.md |
joystick / rocker |
peripheral-drivers/peripheral-joystick/SKILL.md |
leds-pixel / WS2812 / addressable strip |
peripheral-drivers/peripheral-leds-pixel/SKILL.md |
The last three (ir / joystick / leds-pixel) are commonly custom peripherals
not present in board-context.md — register them via the usr-board flow and
add the matching CONFIG_ENABLE_* macro to app_default.config.
Not in board-context.md → peripheral-drivers/usr-board/SKILL.md.
On-chip peripherals (SoC buses/pins — not catalogued device parts)
For raw on-chip use (serial data, pin toggling, PWM, bus access, analog read).
These call tal_* / tkl_* directly — no board_register_hardware(), no
CONFIG_ENABLE_* (platform-selected). Confirm the instance + pins and record as
onchip:<type><n> in used-peripherals.json.
| Type | Skill file |
|---|---|
uart / serial (user serial, not PR_* logging) |
peripheral-drivers/onchip-uart/SKILL.md |
gpio / pin level / edge IRQ |
peripheral-drivers/onchip-gpio/SKILL.md |
pwm / dimming / buzzer / servo |
peripheral-drivers/onchip-pwm/SKILL.md |
i2c / iic / sensor bus |
peripheral-drivers/onchip-i2c/SKILL.md |
spi bus master |
peripheral-drivers/onchip-spi/SKILL.md |
qspi / quad-SPI (raw; catalogued QSPI LCD → display skill) |
peripheral-drivers/onchip-qspi/SKILL.md |
adc / analog read / voltage |
peripheral-drivers/onchip-adc/SKILL.md |
timer / hardware timer / periodic callback |
peripheral-drivers/onchip-timer/SKILL.md |
watchdog / wdt / feed dog |
peripheral-drivers/onchip-watchdog/SKILL.md |
rtc / real-time clock |
peripheral-drivers/onchip-rtc/SKILL.md |
dma2d / pixel-format convert / fast blit |
peripheral-drivers/onchip-dma2d/SKILL.md |
vad / voice activity (speech vs silence) |
peripheral-drivers/onchip-vad/SKILL.md |
kws / wake word / keyword spotting |
peripheral-drivers/onchip-kws/SKILL.md |
Serial "hello world" is ambiguous — ask first, don't auto-pick. "用串口发 X" can
mean the debug/log console (PR_*, often a USB-serial the PC already sees — no
peripheral, nothing recorded) OR a dedicated user UART (tal_uart on its own
instance + pins — onchip-uart, a confirmed peripheral). Ask which before coding.
Code Organisation
- New feature → new
source/embedded/src/app_<feature>.c+app_<feature>.h tuya_app_main.conly adds#include+ one call — no logic inlinesrc/is auto-scanned by CMake, no CMakeLists change needed
Entry Point
board_register_hardware() is declared in board_com_api.h — always include
it (not board_register.h / board_register_hardware.h, which do not exist).
tal_api.h covers tal_log_init / tal_sw_timer_init / tal_workq_init;
tkl_output.h provides tkl_log_output.
#include "tal_api.h"
#include "tkl_output.h"
#include "board_com_api.h" // board_register_hardware()
#include "app_xxx.h"
void user_main(void)
{
tal_log_init(TAL_LOG_LEVEL_DEBUG, 4096, (TAL_LOG_OUTPUT_CB)tkl_log_output);
tal_sw_timer_init(); // before any tdl_*_open()
tal_workq_init();
board_register_hardware();
app_xxx_init();
}
Coding Style
POSIX C: static void, const int — never STATIC, VOID, CONST.
SDK typedefs in uppercase are fine (OPERATE_RET, TDL_LED_HANDLE_T).