Initial commit: Base Project for Kode Dot (ESP32-S3)

This commit is contained in:
Lago
2026-04-03 13:21:23 +02:00
commit a3d9840a92
30 changed files with 59419 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html
+37
View File
@@ -0,0 +1,37 @@
# KodeDotBSP
Board Support Package for Kode Dot: display bring-up (Arduino_GFX), LVGL integration and capacitive touch.
## Usage
Include the public headers with the `kodedot/` prefix:
```cpp
#include <kodedot/display_manager.h>
#include <kodedot/pin_config.h>
```
Initialize once in `setup()` and update in `loop()`:
```cpp
DisplayManager display;
void setup() {
Serial.begin(115200);
if (!display.init()) {
while (1) delay(1000);
}
}
void loop() {
display.update();
delay(5);
}
```
`pin_config.h` exposes board pins and constants for the Kode Dot.
## Notes
- Prefers PSRAM for LVGL draw buffers; falls back to internal SRAM.
- Registers LVGL display and input drivers.
- Provides simple brightness and touch helpers.
@@ -0,0 +1,78 @@
#pragma once
#include <Arduino.h>
#include <Arduino_GFX_Library.h>
#include <lvgl.h>
#include <kodedot/pin_config.h>
#include <bb_captouch.h>
/**
* @brief High-level manager that initializes and wires up the display, LVGL, and touch input.
*
* Responsibilities:
* - Bring up the panel using Arduino_GFX
* - Allocate LVGL draw buffers (prefer PSRAM, fallback to SRAM)
* - Register LVGL display and input drivers
* - Provide simple helpers for brightness and touch reading
*/
class DisplayManager {
private:
// Hardware interfaces
Arduino_DataBus *bus;
Arduino_CO5300 *gfx;
BBCapTouch bbct;
// LVGL draw buffer and driver handles
lv_disp_draw_buf_t draw_buf;
lv_color_t *buf;
lv_color_t *buf2;
lv_disp_drv_t disp_drv;
// Static callbacks required by LVGL
static void disp_flush_callback(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p);
static void touchpad_read_callback(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
// Singleton-like back-reference used by static callbacks
static DisplayManager* instance;
public:
DisplayManager();
~DisplayManager();
/**
* @brief Fully initialize display, LVGL, and touch.
* @return true on success, false otherwise
*/
bool init();
/**
* @brief Get the underlying Arduino_GFX panel instance.
*/
Arduino_CO5300* getGfx() { return gfx; }
/**
* @brief Get the touch controller instance.
*/
BBCapTouch* getTouch() { return &bbct; }
/**
* @brief Pump LVGL timers and tick. Call frequently in loop().
*/
void update();
/**
* @brief Set backlight brightness.
* @param brightness Range 0-255
*/
void setBrightness(uint8_t brightness);
/**
* @brief Read current touch coordinates.
* @param x Output X coordinate
* @param y Output Y coordinate
* @return true if there is an active touch
*/
bool getTouchCoordinates(int16_t &x, int16_t &y);
};
@@ -0,0 +1,88 @@
#pragma once
/* ---------- Buttons ---------- */
#define BUTTON_TOP 0 // GPIO_NUM_0
// Bottom button is provided by the IO expander
#define BUTTON_BOTTOM EXPANDER_BUTTON_BOTTOM
/* ---------- NeoPixel LED ---------- */
#define LED_STRIP_PIN 4
#define LED_STRIP_NUM_LEDS 1
#define LED_STRIP_RMT_RES_HZ 10000000 // 10 MHz
/* ---------- LCD Panel ---------- */
#define LCD_WIDTH 410
#define LCD_HEIGHT 502
#define LCD_SPI_HOST SPI3_HOST
#define LCD_PIXEL_CLK_HZ 40000000 // 40 MHz
#define LCD_CMD_BITS 8
#define LCD_PARAM_BITS 8
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_RGB
#define LCD_BITS_PER_PIXEL 16
#define LCD_DRAW_BUFF_DOUBLE 1
// Use full-height buffer (PSRAM available)
#define LCD_DRAW_BUFF_HEIGHT LCD_HEIGHT
// LCD pins (QSPI)
#define LCD_SCLK 17
#define LCD_SDIO0 15
#define LCD_SDIO1 14
#define LCD_SDIO2 16
#define LCD_SDIO3 10
#define LCD_RST 8
#define LCD_CS 9
#define LCD_EN -1 // no dedicated enable pin
/* ---------- Touch / IO Expander ---------- */
#define TOUCH_I2C_NUM 0
#define TOUCH_I2C_SCL 47
#define TOUCH_I2C_SDA 48
#define TOUCH_INT -1
#define TOUCH_RST -1
#define IOEXP_I2C_NUM TOUCH_I2C_NUM
#define IOEXP_I2C_SCL TOUCH_I2C_SCL
#define IOEXP_I2C_SDA TOUCH_I2C_SDA
#define IOEXP_I2C_ADDR 0x20 // TCA9555 address 000
#define IOEXP_INT_PIN 18
/* ---------- SD-Card (SDMMC) ---------- */
// Host and frequency
#define SD_SDMMC_HOST SDMMC_HOST_SLOT_1
#define SD_MAX_FREQ_KHZ 50000
// SDMMC pins (1-bit width)
#define SD_PIN_CMD 5
#define SD_PIN_CLK 6
#define SD_PIN_D0 7
// Card detect via IO expander
#define SD_DETECT_PIN EXPANDER_SD_CD
// Mount parameters
#define SD_MOUNT_POINT "/sdcard"
#define SD_MAX_FILES 5
#define SD_FORMAT_IF_FAIL 0
/* ---------- Sensors ---------- */
#define MAX17048_I2C_ADDRESS 0x36
#define BQ25896_I2C_ADDRESS 0x6A
/* ---------- IO Expander pin map ---------- */
// These defines require including <TCA9555.h> in the source file that uses them
#define EXPANDER_MAG_INT 0x00 // P00 - Magnetometer interrupt
#define EXPANDER_RTC_INTB 0x01 // P01 - RTC INTB
#define EXPANDER_RTC_INTA 0x02 // P02 - RTC INTA
#define EXPANDER_SPK_SHUTDOWN 0x03 // P03 - Speaker shutdown
#define EXPANDER_PWR_PHRL 0x04 // P04 - Power PHRL
#define EXPANDER_FG_ALRT 0x05 // P05 - Fuel gauge alert
#define EXPANDER_PAD_TOP 0x06 // P06 - D-pad TOP (external pull-up)
#define EXPANDER_PAD_LEFT 0x07 // P07 - D-pad LEFT (external pull-up)
#define EXPANDER_PAD_BOTTOM 8 // P10 - D-pad BOTTOM (external pull-up)
#define EXPANDER_BUTTON_BOTTOM 9 // P11 - Bottom button (external pull-up)
#define EXPANDER_PWR_INT 10 // P12 - Power INT
#define EXPANDER_PAD_RIGHT 11 // P13 - D-pad RIGHT (external pull-up)
#define EXPANDER_SD_CD 14 // P16 - SD card detect
/* ---------- NeoPixel convenience ---------- */
#define NEO_PIXEL_PIN LED_STRIP_PIN
#define NEO_PIXEL_COUNT LED_STRIP_NUM_LEDS
// Type: NEO_GRB + NEO_KHZ800 (define in the source using Adafruit_NeoPixel)
+22
View File
@@ -0,0 +1,22 @@
{
"name": "KodeDotBSP",
"version": "0.1.0",
"license": "MIT",
"description": "Board Support Package for Kode Dot: display, LVGL and touch bring-up.",
"authors": [
{
"name": "Kode Project",
"maintainer": true
}
],
"build": {
"includeDir": "include",
"srcDir": "src"
},
"frameworks": ["arduino"],
"platforms": ["espressif32"],
"headers": [
"kodedot/display_manager.h",
"kodedot/pin_config.h"
]
}
@@ -0,0 +1,10 @@
// src/custom_ota_override.cpp
// Strong override of the weak boot/OTA hook in the core.
// Returning true means: "do NOT auto-validate the running OTA image".
// After any reset, the bootloader will roll back to the last valid app (your factory).
extern "C" bool verifyRollbackLater(); // must use C linkage to match the weak symbol
extern "C" bool verifyRollbackLater() {
return true; // never auto-validate OTAs
}
+204
View File
@@ -0,0 +1,204 @@
#include <kodedot/display_manager.h>
#include <Preferences.h>
// Forward declarations for internal helpers
extern "C" void __wrap_esp_ota_mark_app_valid_cancel_rollback(void);
void init_nvs();
void lvgl_port_rounder_callback(lv_disp_drv_t *disp_drv, lv_area_t *area);
// Static instance used by LVGL callbacks
DisplayManager* DisplayManager::instance = nullptr;
// --- Internal system utilities ---
extern "C" void __wrap_esp_ota_mark_app_valid_cancel_rollback(void) {
// no-op: always disables OTA validation
}
static Preferences prefs;
void init_nvs() {
// Initialize NVS namespace for application storage
prefs.begin("kode_storage", false);
}
// --- LVGL helper ---
void lvgl_port_rounder_callback(lv_disp_drv_t *disp_drv, lv_area_t *area) {
// Efficiently align area to even/odd boundaries as required by the panel
area->x1 &= ~1; // round down to even
area->y1 &= ~1;
area->x2 = (area->x2 & ~1) + 1; // round up to odd
area->y2 = (area->y2 & ~1) + 1;
}
DisplayManager::DisplayManager() : bus(nullptr), gfx(nullptr), buf(nullptr), buf2(nullptr) {
instance = this;
}
DisplayManager::~DisplayManager() {
if (buf) free(buf);
if (buf2) free(buf2);
if (gfx) {
delete gfx;
}
if (bus) {
delete bus;
}
instance = nullptr;
}
bool DisplayManager::init() {
Serial.println("Bringing up display subsystem...");
// Initialize NVS (preferences)
init_nvs();
// Power on the panel if an enable pin is provided
if (LCD_EN >= 0) {
pinMode(LCD_EN, OUTPUT);
digitalWrite(LCD_EN, HIGH);
}
bus = new Arduino_ESP32QSPI(
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3
);
gfx = new Arduino_CO5300(
bus, LCD_RST, 0, LCD_WIDTH, LCD_HEIGHT,
22, 0, 0, 0
);
if (!gfx->begin()) {
Serial.println("Error: failed to initialize panel");
return false;
}
gfx->setRotation(0);
// Load brightness percentage (0-100) from NVS and apply to panel (0-255)
{
uint8_t saved_pct = prefs.getUChar("brightness_pct", 100);
if (saved_pct > 100) saved_pct = 100;
uint8_t saved_brightness = (uint8_t)(((uint16_t)saved_pct * 255 + 50) / 100); // round
gfx->setBrightness(saved_brightness);
Serial.printf("Brightness (pct=%u) applied from NVS\n", (unsigned)saved_pct);
}
gfx->fillScreen(BLACK);
Serial.println("Panel initialized");
// Initialize LVGL core
lv_init();
// Allocate draw buffers (prefer PSRAM; fallback to internal SRAM)
size_t draw_buf_pixels = (size_t)LCD_WIDTH * (size_t)LCD_DRAW_BUFF_HEIGHT;
size_t draw_buf_bytes = draw_buf_pixels * sizeof(lv_color_t);
Serial.printf("Requesting LVGL draw buffer: %u bytes\n", (unsigned)draw_buf_bytes);
buf = (lv_color_t*)heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!buf) {
// Fallback to internal SRAM with reduced height window
size_t fallback_height = 100; // small window to avoid exhausting SRAM
draw_buf_pixels = (size_t)LCD_WIDTH * fallback_height;
draw_buf_bytes = draw_buf_pixels * sizeof(lv_color_t);
Serial.printf("PSRAM not available, using SRAM fallback: %u bytes\n", (unsigned)draw_buf_bytes);
buf = (lv_color_t*)heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (!buf) {
Serial.println("Error: unable to allocate draw buffer in SRAM");
return false;
}
}
// Try double buffering in PSRAM if there is room
buf2 = (lv_color_t*)heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!buf2) {
// If not enough memory, keep single buffer
buf2 = NULL;
}
// Configure LVGL display driver
lv_disp_draw_buf_init(&draw_buf, buf, buf2, (uint32_t)draw_buf_pixels);
// Initialize the display driver
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = LCD_WIDTH;
disp_drv.ver_res = LCD_HEIGHT;
disp_drv.flush_cb = disp_flush_callback;
disp_drv.rounder_cb = lvgl_port_rounder_callback; // avoid visual artifacts on odd/even alignment
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
Serial.println("LVGL initialized");
// Initialize capacitive touch
if(bbct.init(TOUCH_I2C_SDA, TOUCH_I2C_SCL, TOUCH_RST, TOUCH_INT) != CT_SUCCESS) {
Serial.println("Error: failed to initialize touch");
return false;
} else {
Serial.println("Touch initialized");
}
// Register LVGL input driver (touch)
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touchpad_read_callback;
lv_indev_drv_register(&indev_drv);
Serial.println("Display subsystem ready");
return true;
}
void DisplayManager::update() {
// Advance LVGL tick by 5 ms per iteration
lv_tick_inc(5);
// Let LVGL process timers
lv_timer_handler();
}
void DisplayManager::setBrightness(uint8_t brightness) {
if (gfx) {
gfx->setBrightness(brightness);
}
// Persist brightness as percentage (0-100) in NVS
uint8_t pct = (uint8_t)(((uint16_t)brightness * 100 + 127) / 255); // round
if (pct > 100) pct = 100;
prefs.putUChar("brightness_pct", pct);
}
bool DisplayManager::getTouchCoordinates(int16_t &x, int16_t &y) {
TOUCHINFO ti;
if(bbct.getSamples(&ti) && ti.count > 0) {
x = ti.x[0];
y = ti.y[0];
return true;
}
return false;
}
// LVGL display flush callback
void DisplayManager::disp_flush_callback(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
if (!instance || !instance->gfx) return;
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
instance->gfx->startWrite();
instance->gfx->writeAddrWindow(area->x1, area->y1, w, h);
instance->gfx->writePixels((uint16_t *)&color_p->full, w * h);
instance->gfx->endWrite();
lv_disp_flush_ready(disp);
}
// LVGL touch read callback
void DisplayManager::touchpad_read_callback(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) {
if (!instance) return;
TOUCHINFO ti;
if(instance->bbct.getSamples(&ti) && ti.count > 0) {
data->state = LV_INDEV_STATE_PRESSED;
data->point.x = ti.x[0];
data->point.y = ti.y[0];
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}