| name | hardware-vibe-coding |
| description | Hardware-aware code generation for TuyaOpen embedded projects. Reads the project's confirmed hardware (.tuyaopen/used-peripherals.json) + board catalog (.tuyaopen/board-context.md), confirms the selection with the user, records it BEFORE writing code, then delegates to the matching peripheral sub-skill. 外设初始化、硬件驱动、Vibe Coding、使用外设、硬件相关代码、点灯、点亮 LED、按键、 屏幕、显示、摄像头、音频、录音、播放、触摸、打印、红外、摇杆、灯带、串口、UART、 GPIO、PWM、I2C、SPI、ADC、传感器、引脚、片上外设、发送数据、读取传感器。 |
| when_to_use | Use for ANY hardware / peripheral / pin request on a TuyaOpen board — using or initializing a peripheral, serial / UART, GPIO, PWM, I2C, SPI, ADC, display, camera, button, LED, audio, touch, sensor, or any "make the hardware do X" task. Load this skill FIRST; it is the entry point that reads the project's confirmed hardware, enforces confirm-before-code, and routes to the specific peripheral sub-skill. 任何涉及硬件/外设/引脚/串口的需求都先加载本 skill。 |
| id | hardware-vibe-coding |
| surfaces | ["embedded"] |
| tags | ["hardware","peripheral","vibe-coding","routing"] |
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
(device ID:s and/or onchip:<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's
ID:, enable Kconfig:, 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. If false, 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 the functions it 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
expansionPins only. When you choose a pin for a
user/on-chip peripheral or a usr_board device, pick a gpio from
board.json.expansionPins whose functions include the role you need (e.g. a UART2
TX pin must list UART2_TX; a plain output needs GPIO). Never pick a pin that is not
in expansionPins — it isn't physically accessible. If expansionPins is 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_uart on its own instance + pins —
onchip:uart<N>). Do NOT assume PR_*; 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.md is board-adapted → board_register_hardware() registers
it; write no TDD code and no CONFIG_ENABLE_*. A device peripheral the user
attaches externally (NOT in board-context.md) → you register it yourself
in usr_board, reusing the SDK's existing TDD driver (e.g. an extra GPIO
button → call tdd_gpio_button_register from usr_register_hardware()). The
SDK shipping the driver does not mean board_register_hardware() wires
your hardware — it only wires what the board adapted. Rule of thumb: any
peripheral you wire on yourself goes through usr_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) calls tal_*/tkl_*
directly with no registration and no CONFIG_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 = the ID: 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 its usr_board device 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 — see usr-board/SKILL.md Step 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 to
app_default.config: the group-level Kconfig: for a group, or the device's
own indented Kconfig: 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.c only adds #include + one call — no logic inline
src/ 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"
#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();
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).