Skip to main content

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. id and slug must be unique across boards/, and ui_scale must divide both display axes.
  • No unknown names: an unknown key in peripherals, buttons, display.backlight, display.offset or any part block, an unknown peripheral, and a chip or codec with no firmware backend are errors. Today that means pcf85063 (rtc), axp2101 (pmic), qmi8658 (imu), es8311 (audio output) and es7210 (audio input). The only sd.bus is sdmmc-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 in reserved_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_pins is an error.
  • imu.orientation must be a rotation: nine entries of -1, 0 or 1, 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, pmic and imu must say bus: i2c_system, and a board with any of them, or with audio, must declare i2c_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 HwError with code === 'PIN_RESERVED'. The reserved_pins list 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.pins follows the bus: spi uses sclk/mosi/miso/cs/dc/rst, and qspi uses sclk, d0–d3, cs, te, and rst.
  • 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.