board.yaml
The declarative hardware description from spec/06: chip, memory, display, touch, and pins. Each blessed board's description lives in the Kog repository under boards/. It generates the flash manifest, the Studio device picker, the simulator's board geometry (board: in kog.config), and the display shape baked into firmware. The rest of the firmware's board setup (panel, pins, reserved pins, peripherals) is a hand-written C descriptor that a drift test keeps in step with the yaml's size and scale. The CLI doesn't read a project-level board.yaml yet, and kog board validate (schema validation) isn't wired up yet. See Boards for what's available today.
Blessed board
A scaffolded project's board.yaml names a blessed board with one line (the CLI doesn't act on it yet):
extends: waveshare-esp32-s3-touch-lcd-1.46b
Full schema
The shape of the files in boards/ (spec/06 §1), shown for the Waveshare 1.46B (abridged):
id: waveshare-esp32-s3-touch-lcd-1.46b # stable full id
slug: waveshare-1.46b # short id used by `kog flash --board`, the manifest, and Studio
name: Waveshare ESP32-S3-Touch-LCD-1.46B
short_name: Waveshare 1.46" # compact name for device pickers
vendor: waveshare
soc:
chip: esp32s3 # esp32 | esp32s3 (v1)
flash_mb: 16 # required: the flash part the board ships with (flashers read it)
psram: { size_mb: 8, type: octal } # null for no-PSRAM boards
display:
controller: spd2010 # st7789 | ili9341 | spd2010 | co5300 in today's firmware
bus: qspi # spi | qspi
width: 412 # physical px
height: 412
shape: round # optional (default rect): rect | round | ellipse | { kind: rounded-rect, radius } | { path }
ui_scale: 2 # optional (default 1): apps lay out in width/ui_scale logical px
offset: { x: 0, y: 0 } # optional: panel RAM origin of the visible area (non-negative ints)
color_depth: 16
rotation: 0
color_order: rgb # optional (default rgb): rgb | bgr
invert_colors: false # optional (default false)
mirror: { x: false, y: false } # optional
backlight: { pin: 5, active: high, pwm: true } # or { kind: dcs } on an AMOLED; omit for none
pins: { sclk: 40, d0: 46, d1: 45, d2: 42, d3: 41, cs: 21, te: 18, rst: exio2 }
touch:
controller: spd2010 # xpt2046 (spi) | spd2010 | ft3168 (i2c) in today's firmware
bus: i2c
addr: 0x53
# threshold: 40 # optional, ft3168 only: touch-detect threshold (1..255, lower =
# lighter touches register, too low = ghost touches); omitted =
# the controller keeps its own value
pins: { sda: 11, scl: 10, int: 4, rst: exio1 }
i2c_system: { scl: 10, sda: 11 } # the board's shared system I2C bus
reserved_pins: # pins the board owns, each with a reason
- { pin: 19, why: usb-dn }
- { pin: 20, why: usb-dp }
expander: # optional I2C IO expander; "exioN" pin strings name its lines
chip: tca9554
bus: i2c_system
lines: { exio1: touch-rst, exio2: lcd-rst, exio3: sd-cs }
partitions: flash16 # flash16 | flash4 (spec/05 §3)
firmware: # build wiring for the prebuilt image
build_dir: build_s3_mqjs
boardcfg: s_waveshare_1_46b
peripherals: # onboard parts beyond display + touch (see below)
imu: { chip: qmi8658, bus: i2c_system }
rtc: { chip: pcf85063, bus: i2c_system }
sd: { bus: sdmmc-1bit, pins: { clk: 14, cmd: 17, d0: 16, d3: exio3 } }
signing: {} # trusted OTA public key (spec/05 §2); empty on today's prebuilts
Peripherals and buttons
The peripherals: blocks describe onboard parts the way the firmware drives them, and buttons:
names the board's keys. Shown for the Waveshare AMOLED-2.06 (boards/waveshare-esp32-s3-touch-amoled-2.06.yaml), which declares every kind:
display:
backlight: { kind: dcs } # AMOLED: brightness is the panel's DCS 0x51 register, no pin
offset: { x: 22, y: 0 } # CO5300 RAM column offset
peripherals:
rtc: { chip: pcf85063, bus: i2c_system, addr: 0x51, int: 39 } # addr, int optional
pmic: { chip: axp2101, bus: i2c_system, addr: 0x34 } # addr optional
imu: # addr, int, sensors, orientation optional
chip: qmi8658
bus: i2c_system
addr: 0x6b
int: 21
sensors: [accelerometer, gyroscope] # also magnetometer. Default accel + gyro
orientation: [0, 1, 0, 1, 0, 0, 0, 0, -1] # chip axes -> display axes (below)
motor: { pin: 18, active: high } # vibration motor
audio:
output: { codec: es8311, addr: 0x18, amp: { pin: 46, active: high } } # amp optional
input: { codec: es7210, addr: 0x40, mics: 2 } # mics 1..4
i2s: { mclk: 16, bclk: 41, ws: 45, dout: 40, din: 42 } # dout with an output, din with an input
sd: { bus: sdmmc-1bit, pins: { clk: 2, cmd: 1, d0: 3, d3: 17 } } # d3: GPIO number or exioN
buttons: # boot | power | user
boot: { pin: 0, active: low }
power: { pin: 10, active: high }
Each block is optional; a board without the part leaves it out. Declaring a part records the
hardware and reserves its pins. On today's firmware the RTC drives clock, the SD slot serves
file:///sd/ images, the PMIC backs expo-battery, the IMU backs expo-sensors' Accelerometer and
Gyroscope, the motor backs expo-haptics and Vibration, and a pwm or dcs backlight backs
expo-brightness (Device services). Buttons are read with useButton from
@kog/hardware.
imu.orientation maps the chip's axes to the display's (x right, y up, z out of the glass), so an
app reads the same axes whatever way the IMU is soldered. It is a row-major 3×3 matrix with
display = orientation × chip: each row has exactly one 1 or -1, naming which chip axis (and
sign) feeds that display axis. Left out, it is the identity. On the AMOLED-2.06 the part is on the
back of the board, so display x is chip Y, display y is chip X, and display z is minus chip Z.
Validation
boards/registry.mjs is the one reader of these files, and it rejects a board instead of guessing:
- Required fields:
id,slug,name,short_name,soc.chip,soc.flash_mb,display.width/height,partitions,firmware.build_dir/boardcfg.idandslugmust be unique acrossboards/, andui_scalemust divide both display axes. - No unknown names: an unknown key in
peripherals,buttons,display.backlight,display.offsetor any part block, an unknown peripheral, and a chip or codec with no firmware backend are errors. Today that meanspcf85063(rtc),axp2101(pmic),qmi8658(imu),es8311(audio output) andes7210(audio input). The onlysd.busissdmmc-1bit. - Board-owned pins must be reserved: every native GPIO a part uses (the SD lines, the RTC
and IMU
int, the motor, the I2S lines, the amp enable) must also appear inreserved_pins. The error names the entry to add. - Buttons must not be reserved: a button's pin is app-readable, so listing it in
reserved_pinsis an error. imu.orientationmust be a rotation: nine entries of-1,0or1, one non-zero per row and per column, and determinant+1. A mirror (determinant-1) can't come from soldering a chip, so it's reported as a typo with the fix: flip the sign of one axis.- Parts sit on
i2c_system:rtc,pmicandimumust saybus: i2c_system, and a board with any of them, or withaudio, must declarei2c_system: { scl, sda }. I2C addresses are 7-bit (0x08–0x77).
Semantics
- The pins a board wires to its display, touch panel, backlight, IO expander, system I2C bus, and declared peripherals (SD, RTC/IMU interrupts, motor, I2S, amp enable) are reserved in its firmware, along with pins the chip itself reserves. Button pins are not. The hardware API refuses to claim them, throwing an
HwErrorwithcode === 'PIN_RESERVED'. Thereserved_pinslist documents why each board-owned pin is taken; today's firmware doesn't enforce entries beyond the wired pins above, so on the CYD the onboard LED and light-sensor pins stay claimable. display.pinsfollows the bus:spiusessclk/mosi/miso/cs/dc/rst, andqspiusessclk,d0–d3,cs,te, andrst.- spec/06 plans for the CLI to resolve how to run a board from this file (hosted prebuilt, generic image plus a config blob, or a custom compile). Only the hosted prebuilts for blessed boards exist today. See Boards.