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
+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;
}
}