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:

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
Who reads kbsp

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:

Specifications

The board

The facts about the chip, the glass and the rails.

ThingValue
Product, revisionKBSP_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 chipESP32-P4, two RISC-V cores at 400 MHz. Revision v3.2 on the Kode Dot
Flash32 MB NOR, GD25Q256E, JEDEC 0xC84019
PSRAM32 MB in package, HEX mode, 250 MHz
ScreenCO5300 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 railKBSP_LCD_DPHY_LDO_CHAN 3 at KBSP_LCD_DPHY_LDO_MV 2500, up before any MIPI traffic
SD IO railKBSP_SD_LDO_CHAN 4, the supply of the SDMMC pins. The card's own supply is a pin, KBSP_PIN_SD_PWR
microSD defaultsKBSP_SD_WIDTH 4 lines, KBSP_SD_FREQ_KHZ 40000
C5 linkKBSP_C5_SDIO_WIDTH 4, KBSP_C5_SDIO_KHZ 40000. kdrv_c5 asserts at compile time that sdkconfig.board agrees
CrystalsKBSP_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 kbspGPIO0 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.

NameValueControllerOwner
KBSP_I2C_BOARD0I2C0, the board buskhal_i2c
KBSP_SPI_NFC1SPI2the ST25R3916B
KBSP_I2S_CODEC0I2S0the ES8311
KBSP_ADC_EXP0ADC1the analog positions of J3
KBSP_ADC_RFID1ADC2the demodulated 125 kHz line
KBSP_EXP_I2C_ID1I2C1an app, through khal_exp_ctrl()
KBSP_EXP_SPI_ID2SPI3 (SPI3_HOST)an app, through khal_exp_ctrl()
UART0 to UART4KBSP_EXP_UART_LAST 4five UARTsan 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:

ConstantAddressChip
KBSP_I2C_ADDR_LED0x30KTD2026 RGB LED driver (U8)
KBSP_I2C_ADDR_IOEXP0x20TCA6408A expander (U24), ADDR tied low
KBSP_I2C_ADDR_IMU0x6AQMI8658A (U10), SA0 tied high
KBSP_I2C_ADDR_MAG0x2CQMC5883P (U5), a chip of its own on the bus, not behind the IMU
KBSP_I2C_ADDR_CODEC0x18ES8311
KBSP_I2C_ADDR_CHARGER0x6BBQ25896
KBSP_I2C_ADDR_GAUGE0x64CW2217
KBSP_I2C_ADDR_HAPTIC0x5AAW86233 (U7), on the antenna PCB. The chip fixes the address
KBSP_I2C_ADDR_TOUCH0x15CST820, 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.

The eight pins:

PinConstantNetPull-up
P0KBSP_IOEXP_BIT_BTN_BOTBUTTON_BOT (SW2), also the QON pin of the BQ25896TBD: see below
P1KBSP_IOEXP_BIT_PAD_UPPAD_TOP (SW3)R39, 10 kΩ
P2KBSP_IOEXP_BIT_PAD_LEFTPAD_LEFT (SW4)R40, 10 kΩ
P3KBSP_IOEXP_BIT_PAD_DOWNPAD_BOTTOM (SW5)R41, 10 kΩ
P4KBSP_IOEXP_BIT_PAD_RIGHTPAD_RIGHT (SW6)R42, 10 kΩ
P5KBSP_IOEXP_BIT_IMU_INT1INT1 of the QMI8658A, driven push-pull by the chipnone, so the driver makes INT1 a driven output
P6KBSP_IOEXP_BIT_TP_INTtouch interrupt, inputon the panel module
P7KBSP_IOEXP_BIT_DSP_PWRVCI_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.

PartPinsNotes
Display and touch resetKBSP_PIN_DSP_TP_RST GPIO7Active low. The panel and the touch controller share it
microSDSDMMC slot 0: D0 to D3 on GPIO39 to 42, CLK GPIO43, CMD GPIO44Power is KBSP_PIN_SD_PWR GPIO22, active low through Q1. Card detect is IO6 of the C5 (KBSP_C5_IO_SD_CD)
USB-Serial/JTAGGPIO24 (D-), GPIO25 (D+)The console and the flasher
Magnetic padsGPIO26, GPIO27The second full-speed USB pair, used by the dock console. kbsp names no constant: the PHY has fixed pins
Audio, I2S to the ES8311MCLK 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
InfraredIO24 (TX) and IO23 (RX) of the C5KBSP_C5_IO_IR_TX, KBSP_C5_IO_IR_RX. The P4 has no infrared pins
NFC, ST25R3916B, SPI mode 1CS GPIO28, IRQ GPIO29, CLK GPIO30, MOSI GPIO31, MISO GPIO32The reader is alone on SPI2
RFID, 125 kHzOUT GPIO47, PULL GPIO45, CARRIER GPIO46, RF_IN GPIO54RF_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-C5EN GPIO23 (high is running). SDIO: CLK GPIO50, CMD GPIO51, D0 to D3 on GPIO49, 48, 53, 52kdrv_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:

PositionJ3 pinGPIOExtraADC1 channel
EXP138LPnone
EXP249LPnone
EXP3510LPnone
EXP4611LPnone
EXP5712LPnone
EXP6813LPnone
EXP7914LPnone
EXP81221ADC5
EXP91319ADC3
EXP101420ADC4
EXP111517ADC1
EXP121618ADC2
EXP131715LPnone
EXP141816ADC0

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:

ConstantValueMeaning
KBSP_CELL_RSENSE_MOHM10The shunt that the CW2217 reads. Charge reads positive, the same sign as the charger
KBSP_CELL_R_INT_MOHM180Internal resistance, for OCV = V - I * R_int
KBSP_CELL_OCV_MV, KBSP_CELL_OCV_PCT13 pointsThe curve from 3300 mV = 0 % to 4200 mV = 100 %
KBSP_CELL_VCUT_MV3000Discharge cut-off
KBSP_CELL_VTRIP_MV4280Over-voltage trip of the protection board. A reading outside 3000 to 4280 mV is not a battery reading
KBSP_CELL_CAPACITY_MAH1000Capacity
KBSP_CELL_VREG_MV4192Charge voltage, 8 mV under the pack's 4.20 V
KBSP_CELL_ICHG_MA512Fast charge, 0.5 C
KBSP_CELL_IPRECHG_MA, KBSP_CELL_ITERM_MA64, 64Pre-charge and termination, the chip's floor
KBSP_CELL_ICHG_MAX_MA896The most charge_ma may ask for
KBSP_CELL_BOOST_LIM_CODE0BOOST_LIM code 0 is 0.5 A, which keeps the draw under the pack's peak
KBSP_CELL_BOOST_HOT_CODE1BHOT code 1, about 55 °C
KBSP_CELL_BOOST_MIN_MV3600Under this, a boost request is refused
KBSP_CELL_BOOST_STOP_MV3300Under 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.

ResourceWhat it isClaimed by
KBSP_RES_LEDKTD2026 at 0x30AO_LedMgr
KBSP_RES_PANELMIPI-DSI and the CO5300AO_Display
KBSP_RES_TOUCHCST820 at 0x15AO_Touch
KBSP_RES_IOEXPTCA6408A at 0x20 and its interrupt lineAO_IoExp
KBSP_RES_BTN_TOPGPIO35, the only button on a P4 pinAO_Buttons
KBSP_RES_SDMMCthe microSD slotAO_Storage
KBSP_RES_FATFSthe internal storage partitionAO_Storage
KBSP_RES_OTA_SLOTthe inactive kodeOS slotAO_AppMgr
KBSP_RES_OTADATAthe otadata partition: which firmware startsAO_Boot
KBSP_RES_APPS_REGIONthe apps partition: directory and imagesAO_AppMgr
KBSP_RES_APPS_BOOTREQthe boot-request sector: which app starts nextAO_Boot
KBSP_RES_APP_PENDINGthe app_pending mailbox partitionAO_AppMgr
KBSP_RES_PMICBQ25896 at 0x6BAO_Power
KBSP_RES_GAUGECW2217 at 0x64AO_Power
KBSP_RES_CONSOLEthe console transportAO_Log
KBSP_RES_C5the whole ESP32-C5: EN line, link, its pins, its flashAO_Radio
KBSP_RES_JPEGthe P4's JPEG codec, one engineAO_Display, with the mirror
KBSP_RES_PPAthe P4's pixel accelerator, one engineAO_Display, with the mirror
KBSP_RES_IRthe emitters and the receiver, owned wholeAO_Ir
KBSP_RES_NFCthe ST25R3916B, its IRQ and SPI2AO_Nfc
KBSP_RES_AUDIOthe ES8311, microphone, speaker, amplifier path and I2S with its five pinsAO_Audio
KBSP_RES_IMUthe QMI8658A and the QMC5883PAO_Imu
KBSP_RES_HAPTICthe AW86233 at 0x5AAO_Haptic
KBSP_RES_RFIDOUT GPIO47, PULL GPIO45, CARRIER GPIO46 and the demodulated input GPIO54 with its ADCAO_Rfid
KBSP_RES_EXPthe expansion header, all fourteen positionsthe 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.

CallWhat 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.

OptionValueWhy
ESPTOOLPY_FLASHSIZE_32MByThe GD25Q256E
BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASHy4-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_FEATURESyThe gate for the option above
PARTITION_TABLE_CUSTOM, PARTITION_TABLE_OFFSETy, 0x8000A custom table, at the address the Arduino core looks for
MMU_PAGE_SIZE_64KByAn app image mapped in place starts on an MMU page, so KAPPS_ALIGN (0x10000) is this number
CACHE_L2_CACHE_128KBy128 KB of L2 cache. 512 KB does not boot

sdkconfig.board: memory

PSRAM and where the big buffers go.

OptionValueWhy
SPIRAM, SPIRAM_MODE_HEX, SPIRAM_SPEED_250My32 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_NOTFOUNDyBoot without PSRAM when it does not answer. IDF then sizes the Wi-Fi and lwIP buffers as if there were none
SPIRAM_MALLOC_ALWAYSINTERNAL0A plain malloc() takes PSRAM. Internal RAM is asked for by capability
SPIRAM_MEMTESTnNo walk of 32 MB at every boot. test component=psram tests it instead
ESP_HOSTED_MEMPOOL_PREFER_SPIRAMyThe 88 KB of esp-hosted transport pools in PSRAM
BT_NIMBLE_MEM_ALLOC_MODE_EXTERNALyNimBLE's 32 KB of buffers in PSRAM
MBEDTLS_EXTERNAL_MEM_ALLOCyTLS buffers in PSRAM: a session holds about 30 KB of the internal pool

The wiring of the C5 and the BLE host.

OptionValueWhy
ESP_HOSTED_* link linesSDIO slot 1, CMD 51, CLK 50, D0 49, D1 48, D2 53, D3 52, reset 23, 40 MHz, 4-bitThe 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_EXPANDERyReaches the C5's own pins by RPC, for the SD card detect
ESP_HOSTED_SLAVE_RESET_ONLY_IF_NECESSARYyesp-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_NIMBLEyThe 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_MBEDTLSyBonds survive a reboot. Pairing needs P-256 ECDH, which the default backend fails
BT_NIMBLE_LOG_LEVEL_WARNING, BT_NIMBLE_MSYS_1_BLOCK_COUNTy, 24NimBLE'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.

OptionValueWhy
ESP_CONSOLE_USB_SERIAL_JTAGyThe console is the native USB. The pins of UART0 go to the audio codec
ESP_DEFAULT_CPU_FREQ_MHZ_400y400 MHz, stated so a later default cannot move it
RTC_CLK_SRC_EXT_CRYSyThe RTC runs on the 32.768 kHz crystal X3, not the internal RC
FREERTOS_HZ1000A 1 ms scheduler tick
ESP_MAIN_TASK_STACK_SIZE8192kode_init() starts every owner Active Object on the main task, and QP/C runs each initial transition on it
LCD_DSI_ISR_CACHE_SAFEyThe DSI interrupt keeps running while the flash cache is off, which a long erase causes
MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFYyA 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_DEBUGyA 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.

OptionValueWhy
LV_USE_CUSTOM_MALLOCyLVGL allocates from PSRAM through kui_mem.c, with no fallback
LV_DEF_REFR_PERIOD16One 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_PRIOy, 2, 32768, 3Two draw threads, one per core, with 32 KB stacks in PSRAM for the Lottie rasteriser
LV_COLOR_FORMAT_RGB565, LV_COLOR_MIX_ROUND_OFSy, 128The 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_NAME128, y, yThe 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:

NameTypeSubtypeOffsetSizeHolds
nvsdatanvs0xB0000x10000Settings, and Preferences of Arduino sketches
otadatadataota0x1B0000x2000Which OTA slot starts. kodeOS alone writes it
phy_initdataphy0x1D0000x1000RF calibration data
factoryappfactory0x200000x400000The rescue copy of kodeOS, written by cable only
ota_0appota_00x4200000x400000Update slot of kodeOS
ota_1appota_10x8200000x400000Update slot of kodeOS
coredumpdatacoredump0xC200000x20000The last panic, in ELF format. Below 16 MB because the panic handler writes it with the cache off
appsdata0x400xC400000xBC0000Directory, boot request and installed app images (internals/apps-region)
storagedatafat0x18000000x200000The internal FAT volume, wear-levelled
app_pendingdata0x410x1A000000x600000The 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.

FileWhat it does
board/include/kbsp.hUmbrella header: includes kbsp_pins.h, kbsp_board.h and kbsp_cell.h
board/include/kbsp_pins.hEvery GPIO, I2C address and expander bit
board/include/kbsp_board.hThe product name and revision, the glass, the rails, the links, the crystals and the controllers
board/include/kbsp_cell.hThe battery: shunt, charge profile, boost limits and the OCV curve
board/include/kbsp_identity.hKBSP_ID_PRODUCT and KBSP_ID_PREFIX. Not pulled in by kbsp.h
exp/include/kbsp_exp.hThe kbsp_exp_t type, KBSP_EXP[] and the controllers an app may take
exp/src/kbsp_exp.cThe 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.hkbsp_res_t and the four calls
owners/src/kbsp_owners.cThe table of owners: one atomic token and one atomic name per resource
sdkconfig.boardThe build options of the chip and the wiring
partitions.csvThe flash map above
CMakeLists.txtRegisters 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