Skip to content

Commit 35428fb

Browse files
authored
feat(m5tab5): Add support for querying and automatically determining the appropriate display driver closes #573 (#576)
* feat(m5tab5): Add support for querying and automatically determining the appropriate display driver closes #573 * updated with fixes and improvements from @CarbonNeuron (thanks!) * updated st7123 display driver * minor update
1 parent e19da2c commit 35428fb

6 files changed

Lines changed: 381 additions & 17 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
2+
/**
3+
* @brief Base class for display drivers.
4+
*/
5+
class DisplayDriver {
6+
public:
7+
/// @brief Construct a DisplayDriver
8+
/// @param config Configuration for the display driver
9+
explicit DisplayDriver(const Config &config)
10+
: config_(config) {}
11+
12+
/// @brief Initialize the display driver
13+
/// @param write Write function for sending commands to the display
14+
/// @param send_lines Function for sending pixel data to the display
15+
/// @param width Width of the display in pixels
16+
/// @param height Height of the display in pixels
17+
/// @return True if initialization was successful, false otherwise
18+
virtual bool initialize(write_command_fn write, send_lines_fn send_lines, size_t width,
19+
size_t height) {
20+
write_command_ = write;
21+
send_lines_ = send_lines;
22+
width_ = width;
23+
height_ = height;
24+
return true;
25+
}
26+
27+
/// @brief Reset the display
28+
virtual void reset() {
29+
std::scoped_lock lk(config_mutex_);
30+
if (config_.reset_pin != GPIO_NUM_NC) {
31+
gpio_set_level(config_.reset_pin, config_.reset_value);
32+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
33+
gpio_set_level(config_.reset_pin, !config_.reset_value);
34+
std::this_thread::sleep_for(std::chrono::milliseconds(120));
35+
}
36+
}
37+
38+
/// @brief Get the width of the display
39+
/// @return Width in pixels
40+
size_t width() const { return width_; }
41+
42+
/// @brief Get the height of the display
43+
/// @return Height in pixels
44+
size_t height() const { return height_; }
45+
46+
protected:
47+
std::mutex config_mutex_;
48+
Config config_;
49+
size_t width_{0};
50+
size_t height_{0};
51+
};
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
#pragma once
2+
3+
#include <array>
4+
#include <chrono>
5+
#include <thread>
6+
7+
#include "display_drivers.hpp"
8+
9+
namespace espp {
10+
11+
class St7123 {
12+
// MADCTL bits (see Espressif driver)
13+
static constexpr uint8_t GS_BIT = 1 << 0; // Row mirror (Y)
14+
static constexpr uint8_t SS_BIT = 1 << 1; // Column mirror (X)
15+
static constexpr uint8_t BGR_BIT = 1 << 3;
16+
17+
public:
18+
enum class Command : uint8_t {
19+
NOP = 0x00,
20+
SWRESET = 0x01,
21+
RDDID = 0x04,
22+
SLPIN = 0x10,
23+
SLPOUT = 0x11,
24+
NORON = 0x13,
25+
INVOFF = 0x20,
26+
INVON = 0x21,
27+
DISPOFF = 0x28,
28+
DISPON = 0x29,
29+
CASET = 0x2A,
30+
RASET = 0x2B,
31+
RAMWR = 0x2C,
32+
RAMRD = 0x2E,
33+
MADCTL = 0x36,
34+
COLMOD = 0x3A,
35+
};
36+
37+
static bool initialize(const display_drivers::Config &config) {
38+
write_command_ = config.write_command;
39+
read_command_ = config.read_command;
40+
lcd_send_lines_ = config.lcd_send_lines;
41+
reset_pin_ = config.reset_pin;
42+
dc_pin_ = config.data_command_pin;
43+
offset_x_ = config.offset_x;
44+
offset_y_ = config.offset_y;
45+
mirror_x_ = config.mirror_x;
46+
mirror_y_ = config.mirror_y;
47+
mirror_portrait_ = config.mirror_portrait;
48+
swap_xy_ = config.swap_xy;
49+
swap_color_order_ = config.swap_color_order;
50+
51+
// Initialize display pins
52+
display_drivers::init_pins(reset_pin_, dc_pin_, config.reset_value);
53+
54+
// MADCTL value
55+
uint8_t madctl = 0;
56+
if (mirror_x_)
57+
madctl |= GS_BIT;
58+
if (mirror_y_)
59+
madctl |= SS_BIT;
60+
if (swap_color_order_)
61+
madctl |= BGR_BIT;
62+
// Note: swap_xy_ not supported by ST7123 MADCTL
63+
64+
// COLMOD value
65+
uint8_t colmod = 0x55; // 16bpp default
66+
switch (config.bits_per_pixel) {
67+
case 16:
68+
colmod = 0x55;
69+
break;
70+
case 18:
71+
colmod = 0x66;
72+
break;
73+
case 24:
74+
colmod = 0x77;
75+
break;
76+
default:
77+
break;
78+
}
79+
// ST7123 vendor-specific init sequence (from Espressif driver)
80+
using Cmd = display_drivers::DisplayInitCmd<>;
81+
std::array<Cmd, 27> init_cmds = {{
82+
{0x60, {0x71, 0x23, 0xa2}, 0},
83+
{0x60, {0x71, 0x23, 0xa3}, 0},
84+
{0x60, {0x71, 0x23, 0xa4}, 0},
85+
{0xA4, {0x31}, 0},
86+
{0xD7, {0x10, 0x0A, 0x10, 0x2A, 0x80, 0x80}, 0},
87+
{0x90, {0x71, 0x23, 0x5A, 0x20, 0x24, 0x09, 0x09}, 0},
88+
{0xA3,
89+
{0x80, 0x01, 0x88, 0x30, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00,
90+
0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x4F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
91+
0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x6F, 0x58, 0x00, 0x00, 0x00, 0xFF},
92+
0},
93+
{0xA6,
94+
{0x03, 0x00, 0x24, 0x55, 0x36, 0x00, 0x39, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24,
95+
0x55, 0x38, 0x00, 0x37, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24, 0x11, 0x00, 0x00,
96+
0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0xEC, 0x11, 0x00, 0x03, 0x00, 0x03, 0x6E,
97+
0x6E, 0xFF, 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00},
98+
0},
99+
{0xA7,
100+
{0x19, 0x19, 0x80, 0x64, 0x40, 0x07, 0x16, 0x40, 0x00, 0x44, 0x03, 0x6E, 0x6E, 0x91, 0xFF,
101+
0x08, 0x80, 0x64, 0x40, 0x25, 0x34, 0x40, 0x00, 0x02, 0x01, 0x6E, 0x6E, 0x91, 0xFF, 0x08,
102+
0x80, 0x64, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80,
103+
0x64, 0x40, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x6E, 0x6E, 0x84, 0xFF, 0x08, 0x80, 0x44},
104+
0},
105+
{0xAC,
106+
{0x03, 0x19, 0x19, 0x18, 0x18, 0x06, 0x13, 0x13, 0x11, 0x11, 0x08, 0x08, 0x0A, 0x0A, 0x1C,
107+
0x1C, 0x07, 0x07, 0x00, 0x00, 0x02, 0x02, 0x01, 0x19, 0x19, 0x18, 0x18, 0x06, 0x12, 0x12,
108+
0x10, 0x10, 0x09, 0x09, 0x0B, 0x0B, 0x1C, 0x1C, 0x07, 0x07, 0x03, 0x03, 0x01, 0x01},
109+
0},
110+
{0xAD,
111+
{0xF0, 0x00, 0x46, 0x00, 0x03, 0x50, 0x50, 0xFF, 0xFF, 0xF0, 0x40, 0x06, 0x01,
112+
0x07, 0x42, 0x42, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF},
113+
0},
114+
{0xAE, {0xFE, 0x3F, 0x3F, 0xFE, 0x3F, 0x3F, 0x00}, 0},
115+
{0xB2,
116+
{0x15, 0x19, 0x05, 0x23, 0x49, 0xAF, 0x03, 0x2E, 0x5C, 0xD2, 0xFF, 0x10, 0x20, 0xFD, 0x20,
117+
0xC0, 0x00},
118+
0},
119+
{0xE8,
120+
{0x20, 0x6F, 0x04, 0x97, 0x97, 0x3E, 0x04, 0xDC, 0xDC, 0x3E, 0x06, 0xFA, 0x26, 0x3E},
121+
0},
122+
{0x75, {0x03, 0x04}, 0},
123+
{0xE7,
124+
{0x3B, 0x00, 0x00, 0x7C, 0xA1, 0x8C, 0x20, 0x1A, 0xF0, 0xB1, 0x50, 0x00,
125+
0x50, 0xB1, 0x50, 0xB1, 0x50, 0xD8, 0x00, 0x55, 0x00, 0xB1, 0x00, 0x45,
126+
0xC9, 0x6A, 0xFF, 0x5A, 0xD8, 0x18, 0x88, 0x15, 0xB1, 0x01, 0x01, 0x77},
127+
0},
128+
{0xEA, {0x13, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2C}, 0},
129+
{0xB0, {0x22, 0x43, 0x11, 0x61, 0x25, 0x43, 0x43}, 0},
130+
{0xB7, {0x00, 0x00, 0x73, 0x73}, 0},
131+
{0xBF, {0xA6, 0xAA}, 0},
132+
{0xA9, {0x00, 0x00, 0x73, 0xFF, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03}, 0},
133+
{0xC8,
134+
{0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06,
135+
0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32,
136+
0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF},
137+
0},
138+
{0xC9,
139+
{0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06,
140+
0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32,
141+
0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF},
142+
0},
143+
{0x11, {0x00}, 100},
144+
{0x29, {0x00}, 0},
145+
{0x35, {0x00}, 100},
146+
}};
147+
148+
// Send vendor-specific init sequence
149+
for (const auto &cmd : init_cmds) {
150+
write_command_(cmd.command, std::span<const uint8_t>(cmd.parameters), 0);
151+
if (cmd.delay_ms > 0) {
152+
std::this_thread::sleep_for(std::chrono::milliseconds(cmd.delay_ms));
153+
}
154+
std::this_thread::sleep_for(std::chrono::milliseconds(5));
155+
}
156+
157+
// Set MADCTL (mirror/color order)
158+
write_command_(static_cast<uint8_t>(Command::MADCTL), std::span<const uint8_t>(&madctl, 1), 0);
159+
// Set COLMOD (color depth)
160+
write_command_(static_cast<uint8_t>(Command::COLMOD), std::span<const uint8_t>(&colmod, 1), 0);
161+
162+
return true;
163+
}
164+
165+
static constexpr const char *id() { return "ST7123"; }
166+
167+
protected:
168+
static display_drivers::write_command_fn write_command_;
169+
static display_drivers::read_command_fn read_command_;
170+
static display_drivers::send_lines_fn lcd_send_lines_;
171+
static gpio_num_t reset_pin_;
172+
static gpio_num_t dc_pin_;
173+
static int offset_x_;
174+
static int offset_y_;
175+
static bool swap_xy_;
176+
static bool mirror_x_;
177+
static bool mirror_y_;
178+
static bool mirror_portrait_;
179+
static bool swap_color_order_;
180+
};
181+
182+
inline display_drivers::write_command_fn St7123::write_command_{nullptr};
183+
inline display_drivers::read_command_fn St7123::read_command_{nullptr};
184+
inline display_drivers::send_lines_fn St7123::lcd_send_lines_{nullptr};
185+
inline gpio_num_t St7123::reset_pin_{GPIO_NUM_NC};
186+
inline gpio_num_t St7123::dc_pin_{GPIO_NUM_NC};
187+
inline int St7123::offset_x_{0};
188+
inline int St7123::offset_y_{0};
189+
inline bool St7123::swap_xy_{false};
190+
inline bool St7123::mirror_x_{false};
191+
inline bool St7123::mirror_y_{false};
192+
inline bool St7123::mirror_portrait_{false};
193+
inline bool St7123::swap_color_order_{false};
194+
195+
} // namespace espp

components/m5stack-tab5/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The `espp::M5StackTab5` component provides a singleton hardware abstraction for
1010

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

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

69+
## Display Controller Auto-Detection
70+
71+
The M5Stack Tab5 hardware can be manufactured with one of two different MIPI-DSI display controllers:
72+
- **ILI9881** (earlier hardware revisions)
73+
- **ST7123** (newer hardware revisions)
74+
75+
The BSP automatically detects which display controller is present during initialization by:
76+
1. Attempting to initialize with the ILI9881 driver first
77+
2. If ILI9881 detection fails, falling back to ST7123 initialization
78+
3. Logging the detected controller type for debugging
79+
80+
This means your application code works seamlessly across both hardware variants without any code changes. You can optionally query the detected controller type:
81+
82+
```cpp
83+
auto& tab5 = espp::M5StackTab5::get();
84+
tab5.initialize_lcd();
85+
86+
// Query the detected controller
87+
auto controller_type = tab5.get_display_controller();
88+
const char* controller_name = tab5.get_display_controller_name();
89+
```
90+
6891
## Example
6992

7093
The [example](./example) shows how to use the `espp::M5StackTab5` hardware abstraction component to initialize and use various subsystems of the Tab5.

components/m5stack-tab5/example/main/m5stack_tab5_example.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,17 +307,20 @@ extern "C" void app_main(void) {
307307
// start a simple thread to do the lv_task_handler every 16ms
308308
logger.info("Starting LVGL task...");
309309
espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool {
310+
auto start_time = std::chrono::high_resolution_clock::now();
310311
{
311312
std::lock_guard<std::recursive_mutex> lock(lvgl_mutex);
312313
lv_task_handler();
313314
}
314315
std::unique_lock<std::mutex> lock(m);
315-
cv.wait_for(lock, 16ms);
316+
cv.wait_until(lock, start_time + 16ms, []() { return false; });
316317
return false;
317318
},
318319
.task_config = {
319320
.name = "lv_task",
320321
.stack_size_bytes = 10 * 1024,
322+
.priority = 20,
323+
.core_id = 1,
321324
}});
322325
lv_task.start();
323326

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

components/m5stack-tab5/include/m5stack-tab5.hpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "led.hpp"
4040
#include "pi4ioe5v.hpp"
4141
#include "rx8130ce.hpp"
42+
#include "st7123.hpp"
4243
#include "touchpad_input.hpp"
4344
// #include "wifi_ap.hpp"
4445
// #include "wifi_sta.hpp"
@@ -75,8 +76,33 @@ class M5StackTab5 : public BaseComponent {
7576
/// Alias for the pixel type used by the Tab5 display
7677
using Pixel = lv_color16_t;
7778

78-
/// Alias for the display driver used by the Tab5
79-
using DisplayDriver = espp::Ili9881;
79+
/// Enum for display controller type
80+
enum class DisplayController { UNKNOWN, ILI9881, ST7123 };
81+
82+
DisplayController detect_display_controller();
83+
84+
/// Get the detected display controller type
85+
/// \return The display controller type
86+
DisplayController get_display_controller() const { return display_controller_; }
87+
88+
/// Get a string name for the display controller
89+
/// \return String name of the controller
90+
const char *get_display_controller_name() const {
91+
return get_display_controller_name(display_controller_);
92+
}
93+
94+
/// Get a string name for the display controller
95+
/// \return String name of the controller
96+
const char *get_display_controller_name(DisplayController controller) const {
97+
switch (controller) {
98+
case DisplayController::ILI9881:
99+
return "ILI9881";
100+
case DisplayController::ST7123:
101+
return "ST7123";
102+
default:
103+
return "Unknown";
104+
}
105+
}
80106

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

739+
// Display controller detection
740+
DisplayController display_controller_{DisplayController::UNKNOWN};
741+
713742
// original function pointer for the panel del, init
714743
esp_err_t (*original_panel_del_)(esp_lcd_panel_t *panel){nullptr};
715744
esp_err_t (*original_panel_init_)(esp_lcd_panel_t *panel){nullptr};

0 commit comments

Comments
 (0)