Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions components/display_drivers/include/class.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

/**
* @brief Base class for display drivers.
*/
class DisplayDriver {
public:
/// @brief Construct a DisplayDriver
/// @param config Configuration for the display driver
explicit DisplayDriver(const Config &config)
: config_(config) {}

/// @brief Initialize the display driver
/// @param write Write function for sending commands to the display
/// @param send_lines Function for sending pixel data to the display
/// @param width Width of the display in pixels
/// @param height Height of the display in pixels
/// @return True if initialization was successful, false otherwise
virtual bool initialize(write_command_fn write, send_lines_fn send_lines, size_t width,
size_t height) {
write_command_ = write;
send_lines_ = send_lines;
width_ = width;
height_ = height;
return true;
}

/// @brief Reset the display
virtual void reset() {
std::scoped_lock lk(config_mutex_);
if (config_.reset_pin != GPIO_NUM_NC) {
gpio_set_level(config_.reset_pin, config_.reset_value);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
gpio_set_level(config_.reset_pin, !config_.reset_value);
std::this_thread::sleep_for(std::chrono::milliseconds(120));
}
}

/// @brief Get the width of the display
/// @return Width in pixels
size_t width() const { return width_; }

/// @brief Get the height of the display
/// @return Height in pixels
size_t height() const { return height_; }

protected:
std::mutex config_mutex_;
Config config_;
size_t width_{0};
size_t height_{0};
};
195 changes: 195 additions & 0 deletions components/display_drivers/include/st7123.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#pragma once

#include <array>
#include <chrono>
#include <thread>

#include "display_drivers.hpp"

namespace espp {

class St7123 {
// MADCTL bits (see Espressif driver)
static constexpr uint8_t GS_BIT = 1 << 0; // Row mirror (Y)
static constexpr uint8_t SS_BIT = 1 << 1; // Column mirror (X)
static constexpr uint8_t BGR_BIT = 1 << 3;

public:
enum class Command : uint8_t {
NOP = 0x00,
SWRESET = 0x01,
RDDID = 0x04,
SLPIN = 0x10,
SLPOUT = 0x11,
NORON = 0x13,
INVOFF = 0x20,
INVON = 0x21,
DISPOFF = 0x28,
DISPON = 0x29,
CASET = 0x2A,
RASET = 0x2B,
RAMWR = 0x2C,
RAMRD = 0x2E,
MADCTL = 0x36,
COLMOD = 0x3A,
};

static bool initialize(const display_drivers::Config &config) {
write_command_ = config.write_command;
read_command_ = config.read_command;
lcd_send_lines_ = config.lcd_send_lines;
reset_pin_ = config.reset_pin;
dc_pin_ = config.data_command_pin;
offset_x_ = config.offset_x;
offset_y_ = config.offset_y;
mirror_x_ = config.mirror_x;
mirror_y_ = config.mirror_y;
mirror_portrait_ = config.mirror_portrait;
swap_xy_ = config.swap_xy;
swap_color_order_ = config.swap_color_order;

// Initialize display pins
display_drivers::init_pins(reset_pin_, dc_pin_, config.reset_value);

// MADCTL value
uint8_t madctl = 0;
if (mirror_x_)
madctl |= GS_BIT;
if (mirror_y_)
madctl |= SS_BIT;
if (swap_color_order_)
madctl |= BGR_BIT;
// Note: swap_xy_ not supported by ST7123 MADCTL

// COLMOD value
uint8_t colmod = 0x55; // 16bpp default
switch (config.bits_per_pixel) {
case 16:
colmod = 0x55;
break;
case 18:
colmod = 0x66;
break;
case 24:
colmod = 0x77;
break;
default:
break;
}
// ST7123 vendor-specific init sequence (from Espressif driver)
using Cmd = display_drivers::DisplayInitCmd<>;
std::array<Cmd, 27> init_cmds = {{
{0x60, {0x71, 0x23, 0xa2}, 0},
{0x60, {0x71, 0x23, 0xa3}, 0},
{0x60, {0x71, 0x23, 0xa4}, 0},
{0xA4, {0x31}, 0},
{0xD7, {0x10, 0x0A, 0x10, 0x2A, 0x80, 0x80}, 0},
{0x90, {0x71, 0x23, 0x5A, 0x20, 0x24, 0x09, 0x09}, 0},
{0xA3,
{0x80, 0x01, 0x88, 0x30, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00,
0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x4F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x6F, 0x58, 0x00, 0x00, 0x00, 0xFF},
0},
{0xA6,
{0x03, 0x00, 0x24, 0x55, 0x36, 0x00, 0x39, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24,
0x55, 0x38, 0x00, 0x37, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24, 0x11, 0x00, 0x00,
0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0xEC, 0x11, 0x00, 0x03, 0x00, 0x03, 0x6E,
0x6E, 0xFF, 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00},
0},
{0xA7,
{0x19, 0x19, 0x80, 0x64, 0x40, 0x07, 0x16, 0x40, 0x00, 0x44, 0x03, 0x6E, 0x6E, 0x91, 0xFF,
0x08, 0x80, 0x64, 0x40, 0x25, 0x34, 0x40, 0x00, 0x02, 0x01, 0x6E, 0x6E, 0x91, 0xFF, 0x08,
0x80, 0x64, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80,
0x64, 0x40, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x6E, 0x6E, 0x84, 0xFF, 0x08, 0x80, 0x44},
0},
{0xAC,
{0x03, 0x19, 0x19, 0x18, 0x18, 0x06, 0x13, 0x13, 0x11, 0x11, 0x08, 0x08, 0x0A, 0x0A, 0x1C,
0x1C, 0x07, 0x07, 0x00, 0x00, 0x02, 0x02, 0x01, 0x19, 0x19, 0x18, 0x18, 0x06, 0x12, 0x12,
0x10, 0x10, 0x09, 0x09, 0x0B, 0x0B, 0x1C, 0x1C, 0x07, 0x07, 0x03, 0x03, 0x01, 0x01},
0},
{0xAD,
{0xF0, 0x00, 0x46, 0x00, 0x03, 0x50, 0x50, 0xFF, 0xFF, 0xF0, 0x40, 0x06, 0x01,
0x07, 0x42, 0x42, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF},
0},
{0xAE, {0xFE, 0x3F, 0x3F, 0xFE, 0x3F, 0x3F, 0x00}, 0},
{0xB2,
{0x15, 0x19, 0x05, 0x23, 0x49, 0xAF, 0x03, 0x2E, 0x5C, 0xD2, 0xFF, 0x10, 0x20, 0xFD, 0x20,
0xC0, 0x00},
0},
{0xE8,
{0x20, 0x6F, 0x04, 0x97, 0x97, 0x3E, 0x04, 0xDC, 0xDC, 0x3E, 0x06, 0xFA, 0x26, 0x3E},
0},
{0x75, {0x03, 0x04}, 0},
{0xE7,
{0x3B, 0x00, 0x00, 0x7C, 0xA1, 0x8C, 0x20, 0x1A, 0xF0, 0xB1, 0x50, 0x00,
0x50, 0xB1, 0x50, 0xB1, 0x50, 0xD8, 0x00, 0x55, 0x00, 0xB1, 0x00, 0x45,
0xC9, 0x6A, 0xFF, 0x5A, 0xD8, 0x18, 0x88, 0x15, 0xB1, 0x01, 0x01, 0x77},
0},
{0xEA, {0x13, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2C}, 0},
{0xB0, {0x22, 0x43, 0x11, 0x61, 0x25, 0x43, 0x43}, 0},
{0xB7, {0x00, 0x00, 0x73, 0x73}, 0},
{0xBF, {0xA6, 0xAA}, 0},
{0xA9, {0x00, 0x00, 0x73, 0xFF, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03}, 0},
{0xC8,
{0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06,
0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32,
0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF},
0},
{0xC9,
{0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06,
0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32,
0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF},
0},
{0x11, {0x00}, 100},
{0x29, {0x00}, 0},
{0x35, {0x00}, 100},
}};

// Send vendor-specific init sequence
for (const auto &cmd : init_cmds) {
write_command_(cmd.command, std::span<const uint8_t>(cmd.parameters), 0);
if (cmd.delay_ms > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(cmd.delay_ms));
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}

// Set MADCTL (mirror/color order)
write_command_(static_cast<uint8_t>(Command::MADCTL), std::span<const uint8_t>(&madctl, 1), 0);
// Set COLMOD (color depth)
write_command_(static_cast<uint8_t>(Command::COLMOD), std::span<const uint8_t>(&colmod, 1), 0);

return true;
}

static constexpr const char *id() { return "ST7123"; }

protected:
static display_drivers::write_command_fn write_command_;
static display_drivers::read_command_fn read_command_;
static display_drivers::send_lines_fn lcd_send_lines_;
static gpio_num_t reset_pin_;
static gpio_num_t dc_pin_;
static int offset_x_;
static int offset_y_;
static bool swap_xy_;
static bool mirror_x_;
static bool mirror_y_;
static bool mirror_portrait_;
static bool swap_color_order_;
};

inline display_drivers::write_command_fn St7123::write_command_{nullptr};
inline display_drivers::read_command_fn St7123::read_command_{nullptr};
inline display_drivers::send_lines_fn St7123::lcd_send_lines_{nullptr};
inline gpio_num_t St7123::reset_pin_{GPIO_NUM_NC};
inline gpio_num_t St7123::dc_pin_{GPIO_NUM_NC};
inline int St7123::offset_x_{0};
inline int St7123::offset_y_{0};
inline bool St7123::swap_xy_{false};
inline bool St7123::mirror_x_{false};
inline bool St7123::mirror_y_{false};
inline bool St7123::mirror_portrait_{false};
inline bool St7123::swap_color_order_{false};

} // namespace espp
23 changes: 23 additions & 0 deletions components/m5stack-tab5/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The `espp::M5StackTab5` component provides a singleton hardware abstraction for

### Display & Touch
- 5″ 1280 × 720 IPS TFT screen via MIPI-DSI
- **Automatic display controller detection** (supports ILI9881 or ST7123)
- GT911 multi-touch controller (I²C) for smooth interaction
- Adjustable backlight brightness control

Expand Down Expand Up @@ -65,6 +66,28 @@ The `espp::M5StackTab5` component provides a singleton hardware abstraction for
| Battery | NP-F550 2000mAh removable |
| Expansion | Grove, M5-Bus, STAMP pads, GPIO headers |

## Display Controller Auto-Detection

The M5Stack Tab5 hardware can be manufactured with one of two different MIPI-DSI display controllers:
- **ILI9881** (earlier hardware revisions)
- **ST7123** (newer hardware revisions)

The BSP automatically detects which display controller is present during initialization by:
1. Attempting to initialize with the ILI9881 driver first
2. If ILI9881 detection fails, falling back to ST7123 initialization
3. Logging the detected controller type for debugging

This means your application code works seamlessly across both hardware variants without any code changes. You can optionally query the detected controller type:

```cpp
auto& tab5 = espp::M5StackTab5::get();
tab5.initialize_lcd();

// Query the detected controller
auto controller_type = tab5.get_display_controller();
const char* controller_name = tab5.get_display_controller_name();
```

## Example

The [example](./example) shows how to use the `espp::M5StackTab5` hardware abstraction component to initialize and use various subsystems of the Tab5.
Expand Down
9 changes: 6 additions & 3 deletions components/m5stack-tab5/example/main/m5stack_tab5_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,17 +307,20 @@ extern "C" void app_main(void) {
// start a simple thread to do the lv_task_handler every 16ms
logger.info("Starting LVGL task...");
espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool {
auto start_time = std::chrono::high_resolution_clock::now();
{
std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
lv_task_handler();
}
std::unique_lock<std::mutex> lock(m);
cv.wait_for(lock, 16ms);
cv.wait_until(lock, start_time + 16ms, []() { return false; });
return false;
},
.task_config = {
.name = "lv_task",
.stack_size_bytes = 10 * 1024,
.priority = 20,
.core_id = 1,
}});
lv_task.start();

Expand Down Expand Up @@ -348,7 +351,7 @@ extern "C" void app_main(void) {
// sleep first in case we don't get IMU data and need to exit early
{
std::unique_lock<std::mutex> lock(m);
cv.wait_for(lock, 10ms);
cv.wait_for(lock, 20ms);
}
static auto &tab5 = espp::M5StackTab5::get();
static auto imu = tab5.imu();
Expand Down Expand Up @@ -483,7 +486,7 @@ extern "C" void app_main(void) {
.name = "Data Display Task",
.stack_size_bytes = 6 * 1024,
.priority = 10,
.core_id = 0,
.core_id = 1,
}});
imu_task.start();

Expand Down
33 changes: 31 additions & 2 deletions components/m5stack-tab5/include/m5stack-tab5.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "led.hpp"
#include "pi4ioe5v.hpp"
#include "rx8130ce.hpp"
#include "st7123.hpp"
#include "touchpad_input.hpp"
// #include "wifi_ap.hpp"
// #include "wifi_sta.hpp"
Expand Down Expand Up @@ -75,8 +76,33 @@ class M5StackTab5 : public BaseComponent {
/// Alias for the pixel type used by the Tab5 display
using Pixel = lv_color16_t;

/// Alias for the display driver used by the Tab5
using DisplayDriver = espp::Ili9881;
/// Enum for display controller type
enum class DisplayController { UNKNOWN, ILI9881, ST7123 };

DisplayController detect_display_controller();

/// Get the detected display controller type
/// \return The display controller type
DisplayController get_display_controller() const { return display_controller_; }

/// Get a string name for the display controller
/// \return String name of the controller
const char *get_display_controller_name() const {
return get_display_controller_name(display_controller_);
}

/// Get a string name for the display controller
/// \return String name of the controller
const char *get_display_controller_name(DisplayController controller) const {
switch (controller) {
case DisplayController::ILI9881:
return "ILI9881";
case DisplayController::ST7123:
return "ST7123";
default:
return "Unknown";
}
}

/// Alias for the GT911 touch controller used by the Tab5
using TouchDriver = espp::Gt911;
Expand Down Expand Up @@ -710,6 +736,9 @@ class M5StackTab5 : public BaseComponent {
esp_lcd_panel_handle_t panel{nullptr}; // color handle
} lcd_handles_{};

// Display controller detection
DisplayController display_controller_{DisplayController::UNKNOWN};

// original function pointer for the panel del, init
esp_err_t (*original_panel_del_)(esp_lcd_panel_t *panel){nullptr};
esp_err_t (*original_panel_init_)(esp_lcd_panel_t *panel){nullptr};
Expand Down
Loading