Initial commit: Base Project for Kode Dot (ESP32-S3)
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user