-
-
Notifications
You must be signed in to change notification settings - Fork 78
M5Stack Tab5 support #449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
M5Stack Tab5 support #449
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| [general] | ||
| vendor=Guition | ||
| name=JC1060P470CIWY | ||
| name=JC1060P470C-I-W-Y | ||
|
|
||
| [hardware] | ||
| target=ESP32P4 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| file(GLOB_RECURSE SOURCE_FILES Source/*.c*) | ||
|
|
||
| idf_component_register( | ||
| SRCS ${SOURCE_FILES} | ||
| INCLUDE_DIRS "Source" | ||
| REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c GT911 PwmBacklight driver vfs fatfs | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| #include "devices/Display.h" | ||
| #include "devices/SdCard.h" | ||
|
|
||
| #include <Tactility/hal/Configuration.h> | ||
|
|
||
| using namespace tt::hal; | ||
|
|
||
| static const auto LOGGER = tt::Logger("Tab5"); | ||
|
|
||
| static DeviceVector createDevices() { | ||
| return { | ||
| createDisplay(), | ||
| createSdCard(), | ||
| }; | ||
| } | ||
|
|
||
| static bool initBoot() { | ||
| /* | ||
| PI4IOE5V6408-1 (0x43) | ||
| - Bit 0: RF internal/external switch | ||
| - Bit 1: Speaker enable | ||
| - Bit 2: External 5V bus enable | ||
| - Bit 3: / | ||
| - Bit 4: LCD reset | ||
| - Bit 5: Touch reset | ||
| - Bit 6: Camera reset | ||
| - Bit 7: Headphone detect | ||
|
|
||
| PI4IOE5V6408-2 (0x44) | ||
| - Bit 0: C6 WLAN enable | ||
| - Bit 1: / | ||
| - Bit 2: / | ||
| - Bit 3: USB-A 5V enable | ||
| - Bit 4: Device power: PWROFF_PLUSE | ||
| - Bit 5: IP2326: nCHG_QC_EN | ||
| - Bit 6: IP2326: CHG_STAT_LED | ||
| - Bit 7: IP2326: CHG_EN | ||
| */ | ||
|
|
||
| // Init byte arrays adapted from https://github.com/m5stack/M5GFX/blob/03565ccc96cb0b73c8b157f5ec3fbde439b034ad/src/M5GFX.cpp | ||
| static constexpr uint8_t reg_data_io1_1[] = { | ||
| 0x03, 0b01111111, // PI4IO_REG_IO_DIR | ||
| 0x05, 0b01000110, // PI4IO_REG_OUT_SET (bit4=LCD Reset, bit5=GT911 TouchReset -> LOW) | ||
| 0x07, 0b00000000, // PI4IO_REG_OUT_H_IM | ||
| 0x0D, 0b01111111, // PI4IO_REG_PULL_SEL | ||
| 0x0B, 0b01111111, // PI4IO_REG_PULL_EN | ||
| }; | ||
|
|
||
| static constexpr uint8_t reg_data_io1_2[] = { | ||
| 0x05, 0b01110110, // PI4IO_REG_OUT_SET (bit4=LCD Reset, bit5=GT911 TouchReset -> HIGH) | ||
| }; | ||
|
|
||
| static constexpr uint8_t reg_data_io2[] = { | ||
| 0x03, 0b10111001, // PI4IO_REG_IO_DIR | ||
| 0x07, 0b00000110, // PI4IO_REG_OUT_H_IM | ||
| 0x0D, 0b10111001, // PI4IO_REG_PULL_SEL | ||
| 0x0B, 0b11111001, // PI4IO_REG_PULL_EN | ||
| 0x09, 0b01000000, // PI4IO_REG_IN_DEF_STA | ||
| 0x11, 0b10111111, // PI4IO_REG_INT_MASK | ||
| 0x05, 0b10001001, // PI4IO_REG_OUT_SET (enable WiFi, USB-A 5V and CHG_EN) | ||
| }; | ||
|
|
||
| constexpr auto IO_EXPANDER1_ADDRESS = 0x43; | ||
| if (!i2c::masterWriteRegisterArray(I2C_NUM_0, IO_EXPANDER1_ADDRESS, reg_data_io1_1, sizeof(reg_data_io1_1))) { | ||
| LOGGER.error("IO expander 1 init failed in phase 1"); | ||
| return false; | ||
| } | ||
|
|
||
| constexpr auto IO_EXPANDER2_ADDRESS = 0x44; | ||
| if (!i2c::masterWriteRegisterArray(I2C_NUM_0, IO_EXPANDER2_ADDRESS, reg_data_io2, sizeof(reg_data_io2))) { | ||
| LOGGER.error("IO expander 2 init failed"); | ||
| return false; | ||
| } | ||
|
|
||
| // The M5Stack code applies this, but it's not known why | ||
| // TODO: Remove and test it extensively | ||
| tt::kernel::delayTicks(10); | ||
|
|
||
| if (!i2c::masterWriteRegisterArray(I2C_NUM_0, IO_EXPANDER1_ADDRESS, reg_data_io1_2, sizeof(reg_data_io1_2))) { | ||
| LOGGER.error("IO expander 1 init failed in phase 2"); | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| extern const Configuration hardwareConfiguration = { | ||
| .initBoot = initBoot, | ||
| .createDevices = createDevices, | ||
| .i2c = { | ||
| i2c::Configuration { | ||
| .name = "Internal", | ||
| .port = I2C_NUM_0, | ||
| .initMode = i2c::InitMode::ByTactility, | ||
| .isMutable = false, | ||
| .config = (i2c_config_t) { | ||
| .mode = I2C_MODE_MASTER, | ||
| .sda_io_num = GPIO_NUM_31, | ||
| .scl_io_num = GPIO_NUM_32, | ||
| .sda_pullup_en = true, | ||
| .scl_pullup_en = true, | ||
| .master = { | ||
| .clk_speed = 400000 | ||
| }, | ||
| .clk_flags = 0 | ||
| } | ||
| }, | ||
| i2c::Configuration { | ||
| .name = "Port A", | ||
| .port = I2C_NUM_1, | ||
| .initMode = i2c::InitMode::ByTactility, | ||
| .isMutable = false, | ||
| .config = (i2c_config_t) { | ||
| .mode = I2C_MODE_MASTER, | ||
| .sda_io_num = GPIO_NUM_53, | ||
| .scl_io_num = GPIO_NUM_54, | ||
| .sda_pullup_en = true, | ||
| .scl_pullup_en = true, | ||
| .master = { | ||
| .clk_speed = 400000 | ||
| }, | ||
| .clk_flags = 0 | ||
| } | ||
| } | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| #include "Display.h" | ||
| #include "Ili9881cDisplay.h" | ||
|
|
||
| #include <Gt911Touch.h> | ||
| #include <PwmBacklight.h> | ||
| #include <Tactility/Logger.h> | ||
| #include <Tactility/Mutex.h> | ||
| #include <Tactility/hal/gpio/Gpio.h> | ||
|
|
||
| constexpr auto LCD_PIN_RESET = GPIO_NUM_0; // Match P4 EV board reset line | ||
| constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_22; | ||
|
|
||
| static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() { | ||
| auto configuration = std::make_unique<Gt911Touch::Configuration>( | ||
| I2C_NUM_0, | ||
| 720, | ||
| 1280, | ||
| false, // swapXY | ||
| false, // mirrorX | ||
| false, // mirrorY | ||
| GPIO_NUM_NC, // reset pin | ||
| GPIO_NUM_NC // "GPIO_NUM_23 cannot be used due to resistor to 3V3" https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L76234 | ||
| ); | ||
|
|
||
| return std::make_shared<Gt911Touch>(std::move(configuration)); | ||
| } | ||
|
|
||
| std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() { | ||
| // Initialize PWM backlight | ||
| if (!driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 5000, LEDC_TIMER_1, LEDC_CHANNEL_0)) { | ||
| tt::Logger("Tab5").warn("Failed to initialize backlight"); | ||
| } | ||
|
|
||
| auto touch = createTouch(); | ||
|
|
||
| // Work-around to init touch : interrupt pin must be set to low | ||
| // Note: There is a resistor to 3V3 on interrupt pin which is blocking GT911 touch | ||
| // See https://github.com/espressif/esp-bsp/blob/ad668c765cbad177495a122181df0a70ff9f8f61/bsp/m5stack_tab5/src/m5stack_tab5.c#L777 | ||
| tt::hal::gpio::configure(23, tt::hal::gpio::Mode::Output, true, false); | ||
| tt::hal::gpio::setLevel(23, false); | ||
|
|
||
| auto configuration = std::make_shared<EspLcdConfiguration>(EspLcdConfiguration { | ||
| .horizontalResolution = 720, | ||
| .verticalResolution = 1280, | ||
| .gapX = 0, | ||
| .gapY = 0, | ||
| .monochrome = false, | ||
| .swapXY = false, | ||
| .mirrorX = false, | ||
| .mirrorY = false, | ||
| .invertColor = false, | ||
| .bufferSize = 0, // 0 = default (1/10 of screen) | ||
| .touch = touch, | ||
| .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, | ||
| .resetPin = LCD_PIN_RESET, | ||
| .lvglColorFormat = LV_COLOR_FORMAT_RGB565, | ||
| .lvglSwapBytes = false, | ||
| .rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB, | ||
| .bitsPerPixel = 16 | ||
| }); | ||
|
|
||
| const auto display = std::make_shared<Ili9881cDisplay>(configuration); | ||
| return std::static_pointer_cast<tt::hal::display::DisplayDevice>(display); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| #pragma once | ||
|
|
||
| #include <Tactility/hal/display/DisplayDevice.h> | ||
| #include <memory> | ||
|
|
||
| std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| #include "Ili9881cDisplay.h" | ||
| #include "disp_init_data.h" | ||
|
|
||
| #include <Tactility/Logger.h> | ||
| #include <esp_lcd_ili9881c.h> | ||
|
|
||
| static const auto LOGGER = tt::Logger("ILI9881C"); | ||
|
|
||
| Ili9881cDisplay::~Ili9881cDisplay() { | ||
| // TODO: This should happen during ::stop(), but this isn't currently exposed | ||
| if (mipiDsiBus != nullptr) { | ||
| esp_lcd_del_dsi_bus(mipiDsiBus); | ||
| mipiDsiBus = nullptr; | ||
| } | ||
| if (ldoChannel != nullptr) { | ||
| esp_ldo_release_channel(ldoChannel); | ||
| ldoChannel = nullptr; | ||
| } | ||
| } | ||
|
|
||
| bool Ili9881cDisplay::createMipiDsiBus() { | ||
| esp_ldo_channel_config_t ldo_mipi_phy_config = { | ||
| .chan_id = 3, | ||
| .voltage_mv = 2500, | ||
| .flags = { | ||
| .adjustable = 0, | ||
| .owned_by_hw = 0, | ||
| .bypass = 0 | ||
| } | ||
| }; | ||
|
|
||
| if (esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldoChannel) != ESP_OK) { | ||
| LOGGER.error("Failed to acquire LDO channel for MIPI DSI PHY"); | ||
| return false; | ||
| } | ||
|
|
||
| LOGGER.info("Powered on"); | ||
|
|
||
| // Create bus | ||
| // TODO: use MIPI_DSI_PHY_CLK_SRC_DEFAULT() in future ESP-IDF 6.0.0 update with esp_lcd_jd9165 library version 2.x | ||
| const esp_lcd_dsi_bus_config_t bus_config = { | ||
| .bus_id = 0, | ||
| .num_data_lanes = 2, | ||
| .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, | ||
| .lane_bit_rate_mbps = 1000 | ||
| }; | ||
|
|
||
| if (esp_lcd_new_dsi_bus(&bus_config, &mipiDsiBus) != ESP_OK) { | ||
| LOGGER.error("Failed to create bus"); | ||
| return false; | ||
| } | ||
|
|
||
| LOGGER.info("Bus created"); | ||
| return true; | ||
| } | ||
|
|
||
| bool Ili9881cDisplay::createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) { | ||
| // Initialize MIPI DSI bus if not already done | ||
| if (mipiDsiBus == nullptr) { | ||
| if (!createMipiDsiBus()) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // Use DBI interface to send LCD commands and parameters | ||
| esp_lcd_dbi_io_config_t dbi_config = ILI9881C_PANEL_IO_DBI_CONFIG(); | ||
|
|
||
| if (esp_lcd_new_panel_io_dbi(mipiDsiBus, &dbi_config, &ioHandle) != ESP_OK) { | ||
| LOGGER.error("Failed to create panel IO"); | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| esp_lcd_panel_dev_config_t Ili9881cDisplay::createPanelConfig(std::shared_ptr<EspLcdConfiguration> espLcdConfiguration, gpio_num_t resetPin) { | ||
| return { | ||
| .reset_gpio_num = resetPin, | ||
| .rgb_ele_order = espLcdConfiguration->rgbElementOrder, | ||
| .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, | ||
| .bits_per_pixel = static_cast<uint8_t>(espLcdConfiguration->bitsPerPixel), | ||
| .flags = { | ||
| .reset_active_high = 0 | ||
| }, | ||
| .vendor_config = nullptr // Will be set in createPanelHandle | ||
| }; | ||
| } | ||
|
|
||
| bool Ili9881cDisplay::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_panel_dev_config_t& panelConfig, esp_lcd_panel_handle_t& panelHandle) { | ||
| // Based on BSP: https://github.com/espressif/esp-bsp/blob/master/bsp/m5stack_tab5/README.md | ||
| // TODO: undo static | ||
| static esp_lcd_dpi_panel_config_t dpi_config = { | ||
| .virtual_channel = 0, | ||
| .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, | ||
| .dpi_clock_freq_mhz = 60, | ||
| .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, | ||
| .in_color_format = LCD_COLOR_FMT_RGB565, | ||
| .out_color_format = LCD_COLOR_FMT_RGB565, | ||
| .num_fbs = 1, // TODO: 2? | ||
| .video_timing = | ||
| { | ||
| .h_size = 720, | ||
| .v_size = 1280, | ||
| .hsync_pulse_width = 40, | ||
| .hsync_back_porch = 140, | ||
| .hsync_front_porch = 40, | ||
| .vsync_pulse_width = 4, | ||
| .vsync_back_porch = 20, | ||
| .vsync_front_porch = 20, | ||
| }, | ||
| .flags { | ||
| .use_dma2d = 1, // TODO: true? | ||
| .disable_lp = 0, | ||
| } | ||
| }; | ||
|
|
||
| // TODO: undo static | ||
| static ili9881c_vendor_config_t vendor_config = { | ||
| .init_cmds = disp_init_data, | ||
| .init_cmds_size = std::size(disp_init_data), | ||
| .mipi_config = { | ||
| .dsi_bus = mipiDsiBus, | ||
| .dpi_config = &dpi_config, | ||
| .lane_num = 2, | ||
| }, | ||
| }; | ||
|
Comment on lines
+117
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Static The static While the TODO suggests this is known, the current implementation is unsafe for object lifecycle scenarios. Consider either:
🐛 Proposed fix - remove static- // TODO: undo static
- static esp_lcd_dpi_panel_config_t dpi_config = {
+ esp_lcd_dpi_panel_config_t dpi_config = {
.virtual_channel = 0,
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = 60,
...
};
- // TODO: undo static
- static ili9881c_vendor_config_t vendor_config = {
+ ili9881c_vendor_config_t vendor_config = {
.init_cmds = disp_init_data,
.init_cmds_size = std::size(disp_init_data),
.mipi_config = {
.dsi_bus = mipiDsiBus,
.dpi_config = &dpi_config,
.lane_num = 2,
},
};Note: If removing 🤖 Prompt for AI Agents |
||
|
|
||
| // Create a mutable copy of panelConfig to set vendor_config | ||
| esp_lcd_panel_dev_config_t mutable_panel_config = panelConfig; | ||
| mutable_panel_config.vendor_config = &vendor_config; | ||
|
|
||
| if (esp_lcd_new_panel_ili9881c(ioHandle, &mutable_panel_config, &panelHandle) != ESP_OK) { | ||
| LOGGER.error("Failed to create panel"); | ||
| return false; | ||
| } | ||
|
|
||
| LOGGER.info("Panel created successfully"); | ||
| // Defer reset/init to base class applyConfiguration to avoid double initialization | ||
| return true; | ||
| } | ||
|
|
||
| lvgl_port_display_dsi_cfg_t Ili9881cDisplay::getLvglPortDisplayDsiConfig(esp_lcd_panel_io_handle_t /*ioHandle*/, esp_lcd_panel_handle_t /*panelHandle*/) { | ||
| // Disable avoid_tearing to prevent stalls/blank flashes when other tasks (e.g. flash writes) block timing | ||
| return lvgl_port_display_dsi_cfg_t{ | ||
| .flags = { | ||
| .avoid_tearing = 0, | ||
| }, | ||
| }; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.