SDK
kbsp
The Kode Dot as constants: pins, I2C addresses, screen, battery and owners, plus the sdkconfig.board and partitions.csv every firmware builds with.
What it is
kbsp is the board support package, and it describes the Kode Dot. It holds:
- every GPIO number and I2C address
- the size and shape of the screen
- the supply rails
- the numbers of the battery
- the name of the product
- the table of who owns which device
It also carries two files that every firmware reads when it is built: sdkconfig.board, the build options that state the chip and wiring facts, and partitions.csv, the map of the 32 MB flash.
kbsp sits at the bottom of the layers and depends on nothing. It describes and never acts: no code in it touches hardware. khal starts the buses with its numbers, the drivers address their chips with its constants, and each Active Object registers in its ownership table.
That gives it a placement test. A file that knows a pin belongs here. A file that knows a part number belongs in kdrv.
A pin number or an I2C address is defined only here. Layer check 3 of tools/check_layers.sh fails when a KBSP_PIN_*, KBSP_I2C_ADDR_* or KBSP_IOEXP_* is defined anywhere else (sdk/design-rules).
How it is connected
flowchart LR subgraph readers["Who reads kbsp"] khal["khal<br/>I2C bus, GPIO, expansion header"] kdrv["kdrv<br/>chip drivers"] kaos["kaos<br/>kaos_claim()"] end subgraph kbsp["kbsp"] consts["Pins, addresses,<br/>owners"] build["sdkconfig.board<br/>partitions.csv"] end khal --> consts kdrv --> consts kaos -- "kbsp_claim()" --> consts build --> fw["Every firmware build"] class kaos ao class build store class fw app
The component has two sources in C and everything else is #defines. The expansion header tables KBSP_EXP[] and KBSP_EXP_CTRL[] live in kbsp_exp.c, because a table in a header would be copied into every file that includes it. The ownership table lives in kbsp_owners.c for the same reason.
Ownership takes no lock
Ownership is a record. Each owner claims its resources in its initial transition, through kaos_claim(). That wrapper calls kbsp_claim(), and when a second owner asks for the same resource it prints kodeOS: <resource> claimed by both <first> and <second> and stops the boot.
Each slot is an atomic pointer changed by compare-and-swap, so two Active Objects on two cores cannot both win. The table does not stand between an owner and its device. The Active Object keeps the device safe by handling one event at a time.
An owner owns a whole device. The TCA6408A expander serves four parts, but it has one input register and one interrupt line, so AO_IoExp owns the chip and everyone else asks it by event.
The build files follow the board
Every project reads <sdk>/components/kbsp/sdkconfig.board first, then its own sdkconfig.defaults, through SDKCONFIG_DEFAULTS. A later file wins, so a project can restate an option, with no warning.
A project spells out two board facts itself, because both are needed before the shared file can be fetched:
set(IDF_TARGET esp32p4)beforeproject().CONFIG_PARTITION_TABLE_CUSTOM_FILENAME. An app beside the SDK fetched as a component points it atmanaged_components/kodediy__kbsp/partitions.csv.
Specifications
The board
The facts about the chip, the glass and the rails.
| Thing | Value |
|---|---|
| Product, revision | KBSP_PRODUCT_NAME "Kode Dot", KBSP_BOARD_REV "DVT". KBSP_ID_PRODUCT "KODE-DOT" is what info prints as product=. KBSP_ID_PREFIX "KODE" starts every Kode serial and is the same for every Kode product |
| Main chip | ESP32-P4, two RISC-V cores at 400 MHz. Revision v3.2 on the Kode Dot |
| Flash | 32 MB NOR, GD25Q256E, JEDEC 0xC84019 |
| PSRAM | 32 MB in package, HEX mode, 250 MHz |
| Screen | CO5300 AMOLED, KBSP_LCD_H_RES 410 by KBSP_LCD_V_RES 502 pixels over MIPI-DSI. Corners are rounded with KBSP_LCD_CORNER_R_PX 114 pixels, so anything in a corner is not on the glass |
| DPHY rail | KBSP_LCD_DPHY_LDO_CHAN 3 at KBSP_LCD_DPHY_LDO_MV 2500, up before any MIPI traffic |
| SD IO rail | KBSP_SD_LDO_CHAN 4, the supply of the SDMMC pins. The card's own supply is a pin, KBSP_PIN_SD_PWR |
| microSD defaults | KBSP_SD_WIDTH 4 lines, KBSP_SD_FREQ_KHZ 40000 |
| C5 link | KBSP_C5_SDIO_WIDTH 4, KBSP_C5_SDIO_KHZ 40000. kdrv_c5 asserts at compile time that sdkconfig.board agrees |
| Crystals | KBSP_XTAL_MAIN_HZ 40000000 (X1). KBSP_XTAL_32K_MHZ 32768000 (X3), in milli-hertz. KBSP_XTAL_32K_TOL_MHZ 327680, a window of 1 %, wide on purpose: the chip counts every frequency against X1, so a tighter bound could not be judged on the board |
| Not in kbsp | GPIO0 and GPIO1 carry the 32.768 kHz crystal (XTAL_32K_N, XTAL_32K_P), so no code may use them as pins |
The controllers kodeOS owns
The chip routes most controllers to any pin, so which controller drives a device is a board choice. The numbers are ESP-IDF's own: for SPI and the ADC that is one less than the chip's name, so SPI2 is SPI2_HOST, 1. Each driver turns its number into the IDF type beside a _Static_assert that the chip has it.
| Name | Value | Controller | Owner |
|---|---|---|---|
KBSP_I2C_BOARD | 0 | I2C0, the board bus | khal_i2c |
KBSP_SPI_NFC | 1 | SPI2 | the ST25R3916B |
KBSP_I2S_CODEC | 0 | I2S0 | the ES8311 |
KBSP_ADC_EXP | 0 | ADC1 | the analog positions of J3 |
KBSP_ADC_RFID | 1 | ADC2 | the demodulated 125 kHz line |
KBSP_EXP_I2C_ID | 1 | I2C1 | an app, through khal_exp_ctrl() |
KBSP_EXP_SPI_ID | 2 | SPI3 (SPI3_HOST) | an app, through khal_exp_ctrl() |
| UART0 to UART4 | KBSP_EXP_UART_LAST 4 | five UARTs | an app. UART0 is on offer only while the console is not a UART, which is the case on the Kode Dot |
KBSP_EXP_CTRL_COUNT is 7 on the board: I2C1, SPI3 and five UARTs. The LP controllers reach only the LP pins and are not offered. The chip has one continuous ADC engine for both units and the RFID driver opens it for each read window, so J3's analog positions are read one sample at a time.
The I2C bus
One shared bus: SDA on KBSP_PIN_I2C_SDA GPIO4, SCL on KBSP_PIN_I2C_SCL GPIO5, at KBSP_I2C_FREQ_HZ 400000. The chips on it:
| Constant | Address | Chip |
|---|---|---|
KBSP_I2C_ADDR_LED | 0x30 | KTD2026 RGB LED driver (U8) |
KBSP_I2C_ADDR_IOEXP | 0x20 | TCA6408A expander (U24), ADDR tied low |
KBSP_I2C_ADDR_IMU | 0x6A | QMI8658A (U10), SA0 tied high |
KBSP_I2C_ADDR_MAG | 0x2C | QMC5883P (U5), a chip of its own on the bus, not behind the IMU |
KBSP_I2C_ADDR_CODEC | 0x18 | ES8311 |
KBSP_I2C_ADDR_CHARGER | 0x6B | BQ25896 |
KBSP_I2C_ADDR_GAUGE | 0x64 | CW2217 |
KBSP_I2C_ADDR_HAPTIC | 0x5A | AW86233 (U7), on the antenna PCB. The chip fixes the address |
KBSP_I2C_ADDR_TOUCH | 0x15 | CST820, on the panel module of supplier A |
kbsp has no axis map for the IMU or the magnetometer. The drivers return each part's own axes.
The expander
The eight pins of the TCA6408A serve the buttons, the motion sensor, the touch controller and the supply of the panel.
- Its interrupt line is
KBSP_PIN_IOEXP_INTGPIO6, active low, pulled up by R24 (10 kΩ). - Its RESET is tied high through R117, so a reset of the P4 never resets the expander.
KBSP_IOEXP_DIR_INPUTSis0x7F: P0 to P6 are inputs, P7 is an output.
The eight pins:
| Pin | Constant | Net | Pull-up |
|---|---|---|---|
| P0 | KBSP_IOEXP_BIT_BTN_BOT | BUTTON_BOT (SW2), also the QON pin of the BQ25896 | TBD: see below |
| P1 | KBSP_IOEXP_BIT_PAD_UP | PAD_TOP (SW3) | R39, 10 kΩ |
| P2 | KBSP_IOEXP_BIT_PAD_LEFT | PAD_LEFT (SW4) | R40, 10 kΩ |
| P3 | KBSP_IOEXP_BIT_PAD_DOWN | PAD_BOTTOM (SW5) | R41, 10 kΩ |
| P4 | KBSP_IOEXP_BIT_PAD_RIGHT | PAD_RIGHT (SW6) | R42, 10 kΩ |
| P5 | KBSP_IOEXP_BIT_IMU_INT1 | INT1 of the QMI8658A, driven push-pull by the chip | none, so the driver makes INT1 a driven output |
| P6 | KBSP_IOEXP_BIT_TP_INT | touch interrupt, input | on the panel module |
| P7 | KBSP_IOEXP_BIT_DSP_PWR | VCI_EN, the supply of the display (R48 holds it low) | none |
All six buttons are SKRMAAE010 and active low. The sixth, KBSP_PIN_BUTTON_TOP GPIO35, is the only one on a P4 pin, and the driver also turns on the internal pull-up.
If P7 becomes an input the screen turns off, so AO_IoExp never writes P7 unless AO_Display asks.
TBD: the pull-up of BUTTON_BOT and BUTTON_TOP. The validation pin map lists external 10 kΩ resistors R38 to R42 on the five expander buttons, while a read of the schematics puts R38 on BUTTON_TOP and leaves BUTTON_BOT to the 200 kΩ internal pull-up of the charger's QON pin. A measurement on the board would settle it.
Other pins
The rest of the pins, by part.
| Part | Pins | Notes |
|---|---|---|
| Display and touch reset | KBSP_PIN_DSP_TP_RST GPIO7 | Active low. The panel and the touch controller share it |
| microSD | SDMMC slot 0: D0 to D3 on GPIO39 to 42, CLK GPIO43, CMD GPIO44 | Power is KBSP_PIN_SD_PWR GPIO22, active low through Q1. Card detect is IO6 of the C5 (KBSP_C5_IO_SD_CD) |
| USB-Serial/JTAG | GPIO24 (D-), GPIO25 (D+) | The console and the flasher |
| Magnetic pads | GPIO26, GPIO27 | The second full-speed USB pair, used by the dock console. kbsp names no constant: the PHY has fixed pins |
| Audio, I2S to the ES8311 | MCLK GPIO36, SCLK GPIO34, LRCK GPIO37, DSDIN GPIO38 (P4 to codec), ASDOUT GPIO33 (codec to P4) | The NS4150B amplifier enable is IO4 of the C5 (KBSP_C5_IO_PA_CTRL). See the first gotcha |
| Infrared | IO24 (TX) and IO23 (RX) of the C5 | KBSP_C5_IO_IR_TX, KBSP_C5_IO_IR_RX. The P4 has no infrared pins |
| NFC, ST25R3916B, SPI mode 1 | CS GPIO28, IRQ GPIO29, CLK GPIO30, MOSI GPIO31, MISO GPIO32 | The reader is alone on SPI2 |
| RFID, 125 kHz | OUT GPIO47, PULL GPIO45, CARRIER GPIO46, RF_IN GPIO54 | RF_IN is ADC2 channel 5. PULL rests high through R11 and a high level shunts the detector, so the driver holds it low only while it listens. CARRIER is an input and the P4 never drives it |
| ESP32-C5 | EN GPIO23 (high is running). SDIO: CLK GPIO50, CMD GPIO51, D0 to D3 on GPIO49, 48, 53, 52 | kdrv_radio asserts that CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE equals KBSP_PIN_C5_EN. KBSP_C5_IO_PWR_EXT is IO5, the enable of the +3.3V_EXT rail of J3, on unless driven |
The RFID input is on ADC2 because GPIO23 is also ADC1 channel 7, and a read on ADC1 there would reconfigure the enable line of the C5 and stop the radio.
The expansion header
J3 is a 2 by 10 header. It brings out GPIO8 to GPIO21 as KBSP_PIN_EXP_1 to KBSP_PIN_EXP_14, each through 68 Ω, with the 3.3 V and 5 V rails and ground.
KBSP_EXP[] has KBSP_EXP_COUNT 14 entries, indexed by position minus one. Each is a kbsp_exp_t: gpio, adc_chan (or KBSP_EXP_NO_ADC, 0xFF) and lp.
Every position is digital and matrix-routed. The ADC channel is the GPIO minus 16, and the LP pins are the GPIOs at or below 15. No position is both.
The 14 positions:
| Position | J3 pin | GPIO | Extra | ADC1 channel |
|---|---|---|---|---|
| EXP1 | 3 | 8 | LP | none |
| EXP2 | 4 | 9 | LP | none |
| EXP3 | 5 | 10 | LP | none |
| EXP4 | 6 | 11 | LP | none |
| EXP5 | 7 | 12 | LP | none |
| EXP6 | 8 | 13 | LP | none |
| EXP7 | 9 | 14 | LP | none |
| EXP8 | 12 | 21 | ADC | 5 |
| EXP9 | 13 | 19 | ADC | 3 |
| EXP10 | 14 | 20 | ADC | 4 |
| EXP11 | 15 | 17 | ADC | 1 |
| EXP12 | 16 | 18 | ADC | 2 |
| EXP13 | 17 | 15 | LP | none |
| EXP14 | 18 | 16 | ADC | 0 |
The table has no bit for the touch-sensing inputs of the chip, because kbsp has no trusted list of which GPIO each channel uses.
The battery
The cell is a 1S Li-ion pouch cell, type 603048 at 1000 mAh, on connector J7 with the shunt R75 in series. These numbers belong to the cell and the shunt, so none of them sits in a driver. AO_Power writes the charge profile into the BQ25896 at every boot.
The constants, in kbsp_cell.h:
| Constant | Value | Meaning |
|---|---|---|
KBSP_CELL_RSENSE_MOHM | 10 | The shunt that the CW2217 reads. Charge reads positive, the same sign as the charger |
KBSP_CELL_R_INT_MOHM | 180 | Internal resistance, for OCV = V - I * R_int |
KBSP_CELL_OCV_MV, KBSP_CELL_OCV_PCT | 13 points | The curve from 3300 mV = 0 % to 4200 mV = 100 % |
KBSP_CELL_VCUT_MV | 3000 | Discharge cut-off |
KBSP_CELL_VTRIP_MV | 4280 | Over-voltage trip of the protection board. A reading outside 3000 to 4280 mV is not a battery reading |
KBSP_CELL_CAPACITY_MAH | 1000 | Capacity |
KBSP_CELL_VREG_MV | 4192 | Charge voltage, 8 mV under the pack's 4.20 V |
KBSP_CELL_ICHG_MA | 512 | Fast charge, 0.5 C |
KBSP_CELL_IPRECHG_MA, KBSP_CELL_ITERM_MA | 64, 64 | Pre-charge and termination, the chip's floor |
KBSP_CELL_ICHG_MAX_MA | 896 | The most charge_ma may ask for |
KBSP_CELL_BOOST_LIM_CODE | 0 | BOOST_LIM code 0 is 0.5 A, which keeps the draw under the pack's peak |
KBSP_CELL_BOOST_HOT_CODE | 1 | BHOT code 1, about 55 °C |
KBSP_CELL_BOOST_MIN_MV | 3600 | Under this, a boost request is refused |
KBSP_CELL_BOOST_STOP_MV | 3300 | Under this, a running boost stops |
All the charge values sit on the grid of the BQ25896's registers, so nothing is rounded on the way in (sdk/kdrv/bq25896).
The owners
kbsp_res_t lists 25 resources. The column on the right is who claims it in the SDK.
| Resource | What it is | Claimed by |
|---|---|---|
KBSP_RES_LED | KTD2026 at 0x30 | AO_LedMgr |
KBSP_RES_PANEL | MIPI-DSI and the CO5300 | AO_Display |
KBSP_RES_TOUCH | CST820 at 0x15 | AO_Touch |
KBSP_RES_IOEXP | TCA6408A at 0x20 and its interrupt line | AO_IoExp |
KBSP_RES_BTN_TOP | GPIO35, the only button on a P4 pin | AO_Buttons |
KBSP_RES_SDMMC | the microSD slot | AO_Storage |
KBSP_RES_FATFS | the internal storage partition | AO_Storage |
KBSP_RES_OTA_SLOT | the inactive kodeOS slot | AO_AppMgr |
KBSP_RES_OTADATA | the otadata partition: which firmware starts | AO_Boot |
KBSP_RES_APPS_REGION | the apps partition: directory and images | AO_AppMgr |
KBSP_RES_APPS_BOOTREQ | the boot-request sector: which app starts next | AO_Boot |
KBSP_RES_APP_PENDING | the app_pending mailbox partition | AO_AppMgr |
KBSP_RES_PMIC | BQ25896 at 0x6B | AO_Power |
KBSP_RES_GAUGE | CW2217 at 0x64 | AO_Power |
KBSP_RES_CONSOLE | the console transport | AO_Log |
KBSP_RES_C5 | the whole ESP32-C5: EN line, link, its pins, its flash | AO_Radio |
KBSP_RES_JPEG | the P4's JPEG codec, one engine | AO_Display, with the mirror |
KBSP_RES_PPA | the P4's pixel accelerator, one engine | AO_Display, with the mirror |
KBSP_RES_IR | the emitters and the receiver, owned whole | AO_Ir |
KBSP_RES_NFC | the ST25R3916B, its IRQ and SPI2 | AO_Nfc |
KBSP_RES_AUDIO | the ES8311, microphone, speaker, amplifier path and I2S with its five pins | AO_Audio |
KBSP_RES_IMU | the QMI8658A and the QMC5883P | AO_Imu |
KBSP_RES_HAPTIC | the AW86233 at 0x5A | AO_Haptic |
KBSP_RES_RFID | OUT GPIO47, PULL GPIO45, CARRIER GPIO46 and the demodulated input GPIO54 with its ADC | AO_Rfid |
KBSP_RES_EXP | the expansion header, all fourteen positions | the console or an app, whichever asks first |
Some pairs are one resource on purpose. The infrared emitters and the receiver share a housing and the receiver hears the emitter. The IMU and the magnetometer share one owner because the test rows and the haptic row's witness go through one place. The audio amplifier's enable pin belongs to the C5 and is not in the audio resource.
KBSP_RES_JPEG and KBSP_RES_PPA are claimed only in a build with the screen mirror, KBSP_RES_C5 only with the radio, and the three AppMgr resources only with AO_AppMgr. The LEDC channels of the RFID carrier are not part of its resource: the driver takes them from khal_pwm while the field is up.
The calls
The four calls of the ownership table.
| Call | What it does |
|---|---|
kbsp_claim(res, owner, name) | Atomic claim of one resource. true when owner holds it now or already; false when another owner holds it or an argument is invalid. A second claim by the holder keeps the first name |
kbsp_release(res, owner) | Gives it back, ignored unless owner holds it. Only the host test suite calls it |
kbsp_owner_name(res) | Who holds it, "?" while it is being named, or NULL when free. ps prints it |
kbsp_res_name(res) | The resource's own name. Never NULL; "?" out of range |
The build file sdkconfig.board
Each option is a fact about the chip or the wiring. A comment on every option names the component that declares it. A project can restate an option in its own sdkconfig.defaults.
The options come in five groups, one table each. The first group holds flash, cache and the partition table.
| Option | Value | Why |
|---|---|---|
ESPTOOLPY_FLASHSIZE_32MB | y | The GD25Q256E |
BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH | y | 4-byte addressing, so the cache maps all 32 MB and code runs above 16 MB. Global, and marked experimental by ESP-IDF. A die that rejects it does not boot at all |
IDF_EXPERIMENTAL_FEATURES | y | The gate for the option above |
PARTITION_TABLE_CUSTOM, PARTITION_TABLE_OFFSET | y, 0x8000 | A custom table, at the address the Arduino core looks for |
MMU_PAGE_SIZE_64KB | y | An app image mapped in place starts on an MMU page, so KAPPS_ALIGN (0x10000) is this number |
CACHE_L2_CACHE_128KB | y | 128 KB of L2 cache. 512 KB does not boot |
sdkconfig.board: memory
PSRAM and where the big buffers go.
| Option | Value | Why |
|---|---|---|
SPIRAM, SPIRAM_MODE_HEX, SPIRAM_SPEED_250M | y | 32 MB in HEX mode at 250 MHz, which needs a die of revision v3.0 or newer. A marginal PSRAM bus corrupts data and does not refuse |
SPIRAM_IGNORE_NOTFOUND | y | Boot without PSRAM when it does not answer. IDF then sizes the Wi-Fi and lwIP buffers as if there were none |
SPIRAM_MALLOC_ALWAYSINTERNAL | 0 | A plain malloc() takes PSRAM. Internal RAM is asked for by capability |
SPIRAM_MEMTEST | n | No walk of 32 MB at every boot. test component=psram tests it instead |
ESP_HOSTED_MEMPOOL_PREFER_SPIRAM | y | The 88 KB of esp-hosted transport pools in PSRAM |
BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL | y | NimBLE's 32 KB of buffers in PSRAM |
MBEDTLS_EXTERNAL_MEM_ALLOC | y | TLS buffers in PSRAM: a session holds about 30 KB of the internal pool |
sdkconfig.board: the radio link and Bluetooth
The wiring of the C5 and the BLE host.
| Option | Value | Why |
|---|---|---|
ESP_HOSTED_* link lines | SDIO slot 1, CMD 51, CLK 50, D0 49, D1 48, D2 53, D3 52, reset 23, 40 MHz, 4-bit | The wiring of the C5 link. 40 MHz is the validated maximum of the PCB. They do nothing until a firmware compiles esp-hosted in |
ESP_HOSTED_ENABLE_GPIO_EXPANDER | y | Reaches the C5's own pins by RPC, for the SD card detect |
ESP_HOSTED_SLAVE_RESET_ONLY_IF_NECESSARY | y | esp-hosted resets the C5 only when the SDIO card does not answer, so a parked C5 can stay up |
BT_ENABLED, BT_NIMBLE_ENABLED, ESP_HOSTED_ENABLE_BT_NIMBLE | y | The BLE host on the P4, controller on the C5. In this file because BT_NIMBLE_ENABLED is a choice that no option can select |
BT_NIMBLE_NVS_PERSIST, BT_NIMBLE_CRYPTO_STACK_MBEDTLS | y | Bonds survive a reboot. Pairing needs P-256 ECDH, which the default backend fails |
BT_NIMBLE_LOG_LEVEL_WARNING, BT_NIMBLE_MSYS_1_BLOCK_COUNT | y, 24 | NimBLE's INFO lines would loop through the BLE console. The default 12 mbufs is fewer than one help listing needs |
sdkconfig.board: console, clock, tick and logging
The console, the CPU and RTC clocks, the scheduler tick and the log levels.
| Option | Value | Why |
|---|---|---|
ESP_CONSOLE_USB_SERIAL_JTAG | y | The console is the native USB. The pins of UART0 go to the audio codec |
ESP_DEFAULT_CPU_FREQ_MHZ_400 | y | 400 MHz, stated so a later default cannot move it |
RTC_CLK_SRC_EXT_CRYS | y | The RTC runs on the 32.768 kHz crystal X3, not the internal RC |
FREERTOS_HZ | 1000 | A 1 ms scheduler tick |
ESP_MAIN_TASK_STACK_SIZE | 8192 | kode_init() starts every owner Active Object on the main task, and QP/C runs each initial transition on it |
LCD_DSI_ISR_CACHE_SAFE | y | The DSI interrupt keeps running while the flash cache is off, which a long erase causes |
MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY | y | A chain that reaches a root of the bundle through a cross-signature verifies. Costs about 700 bytes per handshake |
BOOTLOADER_LOG_LEVEL_NONE, LOG_MAXIMUM_LEVEL_DEBUG | y | A silent bootloader. DEBUG lines of ESP-IDF stay compiled in for log_level |
sdkconfig.board: LVGL
The graphics library's allocator, timing, draw threads and colour format.
| Option | Value | Why |
|---|---|---|
LV_USE_CUSTOM_MALLOC | y | LVGL allocates from PSRAM through kui_mem.c, with no fallback |
LV_DEF_REFR_PERIOD | 16 | One pass per 16.8 ms refresh of the panel |
LV_OS_FREERTOS, LV_DRAW_SW_DRAW_UNIT_CNT, LV_DRAW_THREAD_STACK_SIZE, LV_DRAW_THREAD_PRIO | y, 2, 32768, 3 | Two draw threads, one per core, with 32 KB stacks in PSRAM for the Lottie rasteriser |
LV_COLOR_FORMAT_RGB565, LV_COLOR_MIX_ROUND_OFS | y, 128 | The CO5300 cannot run 410 by 502 at 24 bits per pixel. Colour mixes round half up |
LV_DRAW_SW_SHADOW_CACHE_SIZE, LV_USE_QRCODE, LV_USE_OBJ_NAME | 128, y, y | The blurred-corner cache, the QR code of the empty launcher, and object names |
sdkconfig.board: what it leaves out
CONFIG_ESP_HOSTED_ENABLED is absent on purpose. The component installs a constructor that claims the SDMMC controller before app_main, so a firmware that read this file would lose the microSD slot without asking for a radio. KODEOS_RADIO selects it. CONFIG_XTAL_FREQ_40 is absent because the chip forces it.
Two lines are commented out for a die older than v3.0: ESP32P4_SELECTS_REV_LESS_V3 and ESP32P4_REV_MIN_100. Turning them on also makes SPIRAM_SPEED_250M stop existing, and moves the CPU default to 360 MHz.
The partition table
The bootloader is at 0x2000 and the table at 0x8000. The table fits in one 4 KB sector, and nvs starts at 0xB000 so a table that grows never moves it. Executable code is not confined below 16 MB, because the cache maps 32-bit addresses, so the apps region runs from the last OS slot up to the FAT.
The ten partitions of partitions.csv:
| Name | Type | Subtype | Offset | Size | Holds |
|---|---|---|---|---|---|
nvs | data | nvs | 0xB000 | 0x10000 | Settings, and Preferences of Arduino sketches |
otadata | data | ota | 0x1B000 | 0x2000 | Which OTA slot starts. kodeOS alone writes it |
phy_init | data | phy | 0x1D000 | 0x1000 | RF calibration data |
factory | app | factory | 0x20000 | 0x400000 | The rescue copy of kodeOS, written by cable only |
ota_0 | app | ota_0 | 0x420000 | 0x400000 | Update slot of kodeOS |
ota_1 | app | ota_1 | 0x820000 | 0x400000 | Update slot of kodeOS |
coredump | data | coredump | 0xC20000 | 0x20000 | The last panic, in ELF format. Below 16 MB because the panic handler writes it with the cache off |
apps | data | 0x40 | 0xC40000 | 0xBC0000 | Directory, boot request and installed app images (internals/apps-region) |
storage | data | fat | 0x1800000 | 0x200000 | The internal FAT volume, wear-levelled |
app_pending | data | 0x41 | 0x1A00000 | 0x600000 | The development mailbox. Non-executable: the bootloader never boots it |
The regions end where the next begins: apps ends at 0x1800000, storage at 0x1A00000 and app_pending at 0x2000000, the end of the 32 MB. The offset of apps is fixed, so installed images keep their addresses across a table change.
The files
The files of the component.
| File | What it does |
|---|---|
board/include/kbsp.h | Umbrella header: includes kbsp_pins.h, kbsp_board.h and kbsp_cell.h |
board/include/kbsp_pins.h | Every GPIO, I2C address and expander bit |
board/include/kbsp_board.h | The product name and revision, the glass, the rails, the links, the crystals and the controllers |
board/include/kbsp_cell.h | The battery: shunt, charge profile, boost limits and the OCV curve |
board/include/kbsp_identity.h | KBSP_ID_PRODUCT and KBSP_ID_PREFIX. Not pulled in by kbsp.h |
exp/include/kbsp_exp.h | The kbsp_exp_t type, KBSP_EXP[] and the controllers an app may take |
exp/src/kbsp_exp.c | The table of the 14 positions and the list of controllers, with a compile-time check that a UART console is UART0 |
owners/include/kbsp_owners.h | kbsp_res_t and the four calls |
owners/src/kbsp_owners.c | The table of owners: one atomic token and one atomic name per resource |
sdkconfig.board | The build options of the chip and the wiring |
partitions.csv | The flash map above |
CMakeLists.txt | Registers the two sources and the three include folders |
Gotchas
Example code
Read the board facts from kbsp, then ask the ownership table who holds the LED and the battery charger. After kode_init() returns, the log shows the screen size and the two owners, AO_LedMgr and AO_Power.
/*
* board_facts.c - 2026-10-28 - from Kode with love
*
* Logs the screen size and the I2C address of the charger from kbsp, then asks
* the ownership table who owns the status LED and the BQ25896. On a Kode Dot
* the log reads "screen 410 x 502", "charger at 0x6B", "LED owner: AO_LedMgr"
* and "charger owner: AO_Power".
*/
#include "kode.h"
#include "kbsp.h"
#include "kbsp_owners.h"
#define BOARD_TAG "board"
/* Bring the system up, then log what kbsp says about the board. */
void app_main(void)
{
kode_init();
KLOG_N(BOARD_TAG, "screen %u x %u", (unsigned)KBSP_LCD_H_RES, (unsigned)KBSP_LCD_V_RES);
KLOG_N(BOARD_TAG, "charger at 0x%02X", (unsigned)KBSP_I2C_ADDR_CHARGER);
/* A resource nobody claimed has no owner and answers NULL. */
const char *led_owner = kbsp_owner_name(KBSP_RES_LED);
const char *charger_owner = kbsp_owner_name(KBSP_RES_PMIC);
if (led_owner != NULL) {
KLOG_N(BOARD_TAG, "LED owner: %s", led_owner);
}
if (charger_owner != NULL) {
KLOG_N(BOARD_TAG, "charger owner: %s", charger_owner);
}
}Compiled against the SDK with ESP-IDF v6.1 on 2026-10-28. TBD: a run on a Kode Dot.
In the SDK
components/kbsp: the whole layer, withsdkconfig.boardandpartitions.csvat its root.kbsp_pins.h: every pin and address.kbsp_owners.c: the ownership table.